作者:小傅哥
博客: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. 代码织入锚点
对于代码织入锚点前,咱们在模板类中定义的办法,须要实现接口进行解决,重点包含:
- 通过
CommonDataKeys.EDITOR.getData(dataContext)
、CommonDataKeys.PSI_ELEMENT.getData(dataContext)
封装 GenerateContext 对象上下文信息,也就是一些类、锚点地位、文档编辑的对象。 - 通过 PsiClass 获取光标地位对应的 Class 类信息,在通过
psiClass.getMethods()
读取对象办法,把 set 办法过滤出来,封装到汇合中。 - 通过
Toolkit.getDefaultToolkit().getSystemClipboard()
获取剪切板信息,也就是你在锚点地位给对象生成x.set(y.get)
时,复制的 Y y 对象,并开始提取 get 办法,同样封装到汇合中。 - 那么最初就是代码的组装和织入动作了,这部分咱们的代码如下;
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 步
操作;
- 复制你须要被转换的对象,因为复制当前就能够被插件获取到剪切板信息了,也就能提取到 get 办法汇合。
- 把鼠标定义到须要转换设置值的对象,之后鼠标右键,抉择
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 构造。
七、系列举荐
- 方案设计:基于 IDEA 插件开发和字节码插桩技术,实现研发交付品质主动剖析
- 刚火了的中台转头就拆,一大波公司放不下又拿不起来!
- 《IntelliJ IDEA 插件开发》第 4 节:扩大创立工程向导步骤,开发 DDD 脚手架
- HashMap 外围常识,扰动函数、负载因子、扩容链表拆分,深度学习
- p3c 插件,是怎么查看出你那屎山的代码?