IntelliJ IDEA 是一款开发工具,提供很多插件功能,比如阿里规范插件(Alibaba Java Coding Guidelines),但是随着日常业务展开,很多工作重复性编码,浪费很多时间,需要自定义抽象出来一些插件,自动化的方式解决问题,这也是工程师文化的体现。
IntelliJ 平台是开源的,基于 Apache 许可协议,提供很多丰富的工具,提供组件驱动,基于跨平台 JVM,可以在创建菜单栏、列表、弹出菜单、对话框等等。可以适用于多种语言,提供相关解析器和 PSI 模型,解析文件,构建语义模型。
组件模型
负责生命周期管理以及连接组件之间的相互依赖关系。
生命周期:
线程模型
平台相关数据结构由读/写锁覆盖,适用于 PSI,VFS 和项目模型。允许从任何线程读取数据。从 UI 线程读取数据不需要任何特殊的工作。但是,从任何其他线程执行的读取操作都需要使用 ApplicationManager.getApplication().runReadAction()或 ReadAction.run/compute。
仅允许从 UI 线程写入数据,并且写入操作始终需要用 ApplicationManager.getApplication().runWriteAction()或 WriteAction.run()/compute()。
后台流程管理
后台进度由 ProgressManager 类管理,该类有很多方法可以使用模式(对话框),非模式(在状态栏中可见)或不可见进度来执行给定代码。在所有情况下,代码都是在与 ProgressIndicator 对象关联的后台线程上执行的。
讯息传递
平台中可用的消息传递基础结构,基于 Observer 设计模式扩展实现的,通过该模式能够更好的梳理的一对多关系,实现提供了附加功能,例如在层次结构上进行广播和特殊的嵌套事件处理(此处的嵌套事件是指从另一个事件的回调中(直接或间接)触发新事件的情况)。
具体相关原理研究,可查看官网(sdk 地址)。
.IntelliJIDEA/
└── plugins
└── code_plugin
└── lib
├── lib_foo.jar
├── lib_bar.jar
│ ...
│ ...
└── sample.jar
├── com/foo/...
│ ...
│ ...
└── META-INF
├── plugin.xml
├── pluginIcon.svg
└── pluginIcon_dark.svg
└──src
├──com.code
基本的框架结构,如果要导入依赖放到 lib 文件夹中,还有另一种建立框架的方式,那个是基于 Gradle 管理。
META-INF,配置文件件,管理注册的类。
VFS
从本地 IO 文件中获取
File ioFile = new File("./io.java")
VritualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(ioFile)
virtualFile.refresh(false, true)
对 VirtualFile 进行读写操作:和 Android 一样,Intellij Platform 不允许直接在主线程进行实时的文件写入,需要通过一个异步任务进行。
WriteCommandAction.runWriteCommandAction(project, new Runnable() {
@Override
public void run() {
// virtualFile.getInputStream() / virtualFile.getOutputStream()
}
});
在异步任务结束后,切回 UI 线程进行 UI 更新:
ApplicationManager.getApplication().invokeLater(new Runnable(){
...
})
PSI
PSI(Program Structure Interface)是 Intellij Platform 中一个非常重要的概念,在 IDE 所管理的 Project 中,每个目录,Package,源代码和资源文件都会被抽象成相应的 PSI 对象。
常用子类:PsiDirectory、PsiJavaFile 和 XmlFile。
创建目录和文件:
//创建目录
PsiDirectory baseDir =PsiDirectoryFactory.getInstance(project).createDirectory(project.getBaseDir());
//创建 Java 文件
PsiJavaFile psiFile = (PsiJavaFile) PsiFileFactory.getInstance(project).createFileFromText("",StdFileTypes.JAVA, "");
//创建 Xml 文件
XmlFile psiFile = (XmlFile) PsiFileFactory.getInstance(project).createFileFromText("",StdFileTypes.XML, "");
读写文件:和写入 VirtualFile 一样,读写操作都需要在 WriteCommandAction 异步线程中进行。
创建 Class 文件类:
PsiClass clazz =JavaDirectoryService.getInstance().createClass(subDir, className)
//还有通过 freemarker 模板建立 class 类。
psiClass 类中添加接口:
PsiClass view = myFactory.createInterface("View");
psiClass.add(view);
设置包名:
PsiJavaFile javaFile = (PsiJavaFile) psiClass.getContainingFile();
PsiPackage psiPackage = myDirectoryService.getPackage(directory);
javaFile.setPackageName(psiPackage.getQualifiedName());
设置类权限:
psiClass.getModifierList().setModifierProperty(PsiModifier.PUBLIC,true);
平时开发过程中,代码结构会分层,类似 MVC 思想,这里面有很多可以抽象出来的公共类,比如 JavaBean,DTO,Service 等等,我这个实例结合类似场景,实现自动化插件。
创建插件项目:
还可以用 Gradle 方式创建项目,我用的 idea 版本 2019.2.4,上述内容中提到框架结构,现在可以在 src 目录中编码。
总共有几个部分组成。
BaseAnAction
AnActionEvent 一些基本信息。
public abstract class BaseAnAction extends AnAction {
private AnActionEvent anActionEvent;
private DataContext dataContext;
private Presentation presentation;
private Module module;
private IdeView view;
private ModuleType moduleType;
private Project project;
private PsiDirectory psiDirectory;
private DialogBuilder builder;
private PsiFile file;
private JavaDirectoryService javaDirectoryService;
private MysqlJdbc mysqlJdbc = MysqlJdbc.getMysqlJdbc();
private PropertiesUtil properties = PropertiesUtil.getConfigProperties();
public void init(AnActionEvent anActionEvent) {
this.javaDirectoryService = new JavaDirectoryServiceImpl();
this.anActionEvent = anActionEvent;
IdeView ideView = (IdeView)anActionEvent.getRequiredData(LangDataKeys.IDE_VIEW);
this.psiDirectory = ideView.getOrChooseDirectory();
this.project = this.psiDirectory.getProject();
}
public PropertiesUtil getProperties() {
return this.properties;
}
public MysqlJdbc getMysqlJdbc() {
return this.mysqlJdbc;
}
public PsiDirectory getPsiDirectory() {
return this.psiDirectory;
}
public JavaDirectoryService getJavaDirectoryService() {
return this.javaDirectoryService;
}
public AnActionEvent getAnActionEvent() {
return this.anActionEvent;
}
public void setAnActionEvent(AnActionEvent anActionEvent) {
this.anActionEvent = anActionEvent;
}
public DataContext getDataContext() {
return this.dataContext;
}
public void setDataContext(DataContext dataContext) {
this.dataContext = dataContext;
}
public Module getModule() {
return this.module;
}
public void setModule(Module module) {
this.module = module;
}
public IdeView getView() {
return this.view;
}
public void setView(IdeView view) {
this.view = view;
}
public ModuleType getModuleType() {
return this.moduleType;
}
public void setModuleType(ModuleType moduleType) {
this.moduleType = moduleType;
}
public Project getProject() {
return this.project;
}
public void setProject(Project project) {
this.project = project;
}
public DialogBuilder getBuilder() {
return this.builder;
}
public void setBuilder(DialogBuilder builder) {
this.builder = builder;
}
public PsiFile getFile() {
return this.file;
}
public void setFile(PsiFile file) {
this.file = file;
}
public Presentation getPresentation() {
return this.presentation;
}
public void setPresentation(Presentation presentation) {
this.presentation = presentation;
}
@Override
public void update(AnActionEvent e) {
try {
this.presentation = e.getPresentation();
this.onMenuUpade(e, (PsiFile)e.getData(DataKeys.PSI_FILE), ((IdeView)LangDataKeys.IDE_VIEW.getData(e.getDataContext())).getOrChooseDirectory());
}
catch (Exception exception) {
// empty catch block
}
}
public void show() {
this.presentation.setEnabled(true);
this.presentation.setVisible(true);
}
public void hide() {
this.presentation.setEnabled(false);
this.presentation.setVisible(false);
}
public void onMenuUpade(AnActionEvent e, PsiFile file, PsiDirectory dir) {
}
}
CodeComponent
应用管理。
public class CodeComponent implements ApplicationComponent {
@Override
public void initComponent() {
// TODO: insert component initialization logic here
}
@Override
public void disposeComponent() {
// TODO: insert component disposal logic here
}
@Override
@NotNull
public String getComponentName() {
if ("CreateMicroServiceProjectComponent" == null) {
CodeComponent.reportNull(0);
}
return "CreateMicroServiceProjectComponent";
}
private static void reportNull(int n) {
throw new IllegalStateException(String.format("@NotNull method %s.%s must not return null", "com/code/action/CodeComponent", "getComponentName"));}}
还有一些工具类,比如操作 MySQL 数据库,操作字符串等等。一些 freemarker 模板,Action 动作。
MMS_DO.java.ft
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
import lombok.Data;
import java.util.*;
import java.math.BigDecimal;
/**
* @author ${USER} E-mail:${E_MAIL}
* @version 创建时间:${DATE} ${TIME}
* ${doCalssName}DO 对象
*/
@Data
public class ${doCalssName}DO {
}
CreateServiceAction
创建 Service 接口和实现类。
public class CreateServiceAction extends BaseAnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
this.init(anActionEvent);
String serviceName = Messages.showInputDialog((String)"Service name", (String)"Create service", (Icon)Messages.getInformationIcon());
Map<String, String> param = new HashMap<String, String>();
param.put("doCalssName", BaseUtils.firstLetterUpperCase(BaseUtils.markToHump(serviceName, "_", null)));
param.put("tableName", serviceName);
PsiDirectory implDir = this.getPsiDirectory().findSubdirectory("impl");
if (implDir == null) {
implDir = this.getPsiDirectory().createSubdirectory("impl");
}
PsiClass servicePsiClass = this.getJavaDirectoryService().createClass(this.getPsiDirectory(), "", "MMS_Service", false, param);
String packagePath = servicePsiClass.getQualifiedName();
String implT = "impl";
param.put(implT, packagePath + ";");
param.put("serviceName", serviceName);
this.getJavaDirectoryService().createClass(implDir, "", "MMS_ServiceImpl", false, param);
}
}
项目内容放到 GitHub 中,地址:code_plugin
项目中缺失 jar,有些依赖得自行下载,
在 code_plugin 项目鼠标右击,或者 build 点击 Prepare Plugin Module '插件名称(codeplugin)' For Deployment 生成插件包(zip/jar)。
在 IDEA 文件夹,File->Settings->Plugins->Install Plugin from Disk,安装打出插件,查看目录,重启。
导入插件
在这里插入图片描述
效果展示
插件位置
项目,鼠标右击,新建 New,有 CreateDO、CreateDTO、CreateService 三个功能窗口。
创建 DO
这个实体是跟 MySQL 业务表像映射的,窗口填的是数据库表名称。
创建 DTO
DTO 是跟 DO 相映射的,符合阿里的编程规范,用于处理 Service 层业务处理,这个代码中写上包名称(com.lm.model),DO 得在特定包名下,DTO 才能映射,DTO 创建窗口,填 DO 类名称。
public class CreateDTOAction extends BaseAnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
this.init(anActionEvent);
String doName = Messages.showInputDialog((String)"DO name", (String)"Create DTO", (Icon)Messages.getInformationIcon());
final PsiElementFactory factory = JavaPsiFacade.getInstance((Project)this.getProject()).getElementFactory();
HashMap<String, String> param = new HashMap<String, String>();
String doClassName = BaseUtils.firstLetterUpperCase(BaseUtils.markToHump(doName.substring(0, doName.length() - 2), "_", null));
param.put("doCalssName", doClassName);
GlobalSearchScope searchScope = GlobalSearchScope.allScope((Project)this.getProject());
PsiPackage psiPackage = JavaPsiFacade.getInstance((Project)this.getProject()).findPackage("com.lm.model");
PsiClass[] doPsiClasss = psiPackage.findClassByShortName(doName, searchScope);
PsiClass doPsiClass = doPsiClasss[0];
PsiClass dtoPsiClass = JavaPsiFacade.getInstance((Project)this.getProject()).findClass(doPsiClass.getQualifiedName(), searchScope);
final PsiField[] psiFields = dtoPsiClass.getFields();
final PsiClass psiClass = this.getJavaDirectoryService().createClass(this.getPsiDirectory(), "", "MMS_DTO", false, param);
WriteCommandAction.runWriteCommandAction((Project)this.getProject(), (Runnable)new Runnable(){
@Override
public void run() {
for (PsiField psiField : psiFields) {
String comment = psiField.getDocComment().getText().replaceAll("\\*", "").replaceAll("/", "").replaceAll(" ", "").replaceAll("\n", "");
StringBuffer fieldStrBuf = new StringBuffer(psiField.getDocComment().getText()).append("\nprivate ").append(psiField.getType().getPresentableText()).append(" ").append(psiField.getName()).append(";");
psiClass.add((PsiElement)factory.createFieldFromText(fieldStrBuf.toString(), (PsiElement)psiClass));
}
}
});
}}
总体的 IDEA 插件开发介绍完毕,这个可以基于模板快速拓展,有兴趣的朋友可以尝试下,毕竟授人以鱼不如授人以渔,自动化是工程师文化的一个重要体现。