
简介面向初学者的 IntelliJ IDEA 插件开发源码示例适合希望快速掌握编辑器右键菜单、弹出框以及鼠标事件处理能力的 Java 开发者。压缩包共16个文件大小约10KB以 Java 源码和 XML 配置为主体5个 Java 类实现核心交互逻辑5个 XML 文件负责 plugin.xml 插件注册、项目模块及工作区配置2个 SVG 提供明暗主题图标另有 .name、.gitignore 与 iml 等辅助文件src 目录集中存放 Action 与监听器代码resources 下保存 META-INF 配置和图标资源.idea 目录记录调试运行环境。已有785人浏览学习具备较高参考价值。通过学习可完整理解插件项目结构、Action 机制、事件监听、Dialog/Popup 构建、资源引用及项目配置方式例如在右键菜单中插入自定义操作、用弹出框收集用户输入后触发回调结合源码注释能快速搭建自己的插件框架并利用 IntelliJ IDEA 内置调试工具验证交互效果。这份示例代码体量小巧、目录清晰特别适合在短时间内完成插件开发入门并延伸到 IDE 定制场景。1. 一个带完整源码的 demo 压缩包IntelliJ IDEA 插件开发最值得先打开的东西你下载了idea插件详细源码demo.zip解压之后先别急着翻plugin.xml也别盯着build.gradle发呆。我最建议你做的第一件事是先把runIde跑起来让一个全新的 IDEA 沙箱窗口出现在屏幕上。那一刻你才算真正理解插件开发是怎么闭环的——你写的代码不是在某个测试类里跑而是直接作为 IDE 的一部分活在一个干净的 IntelliJ IDEA 实例里。这个 demo 之所以叫“详细源码”是因为它把插件工程最常见的骨架都摆了出来Action、工具窗口、事件监听、持久化状态外加一整套构建配置。它解决的最实际问题不是“怎么写插件”而是“从零搭出一个能编译、能启动、能调试的插件工程”——这一步劝退了最多新手。这篇笔记适合两类人刚装好 IDEA、想搞懂插件怎么落地的新手以及能写 Java/Kotlin 但还没碰过 IntelliJ 平台的老手。2. 解压到跑通把 idea 插件 demo 工程完整启动起来2.1 先看目录结构理解插件工程的基本盘拿到idea插件详细源码demo.zip之后先解压到纯英文路径下比如D:\plugin-demo或者~/workspace/plugin-demo。中文路径本身一般没问题但后续 Gradle 下载依赖、IDE 缓存索引时偶尔会出现编码类报错没必要为这个增加变量。常见的插件工程目录长这样demo-plugin/ ├── build.gradle ├── settings.gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── gradle/ │ └── wrapper/ │ └── gradle-wrapper.properties └── src/ └── main/ ├── java/com/example/ │ ├── DemoAction.java │ ├── DemoListener.java │ └── DemoWindowFactory.java └── resources/ ├── META-INF/ │ └── plugin.xml └── icons/ └── demo-icon.svg这个目录骨架背后是一条最基本的原理IntelliJ 平台插件本质上是“一个包含类和资源的 jar 包 一个声明插件结构的plugin.xml”。IDEA 启动时并不扫描所有 class而是先读plugin.xml按里面声明的扩展点、Action、监听器去实例化对应类。所以拆 demo 时真正的主干只有三条线build.gradle决定怎么构建plugin.xml决定 IDE 如何识别插件的结构src/main/java下的类决定插件的行为。2.2 导入工程并完成构建先让 demo 能编译用 IntelliJ IDEA 打开解压后的目录注意要选build.gradle而不是直接打开文件夹。IDEA 会问你是用 Gradle 方式导入还是当成普通 Java 工程这里必须选 Gradle。导入后第一件事是检查 Gradle JVM 设置打开Settings - Build, Execution, Deployment - Build Tools - Gradle把 Gradle JVM 指向 JDK 17 或更高版本。当前主流 IntelliJ 平台基于 JDK 17 构建如果你的本机 JDK 是 8 或 11会出现类文件版本冲突报错信息里常见Unsupported class file major version。同步完成后先执行一次干净的构建确认 demo 本身没有问题./gradlew clean buildWindows 下用gradlew.bat clean build。这个命令会编译src/main/java下的所有源码把资源文件打进 jar并生成最终插件包。如果这一步通过说明工程的基础依赖和源码都是完整的如果失败优先看是不是网络原因导致 Gradle 依赖没有下载完国内环境常见多执行几次或者更换 Maven 镜像即可。构建产物会出现在build/libs/目录下文件名一般是demo-plugin-1.0.0.zip或者.jar这个包其实已经可以被 IDEA 安装但我们现在要做的是直接启动一个调试用的 IDE。2.3 跑 runIde看沙箱 IDEA 如何加载你的插件runIde是插件开发里最重要的一个 Gradle 任务它会在本地启动一个全新的 IDEA 实例这个实例和日常使用的 IDE 完全隔离只加载当前工程的插件。执行命令./gradlew runIde首次运行会下载对应版本的 IntelliJ IDEA 运行时体积较大耐心等待。启动后你会看到一个干净得像刚装完一样的 IDEA侧边栏和菜单里会出现 demo 里的 Action 或工具窗口入口。这里要理解一件事这个沙箱 IDEA 的配置目录、插件目录、日志目录都是独立的不会影响你日常使用的 IDE也不会丢失你自己的配置。这就是插件开发里“黑匣子”最透明的一个环节——你能直接看到插件加载前后的差异。运行runIde时 debug 端口默认是5005你可以随时用远程调试的方式挂上去后面我会在第 4 章具体讲。3. 把 demo 源码拆开看plugin.xml、Action 与 ToolWindow 三条主线3.1 plugin.xml 是所有插件的中枢先读懂它无论 demo 的功能多复杂它最后都会被 IDEA 通过src/main/resources/META-INF/plugin.xml这个文件识别。一个最小可用的plugin.xml是这样idea-plugin idcom.example.demo-plugin/id nameDemo Plugin/name version1.0.0/version vendor emailsupportexample.com urlhttps://example.comExample/vendor dependscom.intellij.modules.platform/depends extensions defaultExtensionNscom.intellij toolWindow idDemoWindow anchorright factoryClasscom.example.DemoWindowFactory icon/icons/demo-icon.svg/ /extensions actions action idcom.example.DemoAction classcom.example.DemoAction textDemo Action description这是一个 demo 动作 add-to-group group-idToolsMenu anchorlast/ /action /actions /idea-plugin这里的每个参数都不是摆设。id是全插件唯一的标识建议用com.你的域名.功能名的反域名格式避免和其他插件冲突光这一条就能避开后面“Action 注册了但被别的插件顶掉”的深坑。depends声明依赖的模块com.intellij.modules.platform是基础模块只要做普通插件基本都依赖它如果你的插件要操作编辑器或项目文件还需要加com.intellij.modules.java。extensions段声明扩展点比如表格里的toolWindow会在右侧创建一个工具窗口actions段则声明菜单和工具栏上的动作。注意defaultExtensionNscom.intellij这表示扩展点的命名空间写错一个字母插件就会静默加载失败而日志里只会留一句让人摸不着头脑的Cannot find declaration to goto。3.2 Action 的注册与响应逻辑用户点一下发生了什么Action 是插件里最常见的交互入口demo 里的DemoAction.java本质上只需要做两件事继承AnAction重写actionPerformed。源码一般长这样package com.example; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.ui.Messages; import org.jetbrains.annotations.NotNull; public class DemoAction extends AnAction { Override public void actionPerformed(NotNull AnActionEvent e) { Messages.showInfoMessage( Demo 插件运行正常当前项目 e.getProject(), 来自 Demo ); } }这段代码的关键在于AnActionEvent——它承载了这次动作触发的全部上下文。e.getProject()返回当前打开的项目可能是null所以真实代码里一定要判空e.getDataContext()可以拿到光标位置、选中的文件、编辑器实例等数据。这个机制是 IntelliJ 平台的核心设计Action 不直接操作全局状态而是通过 DataContext 获取当前上下文里的数据也就是业界常说的DataProvider体系。一个常见的误用是新手把需要项目信息的逻辑直接写在 Action 构造函数里然后发现getProject()返回 null。原因很简单——AnAction对象在 IDE 启动时就被实例化了那时候还没有任何项目打开。所以如果需要项目数据只能在actionPerformed里拿Action 类本身要保持无状态。3.3 ToolWindow 与持久化状态让插件的界面真正“活”起来demo 里如果包含工具窗口它展示的才是插件从“菜单弹窗”进化到“常驻界面”的形态。ToolWindow 的注册方式已经在plugin.xml里写好了对应的DemoWindowFactory是这样的package com.example; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowFactory; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentFactory; import org.jetbrains.annotations.NotNull; import javax.swing.*; public class DemoWindowFactory implements ToolWindowFactory { Override public void createToolWindowContent(NotNull Project project, NotNull ToolWindow toolWindow) { JPanel panel new JPanel(); panel.add(new JLabel(这是 Demo 工具窗口的内容区域)); Content content ContentFactory.getInstance() .createContent(panel, Demo, false); toolWindow.getContentManager().addContent(content); } }工具窗口的价值不只是展示一个面板它还意味着插件有了自己的生命周期窗口打开、关闭、项目切换、IDE 启动和退出。demo 里如果还有状态保存逻辑十有八九用的是PropertiesComponent这是 IntelliJ 平台提供的最轻量持久化方案PropertiesComponent.getInstance(project).setValue(demo.lastTimestamp, String.valueOf(System.currentTimeMillis()));它把配置存到 IDE 的配置目录不需要你自己管数据库或文件路径。这里要提醒一句PropertiesComponent适合存少量业务设置如果数据量大、结构复杂用它就是自找麻烦后面我会讲到什么场景该换 PersistentStateComponent。4. build.gradle 与调试手法决定 demo 能不能变成产品的细节4.1 build.gradle 里决定成败的 5 个配置项idea插件详细源码demo.zip里的构建脚本通常是基于 Gradle 插件org.jetbrains.intellij的写法。这个插件帮你完成了下载 IDE 依赖、准备沙箱环境、打包插件等一系列任务。核心配置大致是这样plugins { id java id org.jetbrains.intellij version 1.x.x // 以 demo 工程锁定的版本为准 } group com.example version 1.0.0 repositories { mavenCentral() } dependencies { testImplementation junit:junit:4.13.2 } intellij { version 2023.1 type IC plugins [com.intellij.java] } patchPluginXml { sinceBuild 231 untilBuild 241.* }这五个配置项每一个翻车都能让你卡上一整天。第一org.jetbrains.intellij的版本不要照抄网上的用法要和你本地 Gradle 版本匹配否则会报方法签名错误。第二intellij.version决定你基于哪个 IDE 版本开发这个版本会影响 API 的可用性——有些新 API 在老版本里不存在编译直接失败。第三type表示 IDE 发行版IC是社区版、IU是旗舰版如果你用了旗舰版才有的 API运行时就会报NoClassDefFoundError。第四plugins字段用于声明附带的插件依赖比如开发 Java 相关功能必须加com.intellij.java。第五patchPluginXml的sinceBuild和untilBuild直接决定这个插件能装到哪些 IDEA 版本上这个范围写得过宽插件可能加载后行为异常写得太窄用户升级 IDE 后就装不上了。4.2 调试插件源码的三层手段runIde跑起来之后插件代码就在另一个 IDEA 进程里运行了。第一层调试手段也是最常用的是直接打日志com.intellij.openapi.diagnostic.Logger。在你自己的类里声明一个静态 Logger 实例然后调用logger.info(...)、logger.warn(...)。日志会输出到沙箱 IDE 的日志文件里路径一般在~/Library/Logs/JetBrains/IntelliJIdea版本号/idea.log (macOS) %LOCALAPPDATA%\JetBrains\IntelliJIdea版本号\log\idea.log (Windows)第二层手段是断点调试。runIde默认启动时会开启调试端口你可以在 IDEA 里新建一个Remote JVM Debug运行配置host 填 localhost端口填5005然后点 Debug 按钮。挂上之后在 demo 源码里打断点沙箱 IDE 里触发对应操作调试器就会停在断点上。这是定位插件问题最高效的方式比打日志循环验证快得多。第三层手段是查看 IDE 自带的错误报告。当插件抛出异常时沙箱 IDEA 会弹出Fatal Errors对话框里面会列出异常堆栈。很多新手忽略这个对话框直接点掉然后去源码里瞎猜。实际上堆栈里已经把出错的类、行号、调用链都写清楚了。我一般会先复制堆栈内容用类名去定位源码而不是在代码里到处加日志。调试插件有一个关键观念要转过弯你写的代码跑在 IDE 进程里IDE 本身也是 Java 程序所以 OOM、死锁、类加载冲突这些问题它全都会有只是发生时机和普通 Web 应用完全不同。5. 避坑插件 demo 从导入到发布最常见的 6 个翻车现场5.1 第一类翻车导入环节就卡住的两条坑现象Gradle 同步报错提示Unsupported class file major version 61或者Failed to apply plugin org.jetbrains.intellij。原因这是本机 JDK 版本太老。IDEA 2021.2 之后的平台基于 JDK 17 构建而不少开发机默认的JAVA_HOME还是 JDK 8。Gradle 进程用 JDK 8 启动去加载要求 JDK 17 的插件和平台类自然就翻车了。解决到Settings - Build, Execution, Deployment - Build Tools - Gradle把Gradle JVM切到 JDK 17 或更高。如果你本机没装 JDK 17直接用 IDEA 自带的 JBRJetBrains Runtime也可以它本质上就是一个 JDK 17。注意改完设置后重启 Gradle 同步否则缓存里还是旧 JVM。现象导入之后 IDEA 提示Plugin Plugin DevKit is required或者菜单里找不到任何插件开发相关的入口。原因旧版 IDEA 把插件开发工具做成了独立插件Plugin DevKit默认没有安装。如果 demo 的构建方式是基于 DevKit 而不是纯 Gradle这个插件缺失会导致工程无法识别为插件项目。解决打开Settings - Plugins在 Marketplace 搜索Plugin DevKit并安装。不过我更推荐直接走 Gradle 方式——org.jetbrains.intellij插件不强制依赖 DevKit而且打包、sandbox 管理都更方便这也是如今官方主推的方式。5.2 第二类翻车运行期失灵的三个典型症状现象runIde启动成功了但在菜单里找不到 Action或者点击报错Cannot find class ...。原因最常见的是plugin.xml里的class属性写错了全限定名或者 Action 类没有public构造。IDEA 加载插件时不会在启动阶段就实例化所有 Action它只做“懒加载”——真正点击菜单时才去反射创建对象。所以类名写错不会让runIde启动失败只会让按钮在点击时才报错。解决确认plugin.xml里 class 的全限定名和实际包路径一致打开沙箱 IDE 的日志文件搜索Action或插件 id 相关关键字定位具体的 ClassNotFoundException。然后顺手养成一个习惯Action 类里声明一个无参构造器别依赖任何有参构造。现象改了源码之后重新runIde发现改动没有生效还是旧行为。原因你大概率没有重新构建就直接跑沙箱而沙箱进程是旧代码启动的。或者你在同一个沙箱进程里开了热部署动态插件加载但当前类不支持动态加载。解决每次改动代码以后先执行一次./gradlew build再runIde。如果为了提高效率想用热部署需要满足两个前提一是 IntelliJ 平台开启动态插件加载默认支持部分场景二是你的插件类不能出现静态初始化逻辑锁死类加载器。最可靠的做法是重启沙箱别把热部署想得太神它对于 ToolWindow 这类 UI 组件经常失效。现象插件打包出来在另一台电脑的 IDEA 上安装时提示Plugin requires IDE version X or earlier。原因patchPluginXml里untilBuild写得太死而新版本 IDE 的 build 号超过了声明上限。比如你在 2023.1 上开发把untilBuild写成了231.0别人用 2023.2 就装不上。解决把sinceBuild设为你开发时所用 IDE 的主版本号untilBuild写成通配符形式比如241.*表示允许 2024.1 系列的所有小版本。要测试最低兼容版本需要换一个老版本 IDE 打开工程重新runIde这是最费时间的环节也是插件发布前绝对值得投入的部分。5.3 第三类翻车打包与发布前的隐蔽坑现象打包后的插件安装时 IDEA 提示Plugin is not compatible with the current IDE但版本范围明明是对的。原因插件依赖了某个特定depends模块而这个模块不在目标 IDE 里。典型的例子是基于社区版IC开发但代码里用了旗舰版才有的com.intellij.modules.ultimateAPI。解决在plugin.xml的depends里明确声明你依赖的模块同时把目标type对齐。如果你希望插件同时兼容社区版和旗舰版就不要用平台类里标注了Internal或属于旗舰版命名空间的 API这些类名在编译期可能通过运行期才炸。现象插件发布后功能正常但启动 IDE 时非常慢日志里出现大段的Loading plugin took xxx ms。原因插件的plugin.xml里注册了大量全局组件导致 IDE 启动时全部要初始化。全局组件实例和项目无关却在每个项目打开时都要加载拖慢整体速度。解决只保留必须的 Action 和 ToolWindow 注册全局监听器改成按需激活方式。比如用ProjectManagerListener注册时在projectOpened后再做实际初始化而不是在组件构造时把整个服务的依赖全部拉起。这条优化不会体现在 demo 的功能表面但在真实产品里或许是用户给出差评的主要原因之一。6. 从 demo 到能用的插件三个进阶验证技巧第一个验证技巧是日志定位法。runIde启动的沙箱 IDEA 会生成一个独立的idea.log文件里面记录了插件加载、Action 注册、扩展点发现的全部过程。每当你改完plugin.xml不确定某个组件是否注册成功时直接查日志里Plugin Demo Plugin ...这一段它会明确写出加载耗时的组件的全限定名。看到PluginError级别的内容就该停下来补课而不是继续写业务代码。第二个验证技巧是把插件安装到一个“干净”的 IDEA 里测试。不要一直在runIde的沙箱里验证沙箱环境是全新的不代表现实场景。更贴近用户的做法是用./gradlew buildPlugin打出 zip 包然后在另一台电脑或另一个用户目录下启动一个正式版 IDEA通过Settings - Plugins - Install Plugin from Disk安装。这一步能测出依赖缺失和版本范围两大问题也最接近用户的第一感。第三个技巧是兼容性矩阵验证。你开发时的 IDE 版本越新你的插件对旧版的兼容性越差这是插件开发里绕不开的规律。我的习惯是维护两张表一张记录每个 API 用到的 IDE 版本一张记录目标用户的 IDE 分布。确定最小的sinceBuild之后用那个旧版本重新跑一遍runIde把所有功能过一遍。早期我写过一个插件自测全通过发布后大量用户反馈菜单消失最后发现是用了新版本才有的AnActionEvent.getDataContext()返回值判断方式。从那以后我给自己定了一条死规矩任何一次发布前至少用两个不同大版本的 IDE 跑一遍核心流程。这条规矩让我后来少收了很多条“插件挂了”的邮件希望也能帮到你。本文还有配套的精品资源点击获取