当前位置:网站首页>关于我写的IDEA插件能一键生成service,mapper....这件事(附源码)
关于我写的IDEA插件能一键生成service,mapper....这件事(附源码)
2022-07-26 15:14:00 【Trouvailless】
最终效果
为了留住兄弟们继续看下去,还是先来看看最终效果吧
在新建一个实体类的时候把相关联的mapper、service都一并生成

当做出这个效果之后,面对一个结构复杂的DDD的项目,我再也不用新建一个实体类,新建一个mapper,新建一个service,新建一个model,再来一个转换类,再来一个。。。。。我想,这大概就是工业革命吧
难道逆向工程不香么?

什么?逆向工程能用来装逼么!谁能拒绝。。。
调研Api
当然可以上来就直接开搞,毕竟用模版生成文件这种操作,用java实现起来也不是什么难事。但是如果仅仅是实现一个简单的功能,就很难提起兴致。如果在研究兴趣的同时,还能提高一些专业能力的话,岂不美哉
那我们来分析一下,针对这个点子,需要做的事情其实很少
- 找到需要生成文件目录
- 创建一些模版
- 把文件生成过去
但是事情往往没有想象中这么简单
- 这些目录找起来并不轻松,在一个陌生的项目环境下,直接通过文件系统找文件目录总是不那么靠谱
- 直接在插件中写死模版当然很简单,但是损失了不小的灵活性,总不能每次新建一个项目就去把插件发一个版本
- 生成文件的话其实问题不大,但是总觉得用原生的硬搞不是特别优雅,既然我们在IDEA的地盘做事,就要尽量按照他的风格来,应该也可以得到一些便利
针对这些问题,我的选择当然是去看IDEA的源码了,既做插件,又能锻炼阅读源码的能力,一菜多吃!
既然这次主要是文件生成操作,首先肯定是关注创建Class这个Action,大家都是新建文件,只是目录不同,在这个Action中肯定可以找到很多线索。
总之,最后定位到了CreateClassAction这个类,源码我就不全都截出来给大家了,具体的逻辑部分有兴趣的兄弟可以自己去自己研究一下,只说我这边研究得到的线索
创建输入类名的弹窗
这个类中有一个比较有用的方法CreateClassAction. buildDialog(Project project, PsiDirectory directory,CreateFileFromTemplateDialog.Builder builder)
protected void buildDialog(final Project project, PsiDirectory directory, CreateFileFromTemplateDialog.Builder builder) {
builder.setTitle(JavaBundle.message("action.create.new.class", new Object[0])).addKind(JavaPsiBundle.message("node.class.tooltip", new Object[0]), PlatformIcons.CLASS_ICON, "Class").addKind(JavaPsiBundle.message("node.interface.tooltip", new Object[0]), PlatformIcons.INTERFACE_ICON, "Interface");
//根据版本添加一些可以创建的类型class,interface这些
...
//一些验证规则
builder.setValidator(new InputValidatorEx() {
//当验证不通过返回的报错信息
public String getErrorText(String inputString) {
//if中有判断类名是否合法的api,如果有需要的话可以直接调用
if (inputString.length() > 0 && !PsiNameHelper.getInstance(project).isQualifiedName(inputString)) {
return JavaErrorBundle.message("create.class.action.this.not.valid.java.qualified.name", new Object[0]);
}
...
}
//检查输入,他这个直接返回true,我们用的时候也直接返回就好了
public boolean checkInput(String inputString) {
return true;
}
//是否可以关闭对话框,这里可以写相关的验证逻辑
public boolean canClose(String inputString) {
return !StringUtil.isEmptyOrSpaces(inputString) && this.getErrorText(inputString) == null;
}
});
复制代码这个方法其实就是创建大家比较熟悉的这个页面,我呢作为一个后端,对创建页面这种操作自然不怎么感兴趣,那肯定是能用现成的就用现成的

这里提一下,在getErrorText这个方法返回值中JavaErrorBundle.message的内容指向的内容是"This is not a valid Java qualified name",如果你在创建类名的时候输入了一个不合法的类名,出现的就是这句话。当时在看到了这个的时候,还是让我感受到一些阅读源码的乐趣

创建文件
在CreateClassAction.doCreate()方法中,藏了一个生成java文件的api
JavaDirectoryService.getInstance().createClass(dir, className, templateName, true)
复制代码- dir 当前目录
- className 我们在弹窗中输入的类名
- templateName 模版名称
- 当模版中有未设置的变量名时是否提示
获取模版
获取所有模版的Api:
FileTemplateManager.getInstance(project).getAllTemplates()
复制代码在创建文件时用到的模版,来源其实是配置中的File and Code Templates,研究到这里,模版不够灵活的问题就自动被解决了,用户想生成什么样的文件,完全取决于配置了什么样的模版

当把配置模版的描述信息拉到最底部会发现,其实IDEA用到了Velocity模版语言(如果需要教程的话可以在评论区留言~),所以在IDEA本身的创建Class文件也可以有一些小技巧。比如:如果生成的文件是Controller结尾的,就把@RequestMapper这些都提前生成好,反正很多新建的文件就只有输入的名字不一样,像我这样懒的人,肯定能少写一行代码就少些一行。另外,IDEA本身也提供了一些占位符,像类名,日期这些常用的都可以直接使用,更多模版的配置和使用也可以查看
目前还有一个问题,就是模版和文件生成目录的对应关系,应该把什么文件生成到什么目录下面。最开始的想法是写一个配置页面,但是这样就需要创造一个页面出来,前面也说过了,我其实不怎么喜欢这类创建页面的操作,另外配置天然还会关联持久化的操作,以及每次换项目其实还要重新配置,总而言之就是比较麻烦。后来发现了一个取巧的办法----如果直接用包名命名模版,其实问题就解决了,在获取到模版列表之后,通过模版的名字和生成文件的目标包来匹配就解决了这个问题,并且配置可以跟随IDEA的配置,不会因为切换项目出问题,具体的配置示例可以看文末的配置图。
获取鼠标当前的目录和包信息
在CreateClassAction的曾祖父类CreateFromTemplateAction中,有获取当前选中目录的api
DataContext dataContext = e.getDataContext();
IdeView view = (IdeView)LangDataKeys.IDE_VIEW.getData(dataContext);
if (view != null) {
final PsiDirectory dir = Objects.requireNonNull(view).getOrChooseDirectory();
复制代码在CreateClassAction.getActaionName中用到了通过目录获取包信息的api
PsiPackage psiPackage = JavaDirectoryService.getInstance().getPackage(directory);
复制代码psiPackage.getQualifiedName()可以获取到完整的包名,可以用于我们的包和模版的匹配psiPackage.getDirectories()用于生成文件时把包信息再转换成目录信息psiPackage.geParent()用于获取父包psiPackage.getChildren()用于获取所有子包,注意只要包名相同,跨模块的子包也能获取到
开始动手
找出了上面这些之后,之前提到的跟Api相关的问题就都解决掉了,下面贴出实现的主要逻辑,为了逻辑尽量顺畅,删除了很多判断的代码,源码可以访问 【工众号】 ,类名是CreateClasses
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
//获取到project对象
Project project = anActionEvent.getProject();
//获取到当前action的上下文
DataContext dataContext = anActionEvent.getDataContext();
//用于获取当前目录
IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext);
if (view != null) {
获取到当前目录
final PsiDirectory dir = Objects.requireNonNull(view).getOrChooseDirectory();
//创建弹出框
CreateFileFromTemplateDialog.Builder builder = CreateFileFromTemplateDialog.createDialog(project);
//填充弹出框信息
this.buildDialog(project, dir, builder);
//执行相关创建逻辑
builder.show("Error", "class", new CreateFileFromTemplateDialog.FileCreator<>() {
public PsiElement createFile(@NotNull String name, @NotNull String templateName) {
//获取当前目录的包信息
PsiPackage choosePackage = JavaDirectoryService.getInstance().getPackage(dir);
//获取父包
PsiPackage parentPackage = choosePackage.getParentPackage();
List<PsiClass> result = new ArrayList<>();
//获取所有子包(最深的那种)
List<PsiPackage> allSubPackage = getAllSubPackage(Arrays.stream(Objects.requireNonNull(parentPackage.getParentPackage()).getSubPackages()).collect(Collectors.toList()));
allSubPackage.forEach(psiPackage -> {
//这边是创建文件的本体,单独处理用于最后的页面定位
if (psiPackage.getQualifiedName().equals(choosePackage.getQualifiedName())) {
result.add(JavaDirectoryService.getInstance().createClass(psiPackage.getDirectories()[0], name, templateName, true));
return;
}
//生成的关联文件,从获取的模版列表和包名匹配,匹配到了就生成
Arrays.stream(FileTemplateManager.getInstance(project).getAllTemplates())
.forEach(item -> {
if (psiPackage.getQualifiedName().endsWith(item.getName())) {
for (PsiDirectory directory : psiPackage.getDirectories()) {
JavaDirectoryService.getInstance().createClass(directory, name, item.getName(), true);
}
}
});
});
//返回添加进去的元素,用来进行页面定位
return result.get(0);
}
public boolean startInWriteAction() {
return false;
}
//弹出框的名字
public @NotNull String getActionName(@NotNull String name, @NotNull String templateName) {
return "Create Classes";
}
},
//最后定位的位置
(createdElement) -> {
if (createdElement != null) {
view.selectElement(createdElement);
}
});
}
}
复制代码使用
现在我们就可以香起来了,在配置中配置好,就可以像文章开头那样生成文件了

