• 我写了个IDEA开源插件,vo2dto 一键生成对象转换


    让人头疼的对象转换

    头炸,po2vovo2dodo2dto,一堆对象属性,取出来塞进来。要不是为了 DDD 架构下的各个分层防腐,真想一竿子怼下去

    那上 BeanUtils.copyProperties 呀,其实对象转换不只这个方法,还有同类的12种手段,但综合来看还是 MapStruct 在编译期生成x.set(y.get)代码的最终效果最好,整体压测数据如下:

    • BeanUtils.copyProperties 是大家代码里最常出现的工具类,但只要你不把它用错成 Apache 包下的,而是使用 Spring 提供的,就基本还不会对性能造成多大影响。
    • 但如果说性能更好,可替代手动get、set的,还是 MapStruct 更好用,因为它本身就是在编译期生成get、set代码,和我们写get、set一样。
    • 其他一些组件包主要基于 AOPASMCGlib,的技术手段实现的,所以也会有相应的性能损耗。

    咋办? 给每一个转换对象属性的操作都写一个 MapStruct 吗?也不合适呀,有些就是方法中很简单的操作一下,写写代码就能搞定,问题就是懒的写,一多了还容易写错。别提 BeanUtils.copyProperties 有时候确定有性能问题,从编码上还看不出来属性的添加和减少

    所以 我要写个 IDEA Plugin 解决这个问题,目的就一个,通过 IDEA 插件开发能力,定义到我需要转换属性的2个对象,把2个对象的转换代码自动生成出来,并织入到我的对象定位位置上。

    设计一个插件

    我是这么思考的:在 IDEA 开发工程代码中,在需要转换的2个对象间,复制第一个对象和属性,再把光标定位到转换对象上,接下来我给它提供个按钮或者快捷键,一点就把所有转换代码生成出来,这样不就解决了需要手写的问题了吗,效果如下:

    1. 工程结构

    vo2dto
    ├── .gradle
    └── src
        ├── main
        │   └── java
        │   	└── cn.bugstack.guide.idea.plugin 
        │       	├── action
        │       	│	└── Vo2DtoGenerateAction.java     
        │       	├── application
        │       	│	└── IGenerateVo2Dto.java      
        │       	├── domain
        │       	│	├── model
        │       	│	│	├── GenerateContext.java     
        │       	│	│	├── GetObjConfigDO.java      
        │       	│	│	└── SetObjConfigDO.java       
        │       	│	└── service   
        │       	│	 	├── impl     
        │       	│	 	│	└── GenerateVo2DtoImpl.java    
        │       	│	 	└── AbstractGenerateVo2Dto.java      
        │       	└── infrastructure   
        │       	 	└── Utils.java    
        ├── resources
        │   └── META-INF
        │       └── plugin.xml 
        ├── build.gradle  
        └── gradle.properties
    

    源码获取https://github.com/fuzhengwei/vo2dto - 欢迎提交 issue、PR 共同维护

    在此 IDEA 插件工程中,主要分为4块区域:

    • action:提供菜单栏窗体,在插件中我们把这个菜单栏配置到 Generate 下,也就是通常你生成 get、set、constructor 方法的地方。
    • application:应用层定义接口,这里定义了一个用于生成代码并织入到锚点的方法接口。
    • domian:领域层专门处理代码的生成和织入动作,这一层把代码的中锚点位置获取、剪切板信息复制、应用上下文、类中get、set的解析,以及最终把生成代码织入到锚点后的操作。
    • infrastructure:在基础层提供了工具类,用于获取剪切板信息和锚点位置判断等操作。

    2. 织入代码接口

    cn.bugstack.guide.idea.plugin.application.IGenerateVo2Dto

    public interface IGenerateVo2Dto {
    
        void doGenerate(Project project, DataContext dataContext);
    
    }
    
    • 定义接口其实非常重要的一步,因为这样一步就把生成的标准定义下来了,所有的生成动作都要从这个接口发起。学习源码也一样,你要找到一个核心的入口点,才能更好的开始学习

    3. 定义模板方法

    因为生成代码并织入锚点位置的操作,整个来看其实也是一套流程操作,因为在这个过程需要;获取上下文信息(也就是工程对象)、给当前锚点位置的类提取 set 方法集合、之后在给Ctrl+C剪切板上的信息读取出来提取 get 方法集合,第四步把set、get进行组合并织入代码到锚点位置。整体过程如下:

    • 那么在使用模板方法后,就可以非常容易的把写在一个类里的成片的代码按照职责进行拆分。
    • 同时因为有了模板的定义,也就定义出了整个一套标准流程,在流程规范下执行代码,后续再补充逻辑迭代功能也会更加容易。

    4. 代码织入锚点

    关于代码织入锚点前,我们在模板类中定义的方法,需要实现接口进行处理,重点包括:

    1. 通过 CommonDataKeys.EDITOR.getData(dataContext)CommonDataKeys.PSI_ELEMENT.getData(dataContext) 封装 GenerateContext 对象上下文信息,也就是一些类、锚点位置、文档编辑的对象。
    2. 通过 PsiClass 获取光标位置对应的 Class 类信息,在通过 psiClass.getMethods() 读取对象方法,把 set 方法过滤出来,封装到集合中。
    3. 通过 Toolkit.getDefaultToolkit().getSystemClipboard() 获取剪切板信息,也就是你在锚点位置给对象生成 x.set(y.get) 时,复制的 Y y 对象,并开始提取 get 方法,同样封装到集合中。
    4. 那么最后就是代码的组装和织入动作了,这部分我们的代码如下;

    cn.bugstack.guide.idea.plugin.domain.service.impl.GenerateVo2DtoImpl

    @Override
    protected void weavingSetGetCode(GenerateContext generateContext, SetObjConfigDO setObjConfigDO, GetObjConfigDO getObjConfigDO) {
        Application application = ApplicationManager.getApplication();
        // 获取空格位置长度
        int distance = Utils.getWordStartOffset(generateContext.getEditorText(), generateContext.getOffset()) - generateContext.getStartOffset();
        application.runWriteAction(() -> {
            StringBuilder blankSpace = new StringBuilder();
            for (int i = 0; i < distance; i++) {
                blankSpace.append(" ");
            }
            int lineNumberCurrent = generateContext.getDocument().getLineNumber(generateContext.getOffset()) + 1;
            List<String> setMtdList = setObjConfigDO.getParamList();
            for (String param : setMtdList) {
                int lineStartOffset = generateContext.getDocument().getLineStartOffset(lineNumberCurrent++);
                
                WriteCommandAction.runWriteCommandAction(generateContext.getProject(), () -> {
                    generateContext.getDocument().insertString(lineStartOffset, blankSpace + setObjConfigDO.getClazzParamName() + "." + setObjConfigDO.getParamMtdMap().get(param) + "(" + (null == getObjConfigDO.getParamMtdMap().get(param) ? "" : getObjConfigDO.getClazzParam() + "." + getObjConfigDO.getParamMtdMap().get(param) + "()") + ");\n");
                    generateContext.getEditor().getCaretModel().moveToOffset(lineStartOffset + 2);
                    generateContext.getEditor().getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
                });
            }
        });
    }
    
    • 织入代码的流程动作,主要是对set方法集合进行遍历,把对应的x.set(y.get)通过 document.insertString 到具体的位置和代码。
    • 最终所有生成的代码方法织入完成,即完成了整个 x.set(y.get) 的过程。

    5. 配置菜单入口

    plugin.xml

    <actions>
        <!-- Add your actions here -->
        <action id="Vo2DtoGenerateAction" class="cn.bugstack.guide.idea.plugin.action.Vo2DtoGenerateAction"
                text="Vo2Dto - 小傅哥" description="Vo2Dto generate util" icon="/icons/logo.png">
            <add-to-group group-id="GenerateGroup" anchor="last"/>
            <keyboard-shortcut keymap="$default" first-keystroke="ctrl shift K"/>
        </action>
    </actions>
    
    • 这次我们给生成 x.set(y.get) 代码的操作加个快捷键,可以让我们更加方便的进行操作。

    安装使用验证

    接下来你就可以 So Easy 的转换对象了,操作如下:

    1. 复制你需要被转换的对象,因为复制以后就可以被插件获取到剪切板信息了,也就能提取到get方法集合。
    2. 把鼠标定义到需要转换设置值的对象,之后鼠标右键,选择 Generate -> Vo2Dto - 小傅哥

    1. 复制对象

    2. 生成对象

    3. 最终效果

    • 最终你就可以看到已经把你全部的对象转换,自动生成出来代码了,是不是很香。
    • 如果你直接使用快捷键 Ctrl + Shift + K 也是可以自动生成的。

    拿去用用吧,最好再给提一些建议,提交issue、提交PR,都非常的欢迎!

    公众号:bugstack虫洞栈 | 作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果能为您提供帮助,请给予支持(关注、点赞、分享)!
  • 相关阅读:
    学习方法
    Python——语言基础
    JSP——JavaServer Page中的隐式对象(implicit object)、指令(directive)、脚本元素(scripting element)、动作(action)、EL表达式
    Socket——实现一个简单的静态网页服务器
    CSS效果——绝对居中
    Java——重写hashCode()和euqals()方法
    Java操作符——i++ 和 ++i的区别
    JDBC——数据库连接池以及JDBC代码模版模版
    JDBC——DBHelper代码模版
    JDBC——JDBC基础
  • 原文地址:https://www.cnblogs.com/xiaofuge/p/15723404.html
Copyright © 2020-2023  润新知