IDEA 插件开发实战

本文涉及的产品
实时计算 Flink 版,5000CU*H 3个月
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
简介: IDEA 插件开发实战

一. 简介

IntelliJ IDEA是一款开发工具,提供很多插件功能,比如阿里规范插件(Alibaba Java Coding Guidelines),但是随着日常业务展开,很多工作重复性编码,浪费很多时间,需要自定义抽象出来一些插件,自动化的方式解决问题,这也是工程师文化的体现。

二.原理

2.1 背景

IntelliJ平台是开源的,基于Apache许可协议,提供很多丰富的工具,提供组件驱动,基于跨平台JVM,可以在创建菜单栏、列表、弹出菜单、对话框等等。可以适用于多种语言,提供相关解析器和PSI模型,解析文件,构建语义模型。

2.2 基本原理

组件模型

负责生命周期管理以及连接组件之间的相互依赖关系。

  • Application level components,在IDEA启动的时候创建和初始化,可以使用 getComponent(Class) 获取它们。
  • Project level components,在IDEA中每个Project实例创建的,甚至可以为未打开的项目创建组件,可以使用 getComponent(Class)方法从Project实例中获取它们。
  • Module level components,它们是为IDEA中加载的每个项目中每个模块创建,使用getComponent(Class)方法可以从Module实例获取模块级别组件。

生命周期:

  • 创建,调用构造函数
  • 初始化,initComponent调用该方法(如果组件实现ApplicationComponent接口)
  • 配置,保存和加载每个组件的状态。(PersistentStateComponent和JDOMExternalizable,实例化配置)。
  • 注册,对于模块组件,将调用接口的moduleAdded方法ModuleComponent将模块添加到项目中,对于项目组件,调用接口的projectOpened方法ProjectComponent加载项目。
  • 保存配置,JDOMExternalizable,PersistentStateComponent的调用。
  • 输出,disposeComponent调用输出。

    线程模型

平台相关数据结构由读/写锁覆盖,适用于PSI,VFS 和项目模型。允许从任何线程读取数据。从UI线程读取数据不需要任何特殊的工作。但是,从任何其他线程执行的读取操作都需要使用ApplicationManager.getApplication().runReadAction()或ReadAction.run/compute。

仅允许从UI线程写入数据,并且写入操作始终需要用ApplicationManager.getApplication().runWriteAction()或WriteAction.run()/compute()。

后台流程管理

后台进度由ProgressManager类管理,该类有很多方法可以使用模式(对话框),非模式(在状态栏中可见)或不可见进度来执行给定代码。在所有情况下,代码都是在与ProgressIndicator对象关联的后台线程上执行的。

讯息传递

平台中可用的消息传递基础结构,基于 Observer设计模式扩展实现的,通过该模式能够更好的梳理的一对多关系,实现提供了附加功能,例如在层次结构上进行广播和特殊的嵌套事件处理(此处的嵌套事件是指从另一个事件的回调中(直接或间接)触发新事件的情况)。

2.3 小结