文章的所有内容到这就结束了,都看到这了,难道不想点个赞么!
边栏推荐
- jetson nano上远程桌面
- Creation and traversal of binary tree
- Cs224w (Figure machine learning) 2021 winter course learning notes 5
- Write a summary, want to use a reliable software to sort out documents, is there any recommendation?
- DevSecOps,让速度和安全兼顾
- 蓝牙BLE4.0-HM-10设备配对指南
- R language uses LM function to build multiple linear regression model, writes regression equation according to model coefficient, and uses fitted function to calculate y value (response value) vector
- [basic] the difference between dynamic link library and static link library
- Pytorch--- advanced chapter (function usage skills / precautions)
- R language ggplot2 visualization: use the ggdotplot function of ggpubr package to visualize dot plot, set the add parameter to add the mean and standard deviation vertical lines, and set the error.plo
猜你喜欢

Glyphicons V3 字体图标查询

反射、枚举以及lambda表达式

QT is the most basic layout, creating a window interface

TI C6000 TMS320C6678 DSP+ Zynq-7045的PS + PL异构多核案例开发手册(3)

C # set different text watermarks for each page of word

What are the skills and methods of searching foreign literature

03 common set security classes under JUC

pytorch安装 CUDA对应

拒绝噪声,耳机小白的入门之旅

