影动星河近,月明无点尘。雁声鸣远汉,砧韵响西邻。归鸟栖枯树,禅僧讲梵音。蒲团一榻上,坐到夜将分。——《西游记·第十三回》
我们来评价一个插件的好与坏,除了它的功能是否符合自己的需求定位,性能也是一个很重要的指标。这期我们就聊下,可以提高插件反应速度的-插件持久化数据是怎么做的。
IntelliJ 平台为我们 提供了一个 API,允许组件或服务在 IDE 重启之间保持它们的状态。'com.intellij.openapi.components.PersistentStateComponent`此api提供了最大的灵活性来定义要保留的值、它们的格式和存储位置。
使用方式:
- 将service标记为实现
PersistentStateComponent
接口 - 定义状态类
- 使用指定存储位置
@com.intellij.openapi.components.State
请注意,扩展的实例不能通过实现来保持它们的状态PersistentStateComponent
。如果您的扩展需要具有持久状态,则需要定义一个单独的服务来负责管理该状态。
实现 PersistentStateComponent 接口
的实现PersistentStateComponent
需要用状态类的类型参数化。状态类可以是单独的 JavaBean 类,也可以是实现PersistentStateComponent
.
在前一种情况下,状态类实例通常存储为PersistentStateComponent
类中的一个字段:
@State(...)
class MyService implements PersistentStateComponent<MyService.State> {
public static MyService getInstance() {
// implementation according to Application/Project level service
}
static class State {
public String value;
}
private State myState = new State();
public State getState() {
return myState;
}
public void loadState(State state) {
myState = state;
}
}
在后一种情况下,您可以使用以下模式来实现getState()
和loadState()
方法:
@State(...)
class MyService implements PersistentStateComponent<MyService> {
public static MyService getInstance() {
// implementation according to Application/Project level service
}
public String stateValue;
public MyService getState() {
return this;
}
public void loadState(MyService state) {
XmlSerializerUtil.copyBean(state, this);
}
}
实施状态类
PersistentStateComponent
通过将公共字段、私有字段和 bean 属性序列化为 XML 格式来实现持久化。
要从序列化中排除公共字段或 bean 属性,请使用注释字段或 getter @com.intellij.util.xmlb.annotations.Transient
。
请注意,状态类必须具有默认构造函数。它应该返回组件的默认状态:如果 XML 文件中没有任何内容,则使用该默认状态。
状态类应该有一个equals()
方法,但是如果没有实现,状态对象将通过字段进行比较。
可以保留以下类型的值:
- 数字(原始类型,如
int
和盒装类型,如Integer
) - 布尔值
- 字符串
- 集合
- map
- 枚举
对于其他类型,扩展com.intellij.util.xmlb.Converter
:
class LocalDateTimeConverter extends Converter<LocalDateTime> {
public LocalDateTime fromString(String value) {
long epochMilli = Long.parseLong(value);
ZoneId zoneId = ZoneId.systemDefault();
return Instant.ofEpochMilli(epochMilli).atZone(zoneId).toLocalDateTime();
}
public String toString(LocalDateTime value) {
ZoneId zoneId = ZoneId.systemDefault();
long toEpochMilli = value.atZone(zoneId).toInstant().toEpochMilli();
return Long.toString(toEpochMilli);
}
}
@com.intellij.util.xmlb.annotations.OptionTag
定义上面的转换器@com.intellij.util.xmlb.annotations.Attribute
:
class State {
@OptionTag(converter = LocalDateTimeConverter.class)
public LocalDateTime dateTime;
}
定义存储位置
要指定持久值的精确存储位置,@State
请向类添加注释PersistentStateComponent
。
它有以下字段:
name
(必需)— 指定状态的名称(XML 中根标签的名称)。storages``@com.intellij.openapi.components.Storage
—用于指定存储位置的一个或多个注释。项目级值可选——在这种情况下使用标准项目文件。reloadable
(可选)— 如果设置为 false,当外部更改 XML 文件并且状态已更改时,需要重新加载完整的项目(或应用程序)。
指定@Storage
注释的最简单方法如下:
@Storage("yourName.xml")
如果组件是项目级别的——对于基于**.ipr**的项目,标准项目文件会自动使用——无需指定任何内容。@Storage(StoragePathMacros.WORKSPACE_FILE)
对于存储在工作区文件中的值。
通过为参数指定不同的设置,将状态保存在单独的文件中value
,这是file
2016.x 之前的参数。
注释的roamingType
参数指定共享设置时的漫游类型:@Storage
RoamingType.DEFAULT
- 设置共享RoamingType.PER_OS
- 设置按操作系统共享RoamingType.DISABLED
- 设置共享被禁用
如果有多个组件在同一个文件中存储状态,则它们必须具有相同的
romaingType
属性值。
在 IDE 安装之间共享设置
可以在不同的 IDE 安装之间共享组件的持久状态。这允许用户在每台机器上拥有相同的设置或在团队内共享他们的设置。
可以通过以下功能共享设置:
- 允许在 JetBrains 服务器上同步设置的*设置同步插件。*用户可以选择同步的设置类别。
- *设置存储库*插件,允许在用户创建和配置的 Git 存储库中同步设置。
- 允许手动导入和导出设置的*导出设置功能。*
通过Settings Sync或Settings Repository插件进行的同步仅在安装并启用这些插件时有效。
关于使特定组件的状态可共享的决定应该谨慎做出。仅应共享不特定于给定机器的设置,例如,不应共享用户特定目录的路径。如果一个组件包含可共享和不可共享的数据,则应将其拆分为两个单独的组件。
设置同步插件
设置同步插件从版本 2022.3 开始可用。
要在Settings Sync插件同步中包含插件的组件状态,必须通过注释的category
属性指定设置类别。@State
默认SettingsCategory.OTHER
值禁用组件状态的同步。
如果组件状态依赖于操作系统,roamingType
则@Storage
注释的 必须设置为RoamingType.PER_OS
。
设置存储库插件和导出设置功能
Settings Repository插件从版本 2022.3 开始解绑,将不再维护。
持久性组件可以通过设置存储库插件和导出设置roamingType
功能共享,具体取决于@Storage
注释。有关详细信息,请参阅定义存储位置。
自定义持久值的 XML 格式
如果您想使用默认的 bean 序列化但需要自定义 XML 中的存储格式(例如,为了与以前版本的插件或外部定义的 XML 格式兼容),您可以使用@Tag
, @Attribute
, @Property
, @MapAnnotation
,@XCollection
注解。
如果您需要序列化的状态没有完全映射到 JavaBean,您可以使用org.jdom.Element
状态类。在这种情况下,您可以使用该getState()
方法构建具有任意结构的 XML 元素,然后将其直接保存在状态 XML 文件中。在该loadState()
方法中,您可以使用任何自定义逻辑反序列化 JDOM 元素树。请注意,不建议这样做,应尽可能避免这样做。
迁移持久值
如果底层持久性模型或存储格式已更改,ConverterProvider
则可以提供ProjectConverter
其getAdditionalAffectedFiles()
方法返回受影响的文件以进行迁移并执行存储值的编程迁移。
持久化组件生命周期
该loadState()
方法在创建组件后调用(仅当组件存在一些非默认状态时),并且在外部更改具有持久状态的 XML 文件后(例如,如果项目文件从版本控制系统)。在后一种情况下,组件负责根据更改后的状态更新 UI 和其他相关组件。
getState()
每次保存设置时都会调用该方法(例如,在框架停用或关闭 IDE 时)。如果返回的状态getState()
等于默认状态(通过使用默认构造函数创建状态类获得),则不会在 XML 中保留任何内容。否则,返回的状态以 XML 序列化并存储。
使用 PropertiesComponent 实现简单的不可漫游持久化
如果插件只需要保留几个简单的值,最简单的方法就是使用com.intellij.ide.util.PropertiesComponent
服务。它可以在工作区文件中保存应用程序级别的值和项目级别的值。漫游被禁用PropertiesComponent
,因此仅将其用于临时的、不可漫游的属性。
使用PropertiesComponent.getInstance()
存储应用程序级值的PropertiesComponent.getInstance(Project)
方法和存储项目级值的方法。
由于所有插件共享相同的命名空间,因此强烈建议为键名添加前缀(例如,使用插件 ID com.example.myCustomSetting
)。
保留敏感数据
用于PasswordSafe
处理凭据。
常用实用方法:
private CredentialAttributes createCredentialAttributes(String key) {
return new CredentialAttributes(
CredentialAttributesKt.generateServiceName("MySystem", key)
);
}
检索存储的凭据
String key = null; // e.g. serverURL, accountID
CredentialAttributes credentialAttributes = createCredentialAttributes(key);
Credentials credentials = PasswordSafe.getInstance().get(credentialAttributes);
if (credentials != null) {
String password = credentials.getPasswordAsString();
}
// or get password only
String password = PasswordSafe.getInstance().getPassword(credentialAttributes);
存储凭据
CredentialAttributes credentialAttributes = createCredentialAttributes(serverId); // see previous sample
Credentials credentials = new Credentials(username, password);
PasswordSafe.getInstance().set(credentialAttributes, credentials);