作者:小傅哥

博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!

一、前言

给你机会,你也不中用啊

这些年从事编程开发以来,我好像发现了大部分研发那些不愿意干的事,都成就了别人。就像部署服务麻烦,有了Docker简单CRUD不想开发,有了低代码给方法代码加监控繁琐、有了非入侵的全链路监控

而这些原本你也在干的事情,因为没有想法、没有创新、没有思考,也可能是没有能力,所以一直都是在搬砖、码砖、砌砖,反反复复、来来回回。键盘敲的是越来越快了,代码搞的是越来越烂了。薪资没搞上去,头发是越来越少了。

对于想走技术路线的码农,千万不要只是停留在业务功能的逻辑开发上,只有当你有了共性凝练的逻辑思维,才会逐步思考怎么把一件重复的事做成一个通用的服务或者组件,而这些东西的落地不仅需要你会写代码,还要会思考更要会去索引一些你需要的技术,并用自学的方式来补充这部分技能。

二、需求目的

你想写对象间的get、set吗?烦,烦死了,尤其是在DDD四层架构下,有了多层防污处理,一会一个vo2dto、一会一个vo2do、一会一个do2po,虽然有很多工具的操作,但还是得写呀。

怎么办?不要慌,这是机会呀,我们做个插件搞定它,让它可以自动的给我生成get、set代码,在IDEA Plugin的处理下,选择好需要生成对象代码的锚点,复制下转换对象,自动织入代码,1s钟搞定!

三、案例开发

1. 工程结构

guide-idea-plugin-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

源码获取:#公众号:bugstack虫洞栈 回复:idea 即可下载全部 IDEA 插件开发源码

在此 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++);
new WriteCommandAction(generateContext.getProject()) {
@Override
protected void run(@NotNull Result result) throws Throwable {
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);
}
}.execute();
}
});
}
  • 织入代码的流程动作,主要是对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) 代码的操作加个快捷键,可以让我们更加方便的进行操作。

四、测试验证

点击 Plugin 启动 IDEA 插件,之后有2步操作;

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

1. 复制对象

2. 生成对象

3. 最终效果

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

五、扩展接口

获取当前编辑的文件, 通过PsiFile可获得PsiClass, PsiField等 PsiFile psiFile = e.getData(LangDataKeys.PSI_FILE);
获取当前的project对象 Project project = e.getProject();
获取数据上下文 DataContext dataContext = e.getDataContext();
获取到数据上下文后,通过CommonDataKeys对象可以获得该File的所有信息 Editor editor = CommonDataKeys.EDITOR.getData(dataContext);<br />PsiFile psiFile = CommonDataKeys.PSI_FILE.getData(dataContext);<br />VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
GlobalSearchScope中有Project域,Moudule域,File域等等 PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, name, GlobalSearchScope);
类似于IDE中的Find Usages操作 Query<PsiReference> search = ReferencesSearch.search(PsiElement);
重命名 RenameRefactoring newName = RefactoringFactory.getInstance(Project).createRename(PsiElement, "newName");
搜索一个类的所有子类,重载方法较多,具体不再一一列出 Query<PsiClass> search = ClassInheritorsSearch.search(PsiClass);
根据类的全限定名查询PsiClass,下面这个方法是查询Project域 PsiClass psiClass = JavaPsiFacade.getInstance(project).findClass(classQualifiedName, GlobalSearchScope.projectScope(project));
获取Java类所在的Package PsiPackage psiPackage = JavaPsiFacade.getInstance(Project).findPackage(classQualifiedName);
查找被特定方法重写的方法 Query<PsiMethod> search = OverridingMethodsSearch.search(PsiMethod);

六、总结

  • 本章节中我们涉及了不少对工程对象的类和方法进行操作的处理,这些内容的实践也非常适合你在其他场景使用,比如给工程的接口生成一些自动化API的操作。
  • 在给对象生成 x.set(y.get) 的时候,我也在思考该怎么更合理的把转换对象代入到插件的代码逻辑中,可能会想到是通过弹窗配置或者代码扫描到上一行,但这样的方式终究是不舒服的,考虑到实际自己编码的习惯操作,其实我们做这步的时候,复制是第一步动作,为了更好的体验,所以这里选择了用复制来处理这块的连接性问题。
  • 本系列的 IDEA Plugin 开发都以遵循 DDD 工程结构思想为设计和实现,虽然整体内容看上去也不复杂,但希望这些框架的沉淀可以为 DDD 落地铺路,让更多的工程研发人员适应 DDD 结构。

七、系列推荐