FOC motor control foundation
随机推荐
Environment regulation system based on Internet of things (esp32-c3+onenet+ wechat applet)
QT is the most basic layout, creating a window interface
拒绝噪声,耳机小白的入门之旅
广州地铁十三号线二期全线土建已完成53%,预计明年开通
桌面应用布局图
Desktop application layout
MYSQL 命令大全
超简单!只需简单几步即可为TA定制天气小助理!!
什么是传输层协议TCP/UDP???
Soft test (VII) performance test (1) brief introduction
李宏毅《机器学习》丨3. Gradient Descent(梯度下降)
OSPF综合实验
关于工控网关物联网串口转WiFi模块与串口转网口模块的选型
2023 catering industry exhibition, China catering supply chain exhibition and Jiangxi catering Ingredients Exhibition were held in February
R language ggplot2 visualization: use the ggdotplot function of ggpubr package to visualize dot plot, set the add parameter to add the mean and standard deviation vertical lines, and set the error.plo
jetson nano上远程桌面
R语言检验相关性系数的显著性:使用cor.test函数计算相关性系数的值和置信区间及其统计显著性(如果变量来自正态分布总体使用皮尔森方法pearson)
如何将规划图转成带经纬度的矢量数据geojson
Jintuo shares listed on the Shanghai Stock Exchange: the market value of 2.6 billion Zhang Dong family business has a strong color
The IPO of shengtaier technology was terminated: it was planned to raise 560million yuan, and Qiming and Jifeng capital were shareholders