IntelliJ IDEA插件结构
插件是扩展IDEA功能的唯一途径。一款插件使用IDEA或其他插件暴露的API实现它的功能。这篇文章关注插件系统的结构和插件的生命周期。文章中不会指出任何可能被插件使用的其他API接口。
文章中包含了以下主题:
- 插件内容
- 插件类加载器
- 插件组件
- 插件扩展和扩展点
- 插件交互(Action)
- 插件服务
- 插件配置文件
插件内容
有3种方式组织插件内容:
1.由插件文件夹内的一个.jar文件构成一个插件。这个压缩包内应该包含配置文件(META-INF/plugin.xml)和实现插件功能的类文件。配置文件指出插件的名称、描述、版本、厂商(制作者)、支持的IDEA版本、插件组件、交互(Action)和交互组(Action Group)、Action用户界面位置等。
.IntelliJIDEAx0
plugins
sample.jar/
com/foo/.....
...
...
META-INF
plugin.xml
2.插件文件位于一个文件夹中:
.IntelliJIDEAx0
plugins
Sample
lib
libfoo.jar
libbar.jar
classes
com/foo/....
...
...
META-INF
plugin.xml
“classes”文件和所有“lib”文件夹下的jar包会被自动加入classpath中。
3.插件文件位于lib文件夹下的一个jar文件中:
.IntelliJIDEAx0
plugins
Sample
lib
libfoo.jar
libbar.jar
Sample.jar/
com/foo/.....
...
...
META-INF
plugin.xml
所有来自lib文件夹下的jar包会被自动加入classpath中。
插件类加载器
为了加载各个插件的类文件,IDEA使用一个单独的类加载器。这允许各个插件使用同一类库的不同版本,即使相同的类库被IDEA或另一个插件使用。
默认情况下,IDEA的主要类加载器加载那些插件类加载器找不到的类。然而,在plugin.xml文件中,可以使用<depends>元素来指定一个插件依赖于另一个或更多其他插件。在这种情况下,那些(被依赖的)插件的类加载器将(优先)用来加载当前插件的类加载器找不到的类。这将允许一个插件引用另一个插件中的类。
插件组件
组件是插件整合的基础概念。有三种组件类型:application-level、project-level和module-level。
Application-level组件在IDEA启动时就被创建并初始化。可以从Application实例中使用getComponent(Class)方法来获取它们。
Project-level组件被IDEA中的各个Project实例创建(请注意组件甚至可以被未打开的project创建)。可以从Project实例中使用getComponent(Class)方法来获取它们。
Module-level组件在IDEA加载各个project时为各个Module创建。Module-level组件可以从Module实例中使用相同的方法获得。
各个组件都应在配置文件中指明接口和实现类。接口类用于从其他组件索引组件(附原文,水平有限,实在不知道怎么翻译:The interface class will be used for retrieving the component from other components),实现类用于组件实例化。注意两个同水平(Application、Project或Module)的两个组件不能使用相同的接口类。接口和实现类可以相同。
每个组件具有唯一的名称用于外部和内部的需求。组件名称可由它的getComponentName()方法返回。
组件的命名标记
推荐以<plugin_name>.<component_name>格式命名组件。
Application组件
Application-level组件实现类可以选择性的实现ApplicationComponent接口。一个无依赖的application组件应该有一个无参数的构造器用于组件实例化。如果一个application组件依赖于其他application组件,它应该指定这些组件作为构造器的参数,那么IDEA将会保证这些组件按正确的顺序实例化以保证依赖关系。
注意application-level组件必须在plugin.xml文件中在<application-components>小节中注册(参见之后的组件配置文件小节)。
快速创建Application组件
IntelliJ IDEA建议通过一种简化的方式创建application组件,具有所有需要的基础结构。IDEA的接口将帮助你声明一个application组件实现类并自动在plugin.xml文件的<application-components>小节进行适当的修改。
要创建并注册一个application组件:
- 在你的项目中,在目的包路径的上下文菜单中点击New菜单项或按快捷键ALT + INSERT。
- 在New菜单中,点击Application Component。
- 在打开的New Application Component对话框里,输入application组件名称,然后点击OK。
IntelliJ IDEA生成一个实现ApplicationComponent接口的一个Java类,在plugin.xml文件中注册新创建的组件,在模块树视图中增加一个节点,并在编辑器中打开创建的application组件类文件。
Project组件
Project-level组件的实现类可以实现ProjectComponent接口。一个project-level组件的构造器可以包含一个Project类型的参数,如果它需要一个project实例。还可以指定其他application-level或project-level组件作为参数,如果它依赖这些组件。
注意project-level组件必须在plugin.xml文件的<project-components>小节中注册(参见之后的组件配置文件小节)。
快速创建project组件
IntelliJ IDEA建议通过一种简化的方式创建project组件,具有所有需要的基础结构。IDEA的接口可以帮助你声明一个project组件的实现类并自动在plugin.xml文件的<project-components>小节进行适当的修改。
要创建并注册一个project组件
- 在你的项目中,在目的包路径的上下文菜单中点击New菜单项或按下快捷键ALT + INSERT。
- 在New菜单中,点击Project Component。
- 在打开的New Project Component对话框里,输入project组件名称,然后点击OK。
IntelliJ IDEA生成一个实现ProjectComponent接口的一个Java类,在plugin.xml文件中注册新创建的组件,在模块树视图中增加一个节点,并在编辑器中打开创建的project组件类文件。
Module组件
Module-level组件实现类可以选择性的实现ModuleComponent接口。一个module-level组件的构造器可以包含一个Module类型的参数,如果它需要一个module实例。还可以指定其他application-level、project-level或者module-level组件作为参数,如果它依赖这些组件。
注意module-level组件必须在plugin.xml文件的<module-components>小节中注册(参见之后的组件配置文件小节)。
快速创建module组件
IntelliJ IDEA建议通过一种简化的方式创建module组件,具有所有需要的基础结构。IDEA的接口可以帮助你声明一个module组件的实现类并自动在plugin.xml文件的<module-components>小节进行适当的修改。
要创建并注册一个module组件
- 在你的项目中,在目的包路径的上下文菜单中点击New菜单项或按下快捷键ALT + INSERT。
- 在New菜单中,点击Module Component。
- 在打开的New Module Component对话框里,输入module组件名称,然后点击OK。
IntelliJ IDEA生成一个实现ModuleComponent接口的一个Java类,在plugin.xml文件中注册新创建的组件,在模块树视图中增加一个节点,并在编辑器中打开创建的module组件类文件。
组件状态持久化
如果组件的实现类实现了JDOMExternalizable(已过时)接口或PersistentStateComponent接口,组件的状态会被自动保存和加载。
当组件的类实现了PersistentStateComponent接口时,组件的状态(你可以在Java代码中使用@State和@Storage注释指定)保存到一个XML文件中。
当组件的类实现了JDOMExternalizable接口时,组件在如下文件中保存状态:
- Project-level组件保存状态到project文件(.ipr)。然而如果在plugin.xml文件中workspace选项被设为true,那么组件将保存它的配置到workspace文件(.iws)。
- Module-level组件保存它们的状态到module文件(.iml)。
要获取更多信息和例子,可以参考Persisting State of Components。
默认值
默认值(组件的预定义设置)应该在<component_name>.xml文件中设置。将这个文件放到插件的classpath中与默认包路径对应的文件夹中。readExternal()方法将在<component>根标签处理被调用。如果一个组件有默认值,readExternal()方法将被调用两次:第一次用来读取默认值,第二次用于保存配置(应该是用来对比是否相对默认值做了更改)。
插件组件的生命周期
组件将按如下顺序加载:
- 创建 - 构造器被调用。
- 初始化 - initComponent方法被调用(如果组件实现了ApplicationComponent接口)。
- 配置 - readExternal方法被调用(如果组件实现了JDOMExternalizable接口),或者loadState方法被调用(如果组件实现了PersistentStateComponent接口并且具有非默认(无值硬编码)的持久化状态)。
- 对于module组件,ModuleComponent接口中声明的moduleAdded方法将被调用来通知一个module已经被加入了project。
- 对于project组件,ProjectComponent接口中声明的projectOpened方法将被调用来通知一个project已被加载。
组件将按如下顺序卸载:
- 保存配置 - writeExternal方法将被调用(如果组件实现了JDOMExternalizable接口),或者getState方法(此处可能原文有误,待查)将被调用(如果组件实现了PersistentStateComponent接口)。
- 处理 - disposeComponent方法将被调用。
注意,在你的组件的构造器中,你不能使用getComponent()方法来请求其他组件,否则,你将得到一个断言(get an assertion)。如果你需要在初始化组件时访问其他组件,你可以将它们指定为构造器参数或者在initComponent方法中访问它们。
示例插件
一个阐述如何创建具有application level和project level组件的插件的示例插件可以在<%IDEA project directory%>/community/samples/plugin文件夹下得到。
要打开示例插件
- 运行IntelliJ IDEA并打开<%IDEA project directory%>/community/samples/plugin/plugin.ipr文件。
插件扩展和扩展点
Intellij IDEA提供扩展和扩展点的概念,允许一款插件和另一款插件或IDEA内核进行互动。
扩展点Extension Points
如果你希望你的插件允许其他插件扩展它的功能,在这个插件中,你必须声明一个或多外扩展点。每一个扩展点定义允许访问这个扩展点的一个类或接口。
扩展Extensions
如果你希望你的插件扩展其他插件或IDEA内核的功能点,在这个插件中,你必须声明一个或多个扩展。
如何声明扩展和扩展点?
你可以在插件的配置文件plugin.xml里<extensions> and <extensionPoints>小节的分别声明扩展和扩展点。
要声明一个扩展点
- 在<extensionPoints>小节,插入一个子元素<extensionPoint>,使用“name”、“beanClass”和“interface”属性来分别指定扩展点名和允许扩展插件功能的类或接口的名称。
为了澄清此步骤,参考如下plugin.xml文件示例小节:
<extensionPoints>
<extensionPoint name="MyExtensionPoint1" beanClass="MyPlugin.MyBeanClass1">
<extensionPoint name="MyExtensionPoint2" interface="MyPlugin.MyInterface">
</extensionPoints>
interface属性设定一个有助于此扩展点的插件必须实现的接口。(翻译的感觉不太对,附原文:The interface attribute sets an interfacethe plugin that contributes to the extension point must implement. 原文中的第二个“the”,感觉怎么翻译都不对味。)
beanClass属性设定一个声明了具有一个或多个被“@Attribute”标注注释的属性的bean类。有助于此扩展点的插件将从plugin.xml中读取这些属性(附原文:The plugin that contributes to the extension point will read those properties from the plugin.xml file.)。为了阐述此配置,参考如下示例代码:上述plugin.xml文件配置使用中的MyBeanClass1 bean类:
public class MyBeanClass1 extends AbstractExtensionPointBean {
@Attribute("key")
public String key;
@Attribute("implementationClass")
public String implementationClass;
public String getKey() {
return key;
}
public String getClass() {
return implementationClass;
}
}
注意为了声明一个设计要连接到MyExtensionPoint1扩展点的扩展,你的plugin.xml文件中必须包含具有“key”和“implementationClass”属性的<MyExtensionPoint1>标记来提供适当的值。(参见之后的plugin.xml)
要声明一个扩展
- 对于<extensions>元素,设置xmlns(已过时)或defaultExtensionNs属性为以下一个值:
- ”com.intellij”,如果你的插件扩展IDEA内核功能点。
- <ID of a plugin>,如果你的插件扩展另一个插件的功能点。
- 向<extensions>元素增加一个新的子元素。
这个子元素名必须匹配扩展要访问的扩展点的名称。 - 根据扩展点的类型,你需要做如下中的一件:
- 如果扩展点是使用interface属性声明的,在新增的子元素中,设置implementation属性值为实现指定接口的类的名称。
- 如果扩展点是使用beanClass属性声明的,在新增的子元素中,设置所有有指定的类中被“@Attribute”标注注释的属性。
为了阐述这个过程,参考如下plugin.xml文件示例片段,其中定义了:两个分别设计要访问IDEA内核中定义的appStarter和applicationConfigurable扩展点的扩展、一个要访问在一个测试插件中定义的MyExtensionPoint1扩展点的扩展:
<!-- Declare extensions to access extension points in the IDEA core. These extension points
have been declared using the "interface" attribute.
-->
<extensions defaultExtensionNs="com.intellij">
<appStarter implementation="MyTestPackage.MyTestExtension1"></appStarter>
<applicationConfigurable implementation="MyTestPackage.MyTestExtension2"></applicationConfigurable>
</extensions>
<!-- Declare extensions to access extension points in a custom plugin
The MyExtensionPoint1 extension point has been declared using *beanClass* attribute.
-->
<extensions defaultExtensionNs="MyPluginID">
<MyExtensionPoint1 key="keyValue" implementationClass="MyTestPackage.MyClassImpl"></MyExtensionPoint1>
</extensions>
如何获得扩展点列表?
要获得可在IntelliJ IDEA内核访问的扩展点列表,参阅如下XML配置文件的<extensionPoints>小节:
- LangExtensionPoints.xml
- PlatformExtensionPoints.xml
- VcsExtensionPoints.xml
附加信息和示例
要获取示例插件和关于如何创建贡献IDEA内核扩展的插件的详细介绍,参考Customizing the IDEA Settings Dialog和Creation of Tool Windows.
插件交互(Action)
Intellij IDEA提供交互(action)的概念。一个交互是一个源于AnAction类的子类,其actionPerformed方法将在菜单项或工具栏按钮被选中时调用。交互系统允许插件向IDEA菜单和工具栏中增加自己的菜单/工具项。
交互被按组管理,一个组可以包含其他的组。一组交互可以形成一个工具栏或菜单。组的子组可以构成菜单的子菜单。
你可以从IntelliJ IDEA Action System和Creating an Action找到如何创建并注册交互的详细信息。
插件服务
IntelliJ IDEA提供服务的概念。一个服务是一个在你的插件调用ServiceManager类的getService方法时按需加载的插件组件。即使一个服务被请求多次,IntelliJ IDEA也保证每个服务只有一个实例被加载。一个服务必须在plugin.xml文件中指明接口和实现类。
服务的实现类用于服务的实例化。
IntelliJ IDEA提供3类服务:application服务、project服务和module服务。
如何声明一个服务?
要声明一个服务,你可以使用如下IDEA内核的扩展点:
- applicationService: 设计用来声明一个application服务
- projectService: 设计用来声明一个project服务
- moduleService: 设计用来声明一个module服务
要声明一个服务
- 向plugin.xml文件的<extensions>小节添加适当的子元素(<applicationService>、<projectService>或<moduleService>)。
- 在新增的子元素里,设置如下属性:
- serviceInterface: 指定服务接口类。
- serviceImplementation: 指定服务实现类。
注意接口和实现类可以是同一个类。
为了阐述服务声明过程,参考如下的plugin.xml框架:
<extensions defaultExtensionNs="com.intellij">
<!-- Declare the application level service -->
<applicationService serviceInterface="Mypackage.MyServiceInterfaceClass" serviceImplementation="Mypackage.MyServiceImplClass">
</applicationService>
<!-- Declare the project level service -->
<projectService serviceInterface="Mypackage.MyProjectServiceInterfaceClass" serviceImplementation="Mypackage.MyProjectServiceImplClass">
</projectService>
</extensions>
How It Works?
为了实例化你的服务,在Java代码中,使用如下语法:
MyServiceImplClass service = ServiceManager.getService(MyServiceImplClass.class);
示例插件
这个小节将允许你下载并安装一个说明如何创建并使用一个插件服务的示例插件。
这个插件拥有一个实现了一个服务的project组件,这个服务统计当前IntelliJ IDEA打开的project数量。如果这个统计数超出了允许同时打开project的最大数的限制,这个插件将返回一个错误信息并关闭最近打开的project。
要安装并运行示例插件
- 点击此处下载包含示例插件项目的.zip压缩包。
- 在一个独立的文件夹中解压.zip压缩包中的所有文件。
- 打开IntelliJ IDEA,在欢迎页面,点击Open Project,然后使用Open Project对话框打开下载的MaxOpenedProjects项目。
- 在主菜单,点选Run | Run或按下Shift + F10。
- 如果有必要,修改Run/Debug Configurations。
插件配置文件(plugin.xml)
如下是一份示例插件配置文件。此示例展示并描述了所有会在plugin.xml文件中使用的元素。
<!-- url="" specifies the URL of the plugin homepage (displayed in the Welcome Screen and in "Plugins" settings dialog) -->
<idea-plugin url="http://www.jetbrains.com/idea">
<!-- Plugin name -->
<name>VssIntegration</name>
<!-- Unique identifier of the plugin. Cannot be changed between the plugin versions. If not specified, assumed to be equal to <name>. -->
<id>VssIntegration</id>
<!-- Description of the plugin. -->
<description>Vss integration plugin</description>
<!-- Description of changes in the latest version of the plugin. Displayed in the "Plugins" settings dialog and in the plugin repository Web interface. -->
<change-notes>Initial release of the plugin.</change-notes>
<!-- Plugin version -->
<version>1.0</version>
<!-- The vendor of the plugin. The optional "url" attribute specifies the URL of the vendor homepage. The optional "email"
attribute specifies the e-mail address of the vendor. The optional "logo" attribute specifies the path within the plugin JAR
to a 16x16 icon to be displayed next to the plugin name in the welcome screen. -->
<vendor url="http://www.jetbrains.com" email="support@jetbrains.com" logo="icons/plugin.png">Foo Inc.</vendor>
<!-- The unique identifiers of the plugins on which this plugin depends. -->
<depends>MyFirstPlugin</depends>
<!-- Optional dependency on another plugin. If the plugin with the "MySecondPlugin" ID is installed, the contents of mysecondplugin.xml (the format of this file conforms to the format of plugin.xml) will be loaded. -->
<depends optional="true" config-file="mysecondplugin.xml">MySecondPlugin</depends>
<!-- Allows a plugin to integrate its help system (in JavaHelp format) with the IDEA help system. The "file" attribute specifies the name of the JAR file
in the "help" subdirectory of the plugin directory. The "path" attribute specifies the name of the helpset file within the JAR file.-->
<helpset file="myhelp.jar" path="/Help.hs" />
<!-- Minimum and maximum build of IDEA compatible with the plugin -->
<idea-version since-build="3000" until-build="3999"/>
<!-- Resource bundle from which the text of plugin descriptions, action names and etc. will be loaded -->
<resource-bundle>messages.MyPluginBundle</resource-bundle>
<!-- Plugin's application components -->
<application-components>
<component>
<!-- Component's interface class -->
<interface-class>com.foo.Component1Interface</interface-class>
<!-- Component's implementation class -->
<implementation-class>com.foo.impl.Component1Impl</implementation-class>
</component>
</application-components>
<!-- Plugin's project components -->
<project-components>
<component>
<!-- Interface and implementation classes are the same -->
<interface-class>com.foo.Component2</interface-class>
<!-- If the "workspace" option is set "true", the component saves its state to the .iws file
instead of the .ipr file. Note that the <option> element is used only if the component implements the JDOMExternalizable interface. Otherwise, the use of the <option> element takes no effect.
-->
<option name="workspace" value="true" />
<!-- If the "loadForDefaultProject" tag is present, the project component is instantiated also for the default project. -->
<loadForDefaultProject>
</component>
</project-components>
<!-- Plugin's module components -->
<module-components>
<component>
<interface-class>com.foo.Component3</interface-class>
</component>
</module-components>
<!-- Actions -->
<actions>
<action id="VssIntegration.GarbageCollection" class="com.foo.impl.CollectGarbage" text="Collect _Garbage" description="Run garbage collector">
<keyboard-shortcut first-keystroke="control alt G" second-keystroke="C" keymap="$default"/>
</action>
</actions>
<!-- Extension points defined by the plugin. Extension points are registered by a plugin so that other plugins can provide this plugin
with certain data. The "beanClass" attribute specifies the class the implementations of which can be used for the extension point. -->
<extensionPoints>
<extensionPoint name="testExtensionPoint" beanClass="com.foo.impl.MyExtensionBean"/>
</extensionPoints>
<!-- Extensions which the plugin adds to extension points defined by the IDEA core or by other plugins. The "defaultExtensionNs " attribute must be set to the ID of the plugin defining the extension point,
or to "com.intellij" if the extension point is defined by the IDEA core. The name of the
tag within the <extensions> tag matches the name of the extension point, and the "implementation" class specifies the name of the
class added to the extension point. -->
<extensions xmlns="VssIntegration">
<testExtensionPoint implementation="com.foo.impl.MyExtensionImpl"/>
</extensions>
</idea-plugin>