具体相关原理研究,可查看官网(http://www.jetbrains.org/intellij/sdk/docs/welcome.html)。

三.api

3.1 框架结构

.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,配置文件件,管理注册的类。

3.2 常用API介绍

VFS

  • 提供一个处理文件的通用API,而不关心文件的具体位置(无论文件位于磁盘上、归档文件中还是HTTP服务器上)。
  • 追踪文件变化,并且在检测到文件内容发生更改时能提供新旧两个版本的文件。
  • 建立文件在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等等,我这个实例结合类似场景,实现自动化插件。

image.png

五.准备工作

创建插件项目:

image.png

还可以用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项目

项目内容放到GitHub中,地址:https://github.com/dlimeng/code_plugin.git

有些依赖得自行下载,

image.png

七.部署

在code_plugin项目鼠标右击,或者build 点击Prepare Plugin Module '插件名称(codeplugin)' For Deployment 生成插件包(zip/jar)。

在IDEA 文件夹,File->Settings->Plugins->Install Plugin from Disk,安装打出插件,查看目录,重启。

导入插件

image.png

效果展示

插件位置

项目,鼠标右击,新建New,有CreateDO、CreateDTO、CreateService三个功能窗口。

image.png

创建DO

这个实体是跟MySQL业务表像映射的,窗口填的是数据库表名称。

image.png
image.png
image.png

创建DTO

DTO是跟DO相映射的,符合阿里的编程规范,用于处理Service层业务处理,这个代码中写上包名称,DO得在特定包名下,DTO才能映射。

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插件开发介绍完毕,这个可以基于模板快速拓展,有兴趣的朋友可以尝试下,毕竟授人以鱼不如授人以渔,自动化是工程师文化的一个重要体现。

还可以把插件发布到仓库,支持Plugin中搜索安装,参考:

http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/publishing_plugin.html

就是注册账号,提交jar,填写信息,等着审核就可以了。

文中项目地址:https://github.com/dlimeng/code_plugin

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
4月前
|
网络协议 Windows
两步带你解决IDEA 插件下载安装慢、超时、不成功问题
这篇文章提供了解决IDEA插件下载慢或超时问题的方案,通过查找国内插件节点IP地址并修改本地hosts文件来加速下载。
两步带你解决IDEA 插件下载安装慢、超时、不成功问题
|
2月前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
127 6
|
4月前
|
Java
可直接编辑jar包的IDEA插件-JarEditor
IDEA自带的反编译插件虽可查看jar包中的class文件,但无法直接编辑。为解决此问题,作者开发了JarEditor插件,可在IDEA中直接编辑jar文件内的class及资源文件,无需解压或手动编译。点击Jar Editor可修改代码,通过Save/Compile保存并编译,Build Jar则将更改写回jar包。该插件简化了jar包编辑流程,提高了开发效率。
258 4
可直接编辑jar包的IDEA插件-JarEditor
|
9天前
|
IDE 开发工具
【开发IDE升级】如何对IDEA版本进行升级
本文介绍了如何将 IntelliJ IDEA Ultimate 从 2020.2.2 版本升级到 2022.3.2 版本。主要内容包括准备工作、卸载旧版本和安装新版本的步骤。首先,从官网下载所需版本并备份旧版配置;接着,通过 Uninstall.exe 卸载旧版,保留配置和插件;最后,安装新版并完成激活。详细的操作步骤和截图帮助用户顺利完成升级过程。
【开发IDE升级】如何对IDEA版本进行升级
|
14天前
|
前端开发 Java 开发者
这款免费 IDEA 插件让你开发 Spring 程序更简单
Feign-Helper 是一款支持 Spring 框架的 IDEA 免费插件,提供 URL 快速搜索、Spring Web Controller 路径一键复制及 Feign 与 Controller 接口互相导航等功能,极大提升了开发效率。
|
3月前
|
Windows
IDEA如何查看已经安装的插件并删除
【10月更文挑战第1天】这段内容主要介绍了如何在IntelliJ IDEA中查看和删除已安装的插件。可以通过软件内的插件市场查看插件列表,包括插件名称、版本号和供应商等信息;也可以通过访问插件目录查看。删除插件则建议在插件市场中进行,包括禁用和卸载步骤,手动删除插件文件夹的方法不推荐,因为可能存在配置残留等问题。
916 11
|
3月前
|
人工智能 Java 数据库连接
IDEA开发 常用代码规范插件 常用辅助类插件
IDEA开发 常用代码规范插件 常用辅助类插件
87 0
|
5月前
|
自然语言处理 JavaScript 算法
【插件】IDEA这款插件,爱到无法自拔
本文介绍了阿里云「通义灵码」这一强大IDEA插件,它不仅能够智能生成代码、解答研发问题,还支持多种编程语言和编辑器。文章详细展示了如何安装使用该插件,并通过多个实际案例说明其在代码解释、优化、生成注释及单元测试等方面的应用,助力开发者提高效率。强烈推荐尝试!
163 1
【插件】IDEA这款插件,爱到无法自拔
|
3月前
|
IDE Java Maven
分享几个实用的IDEA插件,提高你的工作效率!
分享几个实用的IDEA插件,提高你的工作效率!
162 0
|
5月前
|
Java
2022年最新最详细的IntelliJ idea高效插件的介绍安装,让你的工作效率提升10倍
这篇文章详细介绍了10款IntelliJ IDEA的高效插件,包括Codota代码智能提示、Key Promoter X快捷键提示、CodeGlance代码缩略图、Lombok代码简化、阿里巴巴代码规范检查、SonarLint代码质量检查、Save Actions格式化代码、Translation翻译、Rainbow Brackets彩虹括号和Nyan Progress Bar彩虹进度条插件,旨在帮助提升开发效率和代码质量。
2022年最新最详细的IntelliJ idea高效插件的介绍安装,让你的工作效率提升10倍