Project Model、Workspace Model 与 External System¶
项目结构是很多插件的基础:模块、内容根、源码根、排除目录、库、SDK、Facet、外部构建系统导入结果,都属于这一层。2024.2 起,官方文档明确说明 Workspace Model API 可供第三方插件使用,并推荐优先于旧 Project Model API。
Project Model 层级¶
传统 Project Model 把项目组织成四层:
| 层级 | 含义 | 常用 API |
|---|---|---|
| Project | 顶层项目容器 | Project、ProjectRootManager |
| Module | 可独立构建、运行、测试的功能单元 | Module、ModuleRootManager |
| Content Root | 模块拥有的目录边界 | ContentEntry |
| Source / Exclude Root | 源码根和排除根 | SourceFolder、ExcludeFolder |
查询文件归属时优先使用 ProjectFileIndex:
val index = ProjectFileIndex.getInstance(project)
val module = index.getModuleForFile(virtualFile)
val sourceRoot = index.getSourceRootForFile(virtualFile)
val isInSource = index.isInSource(virtualFile)
需要打开 Project Structure 中相关页面时,使用 ProjectSettingsService,不要依赖内部 UI 类。
Workspace Model¶
Workspace Model 用统一存储表示项目结构,核心概念包括:
| 概念 | 作用 |
|---|---|
WorkspaceEntity |
项目结构实体接口 |
VersionedEntityStorage |
当前版本化存储入口 |
ImmutableEntityStorage |
不受后续修改影响的不可变快照 |
MutableEntityStorage |
用于批量修改的可变副本 |
| Entity Source | 表示实体来源,例如项目文件、外部系统 |
| Symbolic Reference | 跨实体软引用 |
Workspace Model 的优势:
- 统一入口,不再分别操作 ModuleManager、LibraryTable、FacetManager 等多个服务。
- 快照不可变,读操作更安全。
- 便于批量修改和跨进程复用。
- 可以声明新的语言或框架实体,减少旧 Project Model 的 Java 历史包袱。
常见映射:
| Project Model | Workspace Model |
|---|---|
Module |
ModuleEntity |
ContentEntry |
ContentRootEntity |
SourceFolder |
SourceRootEntity |
Sdk |
SdkEntity |
Library |
LibraryEntity |
Facet |
FacetEntity |
选择 Project Model 还是 Workspace Model¶
| 场景 | 建议 |
|---|---|
| 只查询某文件属于哪个模块或源码根 | ProjectFileIndex |
| 读取当前项目结构快照 | Workspace Model |
| 批量修改模块、库、内容根 | Workspace Model |
| 需要兼容较旧 IDE 版本 | Project Model API 或兼容层 |
| 与已有语言插件 API 交互 | 先看该插件公开 API,必要时桥接 Project Model |
不要为了拿一个模块名就引入复杂的 Workspace Model 修改流程;也不要在需要批量项目结构修改时继续手工管理多个 modifiable model。
修改项目结构¶
旧 Project Model 修改通常需要创建 modifiable model、执行写动作、提交并处理事件。Workspace Model 则通过 MutableEntityStorage 对副本修改,再提交到当前模型。
设计建议:
- 批量修改项目结构时,尽量一次性提交。
- 修改逻辑要能重入,因为外部系统同步、VFS 刷新和用户设置都可能触发项目结构变更。
- 读取结构时使用快照,避免遍历过程中结构被修改。
- 不要依赖内部
impl类;如果公开 API 不足,先查官方示例和 API 状态。
External System Integration¶
External System 子系统用于接入 Maven、Gradle、sbt 等外部项目管理系统,也可以用于接入自定义构建系统。
外部系统通常需要提供:
- 从外部配置文件构建项目模型,例如
pom.xml、build.gradle.kts。 - 展示可用任务。
- 执行外部任务。
- 导入模块、库、内容根、依赖等项目结构。
- 在配置文件变化时自动刷新项目。
DataNode 与 ProjectDataService¶
External System 用 DataNode 表示导入出来的数据图:
DataNode<ProjectData>
DataNode<ModuleData>
DataNode<ContentRootData>
DataNode<LibraryDependencyData>
DataNode<LibraryData>
核心类型:
| 类型 | 作用 |
|---|---|
DataNode<T> |
数据图节点 |
Key<T> |
节点数据类型标识 |
ExternalEntityData |
外部系统数据基类 |
ProjectDataService |
把某类外部数据导入 IDE 项目模型 |
自定义数据服务注册:
<extensions defaultExtensionNs="com.intellij">
<externalProjectDataService implementation="com.example.ExampleProjectDataService"/>
</extensions>
项目导入¶
导入入口:
ProjectImportBuilderProjectImportProviderAbstractExternalProjectImportBuilderAbstractExternalProjectImportProvider
注册:
<extensions defaultExtensionNs="com.intellij">
<projectImportBuilder implementation="com.example.ExampleProjectImportBuilder"/>
<projectImportProvider implementation="com.example.ExampleProjectImportProvider"/>
</extensions>
导入逻辑要把“解析外部模型”和“写入 IDE 项目结构”分开。解析外部配置可以在后台运行;写入项目结构必须遵守平台写入规则。
Auto-Import¶
外部系统配置文件变化后,IDE 可以自动刷新项目结构。从 2020.1 起,用户不能关闭 auto-import。
实现方式:
ExternalSystemManager实现ExternalSystemAutoImportAware,描述哪些设置文件影响哪个外部项目。getAffectedExternalProjectPath()调用频繁,必须非常快。- 可用
CachingExternalSystemAutoImportAware缓存计算结果。 - 没有
ExternalSystemManager的独立系统可实现ExternalSystemProjectAware,并注册到ExternalSystemProjectTracker。
刷新通知图标可通过 ExternalSystemIconProvider 或 reloadIcon 提供。
External System 设置和测试¶
设置 UI:
- 通用设置。
- 已链接外部项目列表。
- 当前外部项目设置。
- 导入外部项目时的目标路径和选项。
项目级设置控件可继承 AbstractExternalProjectSettingsControl,导入 UI 可继承 AbstractImportFromExternalSystemControl。
测试依赖:
dependencies {
testImplementation("com.jetbrains.intellij.platform:external-system-test-framework")
}
常用基类:
ExternalSystemImportingTestCaseExternalSystemTestCase
实战检查清单¶
- 只查询文件所属模块时,用
ProjectFileIndex。 - 2024.2+ 新项目结构能力优先评估 Workspace Model。
- 避免直接访问 Project Model 内部实现类。
- External System 的模型解析和 IDE 导入分层实现。
- Auto-import 判断函数必须快。
- 大型项目同步提供进度、取消和日志。
- 对导入结果写测试,覆盖模块、源码根、库、依赖和排除目录。