如果敲代码时总是会有一些 规律、重复 和 浪费时间 的工作,那么你就可以好好考虑一下能否通过 脚本或插件 的方式提高工作效率。idea 本身自带众多实用代码插件,getter,setter,代码补全… 且其第三方插件采用 集中发布模式 ,可以让开发者快速安装和发布插件,这点要优于 eclipse。
开发插件之前,得看看idea平台给我提供了哪些接口给我们使用,使用接口前最好简单了解下背后的实现原理。
这篇文章包含:
The virtual file system (VFS) is a component of IntelliJ Platform that encapsulates most of its activity for working with files. It serves the following main purposes:
第一个目的和Linux的VFS差不多,都是为了提供 统一的上层接口,以此 屏蔽 底层各种不同的文件系统,对开发者透明,以及方便其他文件系统快速接入。
具体实现就是通过 快照 的方式,将快照作为中间层,提供文件的“多”读“一”写,并且适配不同的文件系统,给插件开发者 提供统一接口。新增类文件,写入代码等对文件的操作 都是通过 com.intellij.openapi.vfs.VirtualFile,基于二进制流进行读写,可以把它当做原生File类来类比。
VituralFile是作用域是应用级别(即使在 多个工程中打开,各工程展示的实例也由同一个虚拟文件实例代理 ), 对于一份快照,来自任何线程的数据读取都是允许的,但是只有主线程能够写入,官方称为 通用线程规则 ,使用一个单独的 读写锁 实现。需要注意的是当读取文件时候,应调用VirtualFile的isValid方法是否返回true,因为本地文件已经被移除了,但是对应的VirtualFile没有被回收。
几种获取VirtualFile的方式:
程序结构接口 Program Structure Interface, 负责解析文件,创建语法和语义代码模型。
PsiFile是将 文件内容按照特定编程语言的元素层次结构相对应进行代理的根结构。PsiFile是所有Psi文件的基类,一种语言会有特定的实现,例如 Java语言对应PsiJavaFile类,XML对应 XmlFile 类 。
PsiFile作用域是 应用级别 ,如果一个文件所属的不同项目同时打开,这个文件会被不同的PsiFile实例代理。
获取 PsiFile 的方式:
从action中:e.getData(LangDataKeys.PSI_FILE)
从虚拟文件中:PsiManager.getInstance(project).findFile()
从文档中:PsiDocumentManager.getInstance(project).getPsiFile()
从文件中的一个元素:psiElement.getContainingFile()
用FilenameIndex.getFilesByName(project, name, scope)方法在项目中找到一个指定名称的文件
PSI(程序结构接口)文件表示PSI元素的层次结构(所谓的PSI树), PsiElement 是所有PSI元素的基类,Psi元素可以是PsiClass Psi类,PsiMethod Psi方法,PsiImportList Psi导入列表,PsiParameter Psi参数,总之只要是Java类文件里有的结构,都用对应的Psi元素。元素之间可以相互嵌套,成树状结构。
获取 PsiElements
介绍完相关背景和知识后,我们实战开发2个plugin,现在开始先来实现第一个最简单的弹窗提示的Plugin,并将其 构建 打包 安装 到自己的idea中。
2.新建 Plugin工程 , 顶部菜单栏 File > New > Project… ,然后出现如下界面,选择 IntelliJ Platform Plugin,SDK检查一下有没有,没有的话,下载到电脑,手动选择目录。然后 点击 Next> 为工程命名 > Next
package com.chenyi; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.ui.Messages; /** * @author: chenyi.zsq * @Date: 2018/11/5 */ public class HelloAction extends AnAction{ @Override public void actionPerformed(AnActionEvent anActionEvent) { Messages.showMessageDialog("Hello Plugin", "PluginTitle", null); // 以下代码用于输出当前java文件里的所有方法信息,在调试模式可以看到。 VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); PsiFile psiFile = PsiManager.getInstance(e.getProject()).findFile(virtualFile); Arrays.stream(psiFile.getChildren()) .filter(child -> child instanceof PsiClass) .map(child -> child.getChildren()) .flatMap(psiChild -> Arrays.stream(psiChild)) .filter(classChild -> classChild instanceof PsiMethod) .forEach(psiMethod -> { PsiMethod method = (PsiMethod)psiMethod; StringBuilder sb = new StringBuilder(); sb.append("方法的名: ").append(method.getName()) .append(" 返回值类型:").append(method.getReturnType().getPresentableText()) .append(" 参数:").append(method.getParameters()); System.out.println(sb.toString()); }); } }
编写 resources/META-INF/plugin.xml 文件。在
<idea-plugin> <id>com.your.company.unique.plugin.id</id> <name>Plugin display name here</name> <version>1.0</version> <vendor email="support@yourcompany.com" url="http://www.yourcompany.com">YourCompany</vendor> <description><![CDATA[ Enter short description for your plugin here.<br> <em>most HTML tags may be used</em> ]]></description> <change-notes><![CDATA[ Add change notes here.<br> <em>most HTML tags may be used</em> ]]> </change-notes> <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description --> <idea-version since-build="173.0"/> <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html on how to target different products --> <!-- uncomment to enable plugin in all products <depends>com.intellij.modules.lang</depends> --> <extensions defaultExtensionNs="com.intellij"> <!-- Add your extensions here --> </extensions> <actions> <action class="com.chenyi.HelloAction" id="HelloAction" text="第一个插件"> <add-to-group group-id="WindowMenu" anchor="first"/><!--添加到菜单栏的Window标签下,位置固定在第一位 --> </action> </actions> </idea-plugin>
6. 编写完之后,可以点击运行或者调试,Idea将会另起一个进程自动安装插件,可以在此调试,但是debug模式中发现,对actionPerformed 方法没响应,对update事件有响应,最后调试时将代码写到update里运行,打正式包时把代码放回actionPerformed。如果想要正式包,需要点击构建打包,会在当前工程根目录下 生成一个 .jar文件。
需求:进入公司后发现,在HSF(内部一个 分布式服务框架 ) 服务间会频繁的进行跨应用服务调用,由于规范性,每个服务在调用前 都要封装一层SPI代码。用于AOP拦截入参和结果,以及对异常的捕捉。而代码往往就是 Service类的一层封装,代码类似如下:
xxxSPI 和 xxxService方法名,参数都一样,返回值只要在xxxService返回值基础上getModule拆一下,开发人员同时需要创建 xxxServiceSPI和xxxServiceImplSPI,然后挨个把方法调用一遍,设置拦截器,我觉得完全可以将这部分时间省掉。
目前已完成大部分编码,GitHub地址: https://github.com/zhengshiqiang47/ChenyiSPIPlugin