前言
为提高线上程式码的质量,我们在专案持续整合的过程中,会对提交编译打包的程式码进行静态程式码扫描。我们预置了一些开发语言的语法规则(bug、漏洞、程式码风格等),在静态程式码扫描时,会对程式码进行扫描,并将不符合规则的的程式码标记出来。如果扫出的程式码问题很严重,则会阻断上线,开发人员必须对问题程式码进行修改。为方便对问题程式码的修改,我们使用了静扫外挂。该外挂会在开发人员的编码过程中,对编写的程式码进行扫描和分析,并将程式码中与指定规则不符合的程式码标记出来,提示开发人员修改。这样可以让开发人员快速发现问题,并及时修改。但此时会面临一个问题,不同的业务线,对于程式码的要求可能是不一样的,如何让这些规则匹配到所有的业务线。于是使用了规则集,把不同业务线的规则放到不同的规则集中,让业务线使用各自的规则集。为此我们开发了58EE外挂,通过这个外挂来修改规则集。
58EE外挂有Eclipse和IDEA两个版本,并同时支援Mac OS和Windows。
下面我们就以IDEA为例,带大家了解下如何开发IDE外挂。
搭建开发环境
1、配置JDK点选File -> Project Structure,开启ProjectStructure面板。
在Project Structure面板中点选SDKs,之后点选加号,选择JDK。
选中JDK所在的路径,点选OK。
2、配置外挂开发SDK
在Project Structure面板中点选SDKs,之后点选加号,选择IntelliJ Platform Plugin SDK。
选中IDEA的安装目录,点选OK。
3、启用Plugin DevKit外挂
在Settings面板的Plugins中找到Plugin DevKit,确保此外挂处于开启状态。一般IDEA中会自带此外挂。
建立外挂工程
外挂工程有两种形式,一种是DevKit,一种是Gradle。DevKit结构简单,上手快,Gradle依赖管理方便。官方推荐使用Gradle。对于外挂开发选用哪种工程,一般根据外挂的功能需求来确定。功能简单,依赖较少就用DevKit;功能复杂,依赖较多就用Gradle。建立DevKit工程
在建立工程时选择IntelliJ Platform Plugin。
点选Next,填写工程名,点选Finish。
建立Gradle工程
在建立工程时选择Gradle,勾选Java和IntelliJPlatform Plugin。
点选Next,填写专案相关资讯,建立专案。(注意Gradle工程需要在IDEA中配置Gradle)
工程配置档案
plugin.xml在resources/META-INF目录下有个plugin.xml档案,这是外挂工程的配置档案。
以上内容是plugin.xml中的基本配置内容,随着外挂功能的增加,配置档案的内容也会越来越多。其它的配置项会在之后的功能开发模组介绍。
build.gradle
对于Gradle工程,会有一个build.gradle的配置档案,其作用就像Maven工程的pom.xml档案一样,用来宣告该工程的配置资讯。
相比DevKit,Gradle有一个好处就是对于依赖包的管理更方便。如果想在DevKit中引入jar包,需要在专案配置面板手动汇入,而在Gradle工程则直接在build.gradle中宣告此jar包的依赖座标,IDEA会自动去配置的Gradle仓库中寻找此jar包。这一点和Maven一样。
需要注意的是,build.gradle和plugin.xml中都有一个version的版本宣告,这两个分别代表不同的含义。build.gradle中的版本是工程的版本,当工程被打包之后,zip包的名称上就带有此版本号。而plugin.xml上的版本是外挂的版本号,也是被IDEA所识别的版本号,自定义外挂仓库中配置的也是此版本号。为便于版本管理,建议将这两个档案中的版本号写出一致的。
开发Demo
现在,我们开发一个外挂小功能,在IDEA中新增一个按钮,点选该按钮弹出一个Hello World的提示窗。建立Action
在plugin.xml中新增Action配置。
Actions标签中的每一个action都是一个动作事件。class用于指定该Action的实现类,id必须唯一,text用于指定该按钮显示的文字。add-to-group用于指定该按钮的位置,group-id指定按钮在哪个选项组,anchor指定此按钮在该选项组中的位置。以上程式码声明了将在IDEA选单栏中File的最后,新增一个名称为HelloWorld的按钮。
编写该Action的实现类。继承AnAction类,重写actionPerformed()方法。
执行除错
开启执行配置面板,点选加号,选择Plugin。
填写相关引数。VM Options为JVM引数,Use classpath of module为执行的外挂专案,选择此次编写的外挂,点选OK。
点选执行或除错按钮,则会启动一个新的IDEA,在此IDEA中会应用此外挂工程。
开发者可以在新开启的这个IDEA中检视外挂的使用效果。点选File,在最后一项出现HelloWorld。
点选HelloWorld,弹出提示框。
外挂打包
DevKit打包在选单栏点选Build -> Prepare Plugin Module ‘xxx’ For Deployment,之后会在专案所在目录生成外挂的zip压缩包。
Gradle打包
在Gradle视窗中点选双击clean,clean结束后双击build。
Build完成后,会在工程目录中生成build资料夹。
在build档案下的distributions目录中可以看到此外挂打包后的档案。
安装外挂
外挂安装方式IDEA上的外挂有两种安装方式:本地安装和线上安装。如果有外挂的档案包,可以选择本地安装;如果你有外挂的安装地址,就可以选择线上安装。
本地安装
本地安装有两种方式。一种是直接将外挂zip档案解压后的资料夹放到IDEA安装目录的plugins目录中。
另一种方式是在IDEA中安装。在Setting面板的Plugins中点选Install plugin from disk。
选择本地的外挂档案,点选OK。
线上安装
如果要安装官方外挂仓库中的外挂,则再外挂面板中点选Browse repositories。
在弹出的视窗中点选Repository单选框,选择外挂所在的仓库。也可以在搜寻框中输入外挂的名称进行搜寻。
找到外挂后选中该外挂,点选右方的Install进行安装。
注:安装完外挂后,须重启IDEA才能使外挂生效。
建立外挂仓库
如果开发的是一个开源外挂,想要全世界的人都可以使用这个外挂,那么可以把这个外挂释出到JetBrains的外挂仓库。如果只想让这个外挂在一定范围内使用,比如在公司内部使用,那么可以建立一个外挂仓库。使用者通过访问这个外挂仓库来安装外挂。
建立一个任意名称的xml档案,在档案中写入以下内容。
其中,plugins标签中的每一项plugin代表一个外挂。
id为外挂工程配置档案plugin.xml中定义的id;
url为该外挂的下载地址;
version为外挂的版本号,需要和plugin.xml中的版本号对应;
如果外挂要升级版本,则将url改为新版本外挂的地址,version改为新的版本号。改完后使用者直接在IDEA上就可以升级。
写完这个xml档案后,把这个档案放到服务器上,档案的地址就是外挂仓库的地址。在IDEA中新增自定义的外挂仓库。在外挂面板中点选Browse repositories。
点选下方的Manage repositories。
在弹出的自定义外挂仓库面板中,点选右方的加号。
输入xml档案的地址。
点选Check Now,如果弹出成功的提示窗,则表示外挂仓库能够被IDEA识别。
如果弹出错误资讯,则表示这个外挂仓库检测失败,需重新检测仓库地址或xml档案编写是否有误。
在外挂进行线上安装时,选择新增的自定义仓库,就可以看到这个仓库中的所有外挂,选择需要的外挂进行安装。
安装完外挂后,如果该外挂释出了新的版本,则Install按钮会变成Update按钮。使用者点选该按钮就可以升级到新版本的外挂。
外挂元件
外挂元件是外挂整合的基础概念。外挂元件的型别
外挂有三种类型的元件,应用级元件(Application Component)、专案级元件(Project Component)、模组级元件(Module Component)。
应用级元件会在IDEA启动的时候建立和初始化,其生命周期伴随整个IDEA程序。在外挂中只能有一个应用级元件。使用应用级元件,需在plugin.xml中定义标签,其实现类必须实现ApplicationComponent界面。在使用时可以通过ApplicationManager来获取Application例项,再通过Application例项的getComponent(Class)方法来获取应用级元件
在每个Project例项建立时,由IDEA自动为该例项建立一个专案级元件。在plugin.xml中需使用标签定义,其实现类需要实现ProjectComponent界面,使用时通过Project例项的getComponent(Class)方法获取。
当Project中有Module被建立时,IDEA会自动为其建立一个模组级元件。在plugin.xml中需使用标签定义,其实现类需要实现ModuleComponent界面,使用时通过Module例项的getComponent(Class)方法获取。
定义的每个元件,无论哪种型别,都有一个唯一的名字,用来作为外部,便于其它元件检索。通过元件的getComponentName()方法返回。元件的名字,推荐使用“外挂名.元件名”的方式来命名。
Component的周期方法
元件界面中有多个方法,会在元件生命周期的不同阶段执行。
ApplicationComponent中有initComponent()和disposeComponent()方法。initComponent()方法会在元件建立的时候被呼叫,disposeComponent()方法会在元件销毁的时候被呼叫。
ProjectComponent比ApplicationComponent多了两个方法,projectOpened()和projectClosed()。projectOpened()会在一个专案已经载入完成是呼叫,projectClosed()会在一个专案关闭后执行。
ModuleComponent比ProjectComponent多了一个moduleAdded()方法,会在模组已经被新增到project时执行。
在IDEA中新增界面
如果开发者想开发一个图形界面,并嵌入到IDEA的面板中,则需要对IDEA中的元件进行扩充套件。扩充套件和扩充套件点
IDEA提供扩充套件与扩充套件点的功能,用于外挂与IDEA平台或其他外挂进行互动。如果开发者想要使用IDEA平台或其他外挂的功能,则可以对其进行扩充套件。同样,如果你希望外挂的某些部分可以被的外挂使用,则可以将该部分宣告为扩充套件点,用于其他外挂的扩充套件。总结就是,扩充套件是你的外挂去呼叫其它的外挂,而扩充套件点则是其它外挂调你的外挂。注意,扩充套件IDEA平台或外挂,只能呼叫其已宣告为扩充套件点的部分,没有宣告为扩充套件点是不能扩充套件呼叫的。
扩充套件Setting面板
下面将展示外挂在Setting面板中增加一项配置。
在plugin.xml新增扩充套件资讯。
extensions用于宣告一项扩充套件,defaultExtensionNs为要扩充套件的物件的id,com.intellij表示是IDEA平台的id,表示对IDEA平台进行扩充套件。ApplicationConfigurable的内容会在Setting面板中展示,displayName为设定项的名称,instance则指定实现类。
接着写实现类,实现类必须实现Configurable界面,并重写改界面的方法。
createComponent()方法返回一个封装为JComponent物件的图形界面。开发者使用Java中的Swing进行图形界面的开发,并在此方法返回。使用时,当用户在Setting面板中点选此设定项,IDEA则会呼叫此方法,在面板中展示此界面。比如,输入return newJButton(“ok”),则会在面板中显示一个名称为ok的按钮。
isModified()方法则是用于监听界面中的内容是否有修改,当界面发生变化时会呼叫此方法。返回值为true时,Setting面板的Apply按钮可以点选;当返回值为false时,Apply按钮置灰,不能被点选。
apply()方法会在使用者点选Apply按钮时执行,外挂对于使用者的响应操作就可以放到此方法中进行。
reset()方法会在使用者点选Cancel按钮时执行,可以用来还原设定。
disposeUIResources()方法会在当前界面消失的时候执行。比如当用户切换到其它设定项或Setting面板关闭时,会呼叫此方法。
执行效果
执行此外挂,在Setting面板的Other Setting中可以看到此外挂增加的设定项。
外挂资料持久化
在使用者使用外挂的过程中,外挂有时需要储存使用者本次操作的资料。比如使用者上次选择的某个选项,在使用者再次开启外挂的时候,需要显示使用者上次的选择。此时则需要将使用者每次选择的资料进行持久化。IDEA提供了两种持久化的方式:PropertiesComponent和PersistentStateComponent。PropertiesComponent操作简单,适合储存简单、少量的的资料;PersistentStateComponent操作复杂,适合储存复杂、量多的资料。PropertiesComponent
PropertiesComponent会把所有资料,以key-value的形式储存到一个IDEA的公共空间,所以定义的key必须是唯一的,建议使用专案名作为字首。使用时,通过setValue()方法将资料储存到propertiesComponent中。
获取资料时,通过getValue()方法获取。
PersistentStateComponent
PersistentStateComponent的方式则会在本地新建一个xml档案,将外挂资料储存到这个xml档案中。使用前需要先写一个PersistentStateComponent界面的实现类,用于定义xml档案。
以上的程式码中,@State用于配置xml档案,name用于指定元件名称,storages用于指定档案储存的位置。在IDEA 2016以后的版本中,直接在@Storage注解为value属性赋值,值为xml档案的档名,即@Storage(value = “filename.xml”)。value属性可以省略,直接填写档名,外挂使用时会在IDEA的使用者目录中自动生成这个档案。
而在IDEA 2016及之前的版本,需要在@Storage注解中给id和file属性赋值(例如: @Storage(id = "rulesGroup", file="$APP_CONFIG$/rulesGroup.xml"), $APP_CONFIG$ 为IDEA安装后的预设使用者路径)。
持久化资料在xml档案中以key-value的形式储存,实现类中的属性名就是key。
以上的PersistentStateComponent实现类是将储存资料的属性定义在了实现类自身,当储存的资料较多时,对应的属性也会很多,类中的程式码管理会显得混乱。这时可以在该实现类中定义一个内部类,把属性写到这个内部类中。
程式码中的getState()方法,会在储存设定(比如settings视窗失去焦点,关闭IDEA)的时候呼叫。如果使用者没有修改内容,则不会储存。loadState()方法会在建立元件或xml档案被外部改变的时候呼叫。
如果没有对实现类中属性进行封装,使用时可以直接对属性进行操作。
建议对属性进行封装,通过get和set方法来或者属性值。
后记
IDEA外挂开发的技术点还有很多,比如进度条、PSI等。由于文章篇幅的限制,以上内容只是对其部分内容进行举例。希望能在以后为大家分享更多的IDEA外挂开发技术。欢迎大家关注“58架构师”微信公众号,定期分享云端计算、AI、区块链、大资料、搜寻、推荐、储存、中介软件、移动、前端、运维等方面的前沿技术和实践经验。