乐趣区

关于IDEA:我写了个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) 代码的操作加个快捷键,能够让咱们更加不便的进行操作。

装置应用验证

  • 因为公布插件须要到 https://plugins.jetbrains.com/ 并期待审核,所以能够在 release 包下载:https://github.com/fuzhengwei/vo2dto/releases/tag/v2.2.2 下载后手动装置即可。

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

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

1. 复制对象

2. 生成对象

3. 最终成果

  • 最终你就能够看到曾经把你全副的对象转换,主动生成进去代码了,是不是很香。
  • 如果你间接应用快捷键 Ctrl + Shift + K 也是能够主动生成的。

拿去用用吧,最好再给提一些倡议,提交 issue、提交 PR,都十分的欢送!

退出移动版