解放双手,自动生成“x.set(y.get)”,搞定vo2dto转换的更多相关文章

  1. 使用Myeclipse10.0自动生成搭建SSH框架(数据库表自动反向转换成Hibernate实体)实现用户登陆

    我这里使用的数据库是mysql5.0 数据是上课用的.这些都不是重点,重要的是学会这个方法: 创建好数据库: create database jboadefault character set utf ...

  2. C代码工具--自动生成enum值和名字映射代码

    这年头好像继续做C语言的人不多了,年轻人大多去互联网和移动应用.确实,那两个领域现在来钱快,且总是供不应求.就说刚刚在一个旧同事的微信群里,有人刚放出自己有团队可以做App几分钟,哇塞,好几个人说有项 ...

  3. 10行Python代码自动清理电脑内重复文件,解放双手!

    大家好,又到了Python办公自动化系列. 今天分享一个系统层面的自动化案例: 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做 ...

  4. 解放双手,markdown文章神器,Typora+PicGo+七牛云图床实现自动上传图片

    本文主要分享使用Typora作为Markdown编辑器,PicGo为上传图片工具,使用七牛云做存储来解放双手实现图片的自动化上传与管理.提高写作效率,提升逼格.用过 Markdown 的朋友一定会深深 ...

  5. 写完批处理脚本,再写个Gradle脚本,解放双手

    前言 上一篇写个批处理来帮忙干活---遍历&字符串处理中,我们已经学习如何写批处理脚本来帮我们做一些简单的重复性工作,本篇继续来学习如何用 Gradle 写脚本,让它也来帮我们干活 Gradl ...

  6. 【原创】使用批处理脚本自动生成并上传NuGet包

    Hello 大家好,我是TANZAME,我们又见面了. NuGet 是什么这里就不再重复啰嗦,园子里一搜一大把.今天要跟大家分享的是,在日常开发过程中如何统一管理我们的包,如何通过批处理脚本生成包并自 ...

  7. 10个 解放双手的 IDEA 插件,这些代码都不用写(第二弹)

    本文案例收录在 https://github.com/chengxy-nds/Springboot-Notebook 大家好,我是小富~ 鸽了很久没发文,不写文章的日子真的好惬意,每天也不用愁着写点什 ...

  8. JavaScript自动生成博文目录导航

    转载于:JavaScript自动生成博文目录导航 我们在写博客的时候,如果博文里面有目录,会给人结构清晰.一种一目了然的感觉,看目录就知道这篇博文要讲解的内容,并且点击目录标题就可以跳转到 具体的内容 ...

  9. mybatis generator maven插件自动生成代码

    如果你正为无聊Dao代码的编写感到苦恼,如果你正为怕一个单词拼错导致Dao操作失败而感到苦恼,那么就可以考虑一些Mybatis generator这个差价,它会帮我们自动生成代码,类似于Hiberna ...

随机推荐

  1. R数据分析:如何给结构方程画路径图,tidySEM包详解

    之前一直是用semPlot这个包给来进行结构方程模型的路径绘制,自从用了tidySEM这个包后就发现之前那个包不香了,今天就给大家分享一下tidySEM. 这个包的很大特点就是所有的画图原始都是存在数 ...

  2. C#-WPF数据绑定基础(一)

    前言:WPF数据绑定技术有效的提高了程序的容错率,可以最大程度的保持程序的健壮性,从而降低程序在使用过程中崩掉的可能性. 接下来,我将分享一下我在写测量程序过程中所用到的数据绑定方面的知识 首先,我所 ...

  3. Maven 源码解析:依赖调解是如何实现的?

    系列文章目录(请务必按照顺序阅读): Maven 依赖调解源码解析(一):开篇 Maven 依赖调解源码解析(二):如何调试 Maven 源码和插件源码 Maven 依赖调解源码解析(三):传递依赖, ...

  4. 路径前面加与不加"/"的区别

    加"/"是绝对路径, 不加"/"是相对路径.假设你的这个html文件的路径是www.example.com/path/to/html/a.html,那么src= ...

  5. Android——ViewHolder的作用与用法

    转载至:https://www.cnblogs.com/wugu-ren/p/6106379.htmlViewHolder通常出现在适配器里,为的是listview滚动的时候快速设置值,而不必每次都重 ...

  6. [loj2470]有向图

    参考ExtremeSpanningTrees,考虑优化整体二分时求$g_{i}\in \{w_{mid},w_{mid+1}\}$的最优解 对于$m=n-1$的问题,不需要去网络流,可以直接树形dp ...

  7. git添加新工程

    git init git remote add origin 码云路径 git pull origin master 代码拉本地后 git add . git commit -m '新添加的文件内容描 ...

  8. 【Lua】实现代码执行覆盖率统计工具

    一.如何评估测试过程的测试情况? 很多时候完成功能测试后就会发布上线,甚至交叉和回归都没有足够的时间去执行,然后通过线上的补丁对遗漏的问题进行修复.如果可以在发布前了解本次测试过程所覆盖代码执行的比例 ...

  9. CF1264D2 Beautiful Bracket Sequence (hard version)

    考虑\(D1\)的\(O(n^2)\),我们直接进行组合处理. 考虑在\(p\)这个位置,左边有\(l\)个(,右边有\(r\)个),左边有\(l\)个问号,右边有\(r\)个问号. 这个位置的贡献为 ...

  10. 洛谷 P5249 - [LnOI2019]加特林轮盘赌(期望 dp+高斯消元)

    题面传送门 期望真 nm 有意思,所以蒟蒻又来颓期望辣 先特判掉 \(P_0=0\) 的情况,下面假设 \(P_0\ne 0\). 首先注意到我们每次将加特林对准一个人,如果这个人被毙掉了,那么相当于 ...