IntelliJ IDEA组件的持久化状态
IntelliJ IDEA提供了允许组件或服务在IntelliJ IDEA每次重启之间保持它们的状态的API。你可以使用一个简单的API保存少量值,也可以使用PersistentStateComponent接口为更复杂的组件的状态进行持久化。
使用PropertiesComponent进行简单的持久化
如果你的插件需要持久化的只是少量简单的值,最容易实现的方式是使用com.intellij.ide.util.PropertiesComponent服务。它即可以在保存application级的值,也可以保存project水平的值(保存在workspace文件中)。
使用PropertiesComponent.getInstance()方法来保存application水平的值,使用PropertiesComponent.getInstance(Project)方法来保存project水平的值。
因为所有的插件共用同一命名空间,因此高度推荐为你的Key名称增加前缀(比如你的插件ID)。
使用PersistentStateComponent
com.intellij.openapi.components.PersistentStateComponent为你提供了定义要持久化的值、它们的格式、存储位置等大多数的灵活性。要使用它,你需要标记组件或服务实现PersistentStateComponent接口、定义状态类并使用@com.intellij.openapi.components.State标注指定存储位置。
注意(仅)通过实现PersistentStateComponent,扩展的实例不能保存它们的状态。如果你的扩展需要有持久化状态,你需要定义一个单独的服务来负责管理那个状态。(附原文:Note that instances of extensions cannot persist their state by implementing PersistentStateComponent. If your extension needs to have persistent state, you need to
define a separate service responsible for managing that state.我实际写的代码里并没有实现一个单独的服务,只是把PersistentStateComponent实现类注册成了application component,持久化也工作了……原文是要表达什么意思不太明白,欢迎回复讨论。)
实现PersistentStateComponent接口
PersistentStateComponent实现类需要通过状态类类型参数化。状态类可以是独立的JavaBean类,也可以是实现PersistentStateComponent的类本身。
前一种情况,通常状态类的实例作为PersistentStateComponent实现类的一个field(在内存中)存储:
class MyService implements PersistentStateComponent<MyService.State> { class State { public String value; } State myState; public State getState() { return myState; } public void loadState(State state) { myState = state; } }
后一种情况下,你可以使用如下模式实现getState()和loadState()方法:
class MyService implements PersistentStateComponent<MyService> { public String stateValue; public MyService getState() { return this; } public void loadState(MyService state) { XmlSerializerUtil.copyBean(state, this); } }
实现State类
PersistentStateComponent实现类通过将public field和bean的属性序列化为XML格式来工作。如下类型的值可以被持久化:
- 数字(包括基础类型如int和封装类型如Integer);
- 布尔值;
- 字符串;
- 集合;
- 映射;
- 枚举。
要排除一个public field或bean属性的序列化,你可以使用@com.intellij.util.xmlb.annotations.Transient标注对field或getter进行注释。
注意状态类必须有一个默认构造器。它应返回组件的默认状态(将用于还没有任何XML持久数据的情况)。
定义存储位置
为了准确的指定持久值要存储的位置,你需要为PersistentStateComponent实现类增加一个@State注释。它需要如下字段:
- name(必需) - 指定状态的名称(XML中的根标记名称)
- 一个或更多@com.intellij.openapi.components.Storage注释(必需) - 为.ipr和基于文件夹的project指定存储位置。
- roamingType(com.intellij.openapi.components.RoamingType,可选) - 指定当IDEA Server插件启用时,在不同IDEA安装之前是否启用状态同步。(不了解IDEA Server plugin,附原文:specifies whether the state is synchronized between different IDEA installations when the IDEA Server plugin is used)
- reloadable(可选) - 如果设为否,当XML文件被外部修改或状态变更时,需要project完全重新加载。
最简单地指定@Storage注释的方式如下:
- @Storage(id=”other”, file = StoragePathMacros.APP_CONFIG + “/other.xml”) 用于application水平的值
- @Storage(id=”other”, file = StoragePathMacros.PROJECT_FILE) 用于值保存在project文件(对于基于.ipr文件的project)中的情况
- @Storage(id = “dir”, file = StoragePathMacros.PROJECT_CONFIG_DIR + “/other.xml”, scheme = StorageScheme.DIRECTORY_BASED)}) 用于值保存在project文件夹(对于基于文件夹的project)中的情况
- @Storage(id=”other”, file = StoragePathMacros.WORKSPACE_FILE) 用于值保存在workspace文件中的情况
@Storage注释的id参数可以用来按指定的格式排除指定field的序列化(译者未试验)。如果你不需要排除任何内容,你可以指定id为任意字符串值。
通过为file参数指定不同的值,你可以使状态持久化不同的文件中。
当使用基于文件夹的项目格式时,如果你需要指定值保存的位置,你需要增加第二个@Storage注释,其包含一个值被设为StorageScheme.DIRECTORY_BASED的scheme参数,例如:
@State( name = "AntConfiguration", storages = { @Storage(id = "default", file = StoragePathMacros.PROJECT_FILE), @Storage(id = "dir", file = StoragePathMacros.PROJECT_CONFIG_DIR + "/ant.xml", scheme = StorageScheme.DIRECTORY_BASED) } )
自定义持久化值的XML格式
如果你要持久化状态并不简洁的匹配一个JavaBean,你可以使用org.jdom.Element作为状态类。在这种情况下,你可以使用getState()方法来构建一个具有任意结构的会被直接保存到状态XML文件中的XML元素。在loadState()方法中,你可以使用自定义的逻辑对JDOM元素树进行反向序列化。
如果你想使用默认的bean序列化但又需要自定义XML存储格式(例如,适应你的插件的旧版本或外部定义的XML格式),你可以使用@Tag、@Attribute、@Property、@MapAnnotation、@AbstractCollection注释。你可以查阅源代码(com.intellij.util.xmlb包下)来获取更多这些注释的意义的信息。
持久化组件的生命周期
当组件被创建后或保持持久化状态的XML文件被外部修改(如project文件被版本控制系统更新)时,loadState()方法被调用(仅当组件存在无默认值的持久化状态时)。在后一种情形下,组件要负责更新UI和其他依据此被修改状态的相关组件。
getState()方法在每次设置保存时(如当窗口失去焦点或IDE关闭时)调用。 如果getState()方法返回的状态与默认状态(由使用默认构造器创建的状态类获取)相等,那么什么都不会持久化到XML里。否则,返回的状态会被序列化为XML并存储。
经典API(JDOMExternalizable)
旧版的IDEA组件使用JDOMExternalizable接口持久化状态。它使用readExternal()方法从JDOM元素中读取状态,使用writeExternal()方法向其写入状态。JDOMExternalizable实现类可以手动地在属性和子元素中存储状态,使用DefaultJDOMExternalizer类来自动地存储所有的public field值。
当组件的类实现JDOMExternalizable接口时,组件将其状态保存到以下文件:
- Project组件保存其状态到project文件(.ipr)。然而,如果plugin.xml文件中workspace选项值为true,组件会将其配置保存到workspace文件(.iws)。
- Module组件保存其状态到module文件(.iml)里。