原文地址:IDEA 插件开发实战

文章目录

  • 一. 简介
  • 二.原理
    • 2.1 背景
    • 2.2 基本原理
    • 2.3 小结
  • 三.api
    • 3.1 框架结构
    • 3.2 常用API介绍
  • 四.实例架构
  • 五.准备工作
  • 六.编码
  • 七.部署
  • 八.总结

一. 简介

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() {@Overridepublic 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;}    @Overridepublic 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 {@Overridepublic void initComponent() {        // TODO: insert component initialization logic here    }    @Overridepublic void disposeComponent() {        // TODO: insert component disposal logic here    }    @Override@NotNullpublic 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 {@Overridepublic 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/77954309/code_plugin.git

有些依赖得自行下载,

七.部署

在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层业务处理,这个代码中写上包名称,DO得在特定包名下,DTO才能映射。

public class CreateDTOAction extends BaseAnAction  {@Overridepublic 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(){            @Overridepublic 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/77954309/code_plugin

公众号


名称:大数据计算
微信号:bigdata_limeng

IDEA 插件开发实战相关推荐

  1. 【Maven】maven 插件开发实战

    [Maven]maven 插件开发实战 附上我历时三个月总结的 Java 面试 + Java 后端技术学习指南,这是本人这几年及春招的总结,目前,已经拿到了大厂offer,拿去不谢! 下载方式 1.  ...

  2. CloudIDE插件开发实战:教你如何调试代码

    摘要:今天我们来重点介绍下CloudIDE插件的调试技巧,在插件开发过程中调试作为重要的问题分析和定位手段能够有效帮助开发者提升插件质量. 今天文章中的样例工程我们继续以上一篇<实战CloudI ...

  3. [js高手之路]jquery插件开发实战-选项卡详解

    在jquery中,插件开发常见的有: 一种是为$函数本身扩展一个方法,这种是静态扩展(也叫类扩展),这种插件一般是工具方法, 还有一种是扩展在原型对象$.fn上面的,开发出来的插件是用在dom元素上面 ...

  4. UNI APP---Android端原生插件开发实战(二)

    1.前言 最近一个项目要求我们的产品必须走网络隧道,并且提供了对应的SDK,很明显只能通过原生开发的方式才能实现这个流程,之前已经写过一篇通过代理的方式进行数据请求,而这次Android端的方式是采用 ...

  5. Chrome插件开发实战

    注意:google说2018年第一季度全面停用的 应用和 扩展不是一个东西,应用指的是 chrome apps 扩展指的是 chrome extension. 在几个月之前,我的一个朋友老张,跟我说了 ...

  6. 视频教程-3D建模理论与实战 - 使用Ruby进行SketchUp(草图大师)插件开发-其他

    3D建模理论与实战 - 使用Ruby进行SketchUp(草图大师)插件开发 澳大利亚工程师协会会员航空(航天)工程师.学习编程超过20年.6年3D建模软件研发经历.4年算法交易/数学建模经历. 张元 ...

  7. Maven插件开发教程

    Maven是一个一个优秀的项目管理开源框架,其插件机制为其功能扩展提供了非常大的便捷性.大多数情况下,我们不需要自己开发Maven插件,因为Maven本身提供了很多便捷的官方插件.但是对于某些特殊场景 ...

  8. golang微服务框架对比_微服务里程碑,Golang与Spring Cloud Alibaba完美结合

    目前微服务架构仍是软件架构中最新的热门话题,虽然Golang是一门新的语言,但Golang的性能比python和java高出不少.既能承受程序使用运行的服务构建的繁重负载,又容易与GitHub集成,管 ...

  9. Knowladge_网站学习_jQuery插件

    几个参考的jquery插件网站 1. 240 多个jQuery 插件 http://www.cnblogs.com/Terrylee/archive/2007/12/09/the-ultimate-j ...

  10. 2020软件开发趋势预测(学习总结)

    数字化,工业4.0,对软件开发人员的巨大需求. 云计算/云服务.容器技术.软件架构.编程语言.Web开发.APP.深度学习.数据库.数据湖.批处理.流处理.字节码(ByteCode).无代码(No-c ...

最新文章

  1. 蚂蚁金服付志嵩:数据膨胀?关系复杂?如何升级图数据库?
  2. outlook express 邮件超过2G时的解决方法
  3. Android是怎么启动的-[Android取经之路]
  4. 单片机上电复位电路图大全
  5. 2020-11-10(安卓如何传递数据)
  6. 【verilog 语法】always 和 always@(*) 的区别
  7. 几种字符串加密解密的方法
  8. Div1 小A抓小B tarjan双连通分量缩点+dfs
  9. sklearn——决策树
  10. 教你一招用python发送QQ邮件
  11. java多客户端请求响应_Java实现服务器端对多个客户端请求响应
  12. 2019上半年白领跳槽盘点:仅3成成功跳槽,17%降薪
  13. 如何保证MQ消息队列的高可用
  14. 阿里云短信接口对接(java版)
  15. 3D游戏引擎的Web化
  16. 梦想还是要有的,万一实现了呢?
  17. tongweb java_home_中标麒麟操作系统下部署 Web项目(Tongweb + DM)
  18. 【matlab 基础篇 02】基础知识一键扫盲,看完即可无障碍编程(超详细+图文并茂)
  19. flowable相关的一些名词说明
  20. 帖子:刚刚结束美国J1签证所需的英语熟练度面试

热门文章

  1. IDEA主题SublimeTest3修改
  2. 服务器行业深度解析:服务器未来需求知多少
  3. 生信技能树 电脑配置linux,生信技能树----Linux练习题答案
  4. 854计算机专业基础,2020年哈工大考研《854计算机基础》考试大纲
  5. spring 定时器功能
  6. 微信小程序锦鲤砍价 搭建教程完整版
  7. Nginx 集群搭建
  8. 宝塔php爬虫无头浏览器,爬虫利器selenium和无头浏览器的使用
  9. 项目部署到tomcat出现js中文乱码解决方案
  10. windows命令查找程序_如何在Windows 10上查找和设置屏幕保护程序