让人头疼的对象转换

头炸,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,都非常的欢迎!

我写了个IDEA开源插件,vo2dto 一键生成对象转换的更多相关文章

  1. 开源工具 DotnetRSA 快速生成和转换RSA秘钥

    一.简介 DotnetRSA 是一个利用 .NET Core 2.1 开发的 .NET Global Tool,是可以想npm全局安装一样,安装在你的系统中,只需敲一行命令便可以快速生成RSA加密算法 ...

  2. 开源框架 WebFirst 一键生成项目,在线建表

    1.WebFirst框架描述 WebFirst  是果糖大数据团队开发的新一代 高性能 代码生成器&数据库设计工具,由.net core 3.1 + sqlsugar 开发 导入1000个表只 ...

  3. 玉渊潭赏樱花有感:从无到有写一个jQuery开源插件

    “玉渊潭公园樱花节”是每年樱花绽放时,都会在玉渊潭公园樱举办樱花节,游客前往玉渊潭公园,可以欣赏到20个品种2000株樱花.2016玉渊潭樱花节时间:3月中旬-4月中旬观赏最佳,2016年3月23日开 ...

  4. .NET开源插件内核

    http://www.cnblogs.com/newmin/ .NET开源插件内核:支持WinForm和Asp.net. 设计的初衷是:利用“开发平台 + 插件内核"来开发子系统,及对系统进 ...

  5. 解放双手,不写SQL!一个开源mybatis神器

    什么是通用 Mapper? 它是一个可以方便的使用 Mybatis 进行单表的增删改查优秀开源产品.它使用拦截器来实现具体的执行 Sql,完全使用原生的 Mybatis 进行操作.在 Github 上 ...

  6. IDEA 集成 Docker 插件实现一键远程部署 SpringBoot 应用,无需三方依赖,开源微服务全栈项目有来商城云环境的部署方式

    一. 前言 最近有些童鞋对开源微服务商城项目 youlai-mall 如何部署到线上环境以及项目中 的Dockerfile 文件有疑问,所以写了这篇文章做个答疑以及演示完整的微服务项目发布到线上的流程 ...

  7. Metronic 使用到的开源插件汇总

    Metronic 是一套完整的 UI 模板,但不仅仅是模板,更应该说是一个 UI 框架.它除了提供了大量网页模板,也提供了非常多的 UI 组件,并且应用了众多 jQuery 插件.通过这些资源的整合, ...

  8. 用jQuery写了一个模态框插件

    用jQuery写了一个模态框插件 大家觉得下面的框框好看么, 水印可以去掉(这个任务交给你们了(- o -)~zZ); "info"框 $("div").con ...

  9. [Java 开源项目]一款无需写任何代码,即可一键生成前后端代码的工具

    作者:HelloGitHub-小鱼干 JeecgBoot 是一款基于代码生成器的低代码开发平台,零代码开发.JeecgBoot 采用开发模式:Online Coding 模式-> 代码生成器模式 ...

随机推荐

  1. Atcoder Regular Contest 058 D - 文字列大好きいろはちゃん / Iroha Loves Strings(单调栈+Z 函数)

    洛谷题面传送门 & Atcoder 题面传送门 神仙题. mol 一发现场(bushi)独立切掉此题的 ycx %%%%%%% 首先咱们可以想到一个非常 naive 的 DP,\(dp_{i, ...

  2. Topcoder 14719 - RatingProgressAward(最小割)

    题面传送门 神仙最小割--好久没写过网络流了,故写题解以祭之( 首先考虑一个非常 trivial 的问题:如果知道排列顺序之后怎样计算最大值,用脚趾头想一下就能知道是原序列的最大子段和,因为每个课程之 ...

  3. Linux mount挂载磁盘报错 mount: wrong fs type, bad option, bad superblock on /dev/vdb

    Linux mount挂载磁盘报错  mount: wrong fs type, bad option, bad superblock on /dev/vdb Linux挂载磁盘报如下错误: moun ...

  4. Notepad++—显示代码对齐是使用了制表符还是空格

    使用Notepad++打开脚本,勾选"显示空格与制表符",此时你会看到代码对齐使用了制表符与空格 右箭头:TAB:空格:点: 参考:https://www.cnblogs.com/ ...

  5. 深入浅出KMP

    前言:曾经有次在阿里的面试中遇到这个基础的问题,当时知道有这么回事,可是时间久了便 想不起来,可能是不怎么用到,基本调用库什么的,还有个是理解不深刻,不能得到show me the code 的程度, ...

  6. 3 - 简单了解一下springboot中的yml语法 和 使用yml赋值

    1.简单了解yml语法 2.使用yml给实体类赋值 准备工作:导入依赖 <!-- 这个jar包就是为了实体类中使用@ConfigurationProperties(prefix = " ...

  7. 学习java 7.3

    学习内容:定义类不需要加static 成员方法在多个对象时是可以共用的,而成员变量不可以共用,多个对象指向一个内存时,改变变量的值,对象所在的类中的变量都会改变 成员变量前加private,成员方法前 ...

  8. hadoop-uber作业模式

    如果作业很小,就选择和自己在同一个JVM上运行任务,与在一个节点上顺序运行这些任务相比,当application master 判断在新的容器中的分配和运行任务的开销大于并行运行它们的开销时,就会发生 ...

  9. 【Reverse】DLL注入

    DLL注入就是将dll粘贴到指定的进程空间中,通过dll状态触发目标事件 DLL注入的大概流程 https://uploader.shimo.im/f/CXFwwkEH6FPM0rtT.png!thu ...

  10. C++构造函数和析构函数初步认识

    构造函数 1.构造函数与类名相同,是特殊的公有成员函数.2.构造函数无函数返回类型说明,实际上构造函数是有返回值的,其返回值类型即为构造函数所构建到的对象.3.当新对象被建立时,构造函数便被自动调用, ...