做 ABAP 开发的朋友应该都有这种感觉:ADT(ABAP Development Tools)很好用,但总有几个操作是 IDE 不提供的,比如“批量生成注释头”“一键打开当前对象的传输请求”“把一段 CDS 代码复制成规范格式”。这些重复劳动很磨人,又不好意思总麻烦同事改标准功能。如果你也遇到过这种问题,那这个标题就是给你准备的——用 ABAP Development Tools 自带的 Eclipse 插件机制,给自己的 IDE 加一个专属 Action。
说白了,ADT 本身就是一套基于 Eclipse 的 RCP 应用,Eclipse 开放了大量扩展点(Extension Point),允许我们以插件的方式往里面注入新命令、新菜单、新快捷键。这篇文章会完整记录我从创建插件工程、写 Handler、挂菜单、调试到最终部署的整个路径,适合有 Java 基础、被 ABAP 重复操作困扰的开发者,也适合想给团队发“效率工具包”的 Basis 或架构师参考。整体不难,难点不在代码,而在“怎么和 ADT 工作台正确打交道”。
1. 为什么需要自定义的 IDE Action
1.1 真实场景:不是没工具,是工具不够贴身
先说我遇到的一个典型场景。某项目上用 CDS 视图做数据模型,团队要求每个 CDS 顶部必须有一段固定格式的注释,包含维护人、变更日期、需求单号。这几样信息人人在记事本里手敲,格式也不统一,代码审查时天天挑毛病。我当时的第一个想法是写个 ABAP 程序去批量处理,但程序跑在后台,跟当前正在编辑的文件没有上下文联动,得手动复制对象名再粘贴,体验很割裂。后来才意识到,真正合理的方案是把“生成注释头”变成 IDE 里的一个按钮,点一下就能拿到当前编辑器里的对象名和类型,自动生成内容插到光标处。
这种需求不是个例。还有“复制对象名时自动带上前缀”“一键格式化 CDS 注释”“对比本地版本和远端版本”等等。只要操作需要“知道当前在编辑什么”,就天然适合做成 IDE Action,而不是外部脚本。
1.2 三条路线对比:为什么最终选了 Eclipse 插件
为了做这个功能,我实际评估过三条技术路线。
- 路线一:SAP GUI 录制回放或 ABAP 后端报表。好处是后端逻辑直接复用 ABAP 代码,缺点是无法感知 Eclipse 编辑器里的当前上下文,ui 层面的体验基本等于零,只能靠人为复制粘贴对象名来关联。
- 路线二:在外部用脚本连接 ADT 的公开协议,去解析文件、调用后端。这个思路可行但学习成本极高,通信协议、认证、对象模型都要自己摸,维护成本不低。
- 路线三:直接开发 Eclipse 插件,挂进 ADT。这是最贴近“IDE 原生功能”的做法。Eclipse 平台本身就把 commands、handlers、menus 这些机制开放出来了,ADT 也允许第三方插件运行在其上。我在 IDE 里连鼠标点击事件都能拿到,自然也知道当前打开的是哪个 ABAP 对象。
三选一的时候,其实核心判断标准只有一句话:这个动作和编辑器上下文耦合得深不深。深,就走插件;浅,就写外部脚本。我的场景明显属于前者。
1.3 什么场景真正值得写插件
不是说“为了写而写”。从我自己的经验看,满足下面任一条件才值得投入做插件:
- 操作频率高,每周至少要做几十次,而且步骤完全固定;
- 和当前编辑对象强相关,必须读取 IDE 里的选中项或编辑器内容;
- 需要在团队内分发,且要求入口极简,不想教别人配脚本。
条件都不满足时,老老实实手操就行,插件不是勋章,不值得为它硬造需求。
2. 动手前必须搞清楚的底层机制
2.1 一句话说清 ADT 和 Eclipse 的关系
ADT 不是一个独立应用,它是运行在 Eclipse 之上的一组功能集。Eclipse 自己有一套“插件 = 组件,扩展点 = 接口”的骨架,ADT 只是按这套规则把 ABAP 工具链装了进去。所以你对 Eclipse 插件模型的理解,基本可以直接平移过来:往 plugin.xml 里声明一个扩展点,就会被 Eclipse 在启动时发现并加载。
这个机制可以用个生活类比:Eclipse 像一个大型商场,停车场规划了固定数量的车位(扩展点),各个品牌(插件)抢到车位后,就能在商场里开自己的柜台。ADT 只是其中一个租户,但商场的管理规则对所有人一视同仁。这意味着,只要 ADT 没有刻意堵住某个扩展开口,第三方插件就能在它的界面里加入自己的入口。
2.2 一套标准动作的完整生命周期
自定义一个 IDE Action,在 Eclipse 里几乎遵循固定套路:
- 在 plugin.xml 里用 org.eclipse.ui.commands 扩展点声明一个命令(command),命令只是个定义,没有任何逻辑。
- 用 org.eclipse.ui.handlers 扩展点把一个 handler 类绑到命令上,handler 才是真正干活的。
- 用 org.eclipse.ui.menus 扩展点把命令挂到某个菜单或工具栏位置。
- 可选地,用 org.eclipse.ui.bindings 注册快捷键。
- 用户点击菜单时,Eclipse 找到绑定的 handler,实例化并调用 execute()。
这个链路里最容易犯的错,是把逻辑全塞到命令里,或者搞不清 command 和 handler 的区别。其实 command 更像一张名片,handler 是名片背后的人;名片可以一直不变,人换不换都行。Eclipse 允许在多个 handler 之间动态切换,只是我们通常用不上。
2.3 ADT 与普通 Eclipse 插件开发的差异点
ADT 与普通 Eclipse 开发最大的不同,在于它有一个“后端连接”的维度。普通的文件编辑器插件操作本地文件就行,ABAP 场景下你得通过 ADT 的 API 去访问 ABAP 后端,拉取源代码、调用某类服务。这意味着 handler 里不仅要处理 UI 上下文,还要处理“当前登录的后端连接”。
我最初踩的一个认知误区是:以为 IDE 里看到什么,文件就在本地。实际上 ADT 里显示的是远端对象的投影,真正的内容存在 ABAP 系统里。你要获取源码,得问 ADT 服务要,而不是去文件系统里翻。理解了这一点,后面写代码时就顺了:第一步拿 UI 上下文,第二步拿后端连接,第三步才会调服务取数。
3. 核心组件逐个拆解:命令、菜单、快捷键、图标
3.1 命令声明:先做一张“名片”
在 plugin.xml 里声明命令很简单,但有几个属性值得注意。id 必须全局唯一,建议用包名倒置加功能名,比如 com.example.adt.command.showObjectInfo,避免跟 ADT 自带命令撞车。name 会显示在某些提示和快捷键列表里,尽量写成人话,比如“显示 ABAP 对象信息”。
我自己习惯先声明命令,再临时把 handler 绑上去跑通最小链路,跑通以后再美化菜单和快捷键。一上来就把配置写全,出问题了反而不知道是哪一步坏了。
3.2 菜单挂载与 visibleWhen:不是所有菜单都要出现在所有位置
把命令挂到菜单,核心是 org.eclipse.ui.menus 扩展点里的 menuContribution。locationURI 决定菜单出现在哪里,常见的有:
- popup:org.eclipse.ui.popup.any:编辑器右键菜单的兜底位置,任何弹窗都可能出现,只配合表达式过滤使用;
- toolbar:common.menu:右侧的视图工具栏;
- menu:edit?after=additions:编辑菜单下的“添加”位置附近。
这里最关键的技巧是 visibleWhen(或 enabledWhen)表达式。如果只写 locationURI 不写过滤条件,你的菜单会在所有编辑器里出现,包括 Java 编辑器、XML 编辑器,那会非常打扰。正确做法是用表达式把可见范围限制在 ABAP 编辑器或特定对象类型上。
我不建议把菜单挂到 popup:org.eclipse.ui.popup.any 以后不做任何过滤,那样基本等于裸奔,用户会在莫名其妙的地方看到你的按钮。
3.3 快捷键:绑定容易,冲突难防
org.eclipse.ui.bindings 可以给命令绑快捷键,比如把“生成注释头”绑到 Alt+Shift+C。但快捷键这块水比较深:Eclipse 的快捷键有 scheme 和 context 的概念,同一个按键在不同上下文里可以做不同的事。你把快捷键绑在默认上下文上,就很可能抢掉 ADT 或编辑器的既有快捷键。
更安全的做法是绑定到“Editor 通用上下文”之外的自定义上下文,或者干脆不在插件里绑快捷键,只在菜单上展示它。真要绑,建议先打开 Preferences → Keys,搜一下你想用的组合键是不是已经被占用。
3.4 图标与状态控制:论“质感”的重要性
一个菜单项有没有图标,直接影响使用频率。在 plugin.xml 的 command 声明里指定 icon 属性即可,路径相对于插件工程的根目录,通常是 icons/xxx.png。图标推荐 16x16 的 PNG,不要塞大图,Eclipse 缩放出来会糊。
状态控制指的是“菜单可点击条件”。如果一个 handler 只在 ABAP 编辑器打开时才有意义,就要用 expressions 扩展点控制 enabledWhen。这样用户点到灰色菜单,比点到按钮没反应体验好太多。我强烈建议每个自定义命令都配上明确的可用条件。
4. 从零创建插件工程到第一个 Action 跑通
4.1 准备开发环境
我用的开发环境组合是:
- Eclipse IDE for RCP and RAP Developers(注意不是 Eclipse for Java 开发者那个发行版,必须带 PDE 插件开发环境);
- JDK 11 或 17,取决于你本机 ADT 目标平台要求;
- ADT 本体的 plugins 目录,作为 target platform 的补充依赖;
- 一个可以访问的 ABAP 后端,用来真实调用 ADT 服务。
我一度偷懒直接拿普通 Java 版 Eclipse 加装 PDE,结果很多 RCP 相关的依赖缺失,启动调试实例时各种找不到类。后来老老实实换了 RCP 发行版,问题消失大半。
4.2 创建第一个 PDE 插件工程
在 Eclipse 里 File → New → Other → Plug-in Development → Plug-in Project,创建一个插件工程。关键配置我建议这样选:
- 工程名:跟最终要发布的插件 ID 保持一致,比如 com.example.adt.tools;
- 执行环境:选 JDK 11 以上,跟 ADT 的 class 版本兼容;
- 勾选“生成 Activator”:如果只是提供静态菜单和 handler,不一定要 activator,但勾上便于以后做插件级初始化和日志;
- 模板:选 Hello World Command 或空工程都行,我一般直接空工程,因为模板生成的代码有一堆用不上的东西。
创建完成后,工程里最重要的两个文件是 META-INF/MANIFEST.MF 和 plugin.xml。前者管依赖,后者管扩展声明。
4.3 把 ADT 的插件加进依赖
这是最开始最容易卡住的环节。新建的插件工程默认不知道 ADT 的存在,你要让它在运行时能引用 ADT 的类,必须把 ADT 插件加进依赖清单。
最靠谱的方式是在 MANIFEST.MF 的 Dependencies 页点 Add,从已安装插件里搜索 com.sap.adt.tools.core 之类的包,选中添加。如果开发环境里没有安装 ADT,你要先按正常方式把 ADT 装进这个 Eclipse,才能作为 target platform 的一部分被引用到。这一步没有捷径,没有 ADT 环境就没法开发 ADT 插件。
建议把所有 ADT 依赖都设置成 optional=true(可选依赖)。理由稍后在第 6 章提,简单说就是避免“少一个边缘依赖就把整个插件拖死”。
4.4 声明一个最小命令并写 Handler
现在先做最小闭环。在 plugin.xml 里声明命令和 handler:
<?xml version="1.0" encoding="UTF-8"?> <plugin> <extension point="org.eclipse.ui.commands"> <command id="com.example.adt.command.showObjectInfo" name="显示 ABAP 对象信息"> </command> </extension> <extension point="org.eclipse.ui.handlers"> <handler commandId="com.example.adt.command.showObjectInfo" class="com.example.adt.handler.ShowObjectInfoHandler"> </handler> </extension> </plugin>Handler 类是一个继承 AbstractHandler 的 Java 类,execute() 是入口:
package com.example.adt.handler; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.handlers.HandlerUtil; public class ShowObjectInfoHandler extends AbstractHandler { @Override public Object execute(ExecutionEvent event) throws ExecutionException { IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindowChecked(event); IEditorPart editor = HandlerUtil.getActiveEditor(event); // 从编辑器输入(IEditorInput)里获取当前 ABAP 对象的类型和名称。 // 这里以 ADT SDK 中的对象引用类示意,具体类名请按你所用 SDK 调整。 Object input = editor.getEditorInput(); // 调用 ADT 服务读取对象信息并展示 return null; } }写到这里,我建议先别急着做功能,先让菜单出现,点进去能弹个简单的 MessageDialog,把当前 editor 的类名打出来,验证链路是通的。这个“拍一拍”思路,是排查问题的最短路径。
4.5 把菜单挂到 ADT 编辑器右键菜单
最小闭环跑通后,再加菜单挂载:
<extension point="org.eclipse.ui.menus"> <menuContribution locationURI="popup:org.eclipse.ui.popup.any?after=additions"> <command commandId="com.example.adt.command.showObjectInfo" style="push"> <visibleWhen checkEnabled="false"> <with variable="activeEditorId"> <equals value="com.sap.adt.tools.ui.editor.AbstractADTEditor"/> </equals> </visibleWhen> </command> </menuContribution> </extension>activeEditorId 的具体值要以你实际安装的 ADT 为准,我这里写的只是示意。更稳的写法是结合 objectClass 和 selection 表达式过滤选中对象类型。当你发现菜单没出现时,优先怀疑这三个地方:locationURI 对不对、activeEditorId 对不对、表达式有没有写错。
5. 调试与部署:从“自己用了”到“团队能跑”
5.1 用 Host Runtime 调试插件
Eclipse 里调试插件不是 F5 跑主程序,而是右键工程 → Debug As → Eclipse Application。这会启动一个新的 Eclipse 实例,称为 runtime 实例,你的插件已经加载好,可以直接在这个实例里复现问题、打断点。
很多人第一次会愣住:为什么我的 ADT 不在这个 runtime 实例里?因为 runtime 实例默认只加载 target platform 里的插件,而 ADT 是否在 target platform 里,取决于你是否把它装进了开发用的 Eclipse。如果没装,runtime 里就没有 ADT,自然看不到 ABAP 菜单。解决办法就是先在开发 Eclipse 里装好 ADT,确保它在 target platform 的插件列表里。
5.2 导出插件并部署到用户的 ADT
自用的话,最简单是导出并放到 dropins 目录。在插件工程上右键 → Export → Deployable Plug-ins and Fragments,导出后会得到一个 jar 包(或一个包含多个 jar 的目录),把这个 jar 放到 ADT 安装目录下的 dropins 文件夹里,重启 IDE 即可。
dropins 方式对个人最方便,改代码就重新 export 覆盖 jar,再重启。缺点是不好控制版本、不方便卸载。团队分发时更合适的做法是做一个 update site(Features + Category),让同事在 Eclipse 里 Help → Install New Software 安装,之后还可以用自带的 Update 机制升级。
我个人的建议是:一两个人自用,直接 dropins;十人以上团队,必须 update site。否则你改个 bug 就要挨个帮同事换 jar,版本混乱到你想骂人。
5.3 版本兼容性:跟 ADT 版本共舞
这是 ADT 插件维护里真正的心头大患。ADT 是大版本更新频繁的工具,插件依赖的 API 可能在新版本里变了包名或方法签名。
我能给的经验是三条:
- 依赖清单里尽量只依赖稳定的公开包,宁可多写几行反射,也别硬绑某个内部类;
- 把 ADT 相关依赖都设为 optional,让缺少依赖时插件能以“未完全激活”的状态存在,不至于把整个 ADT 弄崩;
- 在插件启动时检查 ADT 核心插件版本号,版本不满足就直接给用户弹友好提示,而不是等用户点菜单时抛出 NoClassDefFoundError。
6. 常见问题排查实录:踩坑速查表
6.1 菜单不出现,可能不是扩展点写错
这个坑我印象最深。菜单死活不出来,我把 plugin.xml 检查了三遍,表达式改了好几个版本都没用,最后发现是 runtime 实例里根本没加载我的插件。右键工程 Debug As Eclipse Application 时,如果工程没有被自动添加到 launch configuration 的插件列表里,插件就不会加载。
排查办法很简单,在 runtime 实例里打开 Plug-ins 视图,搜索你的插件 ID,如果不存在,就是没被启动。另一个隐蔽原因是 MANIFEST.MF 里工程没有勾选“作为插件工程的一部分要导出”,导致构建时被漏掉。
6.2 NoClassDefFoundError:版本不对齐的典型信号
这个异常基本是指向同一个问题:你开发时引用的某个 ADT 类,在运行环境的 ADT 里不存在或包名变了。常见于用户用的 ADT 版本比你本地工作区的新或旧。
处理路径是:先在 MANIFEST.MF 里对相关依赖设置版本范围(比如 [1.0.0, 2.0.0)),再在插件启动时主动校验版本。实在不行,就在菜单入口处 try-catch 并给出中文提示“当前 ADT 版本不兼容”。
6.3 拿到 null 的选中对象或编辑器
handler 里通过 HandlerUtil.getCurrentSelection(event) 拿选择,可能拿到 null。这个 null 很容易误导人。后来我意识到,菜单触发时“当前焦点”并不一定在你以为的编辑器里。比如用户从某个视图点了右键,弹出的是视图的菜单,current selection 来自视图,不是编辑器。
稳妥做法是拿 active editor 再拿 editor input,或者用 ISelectionService 获取全局选择并判断类型,不要直接假设 selection 是当前 ABAP 对象。
6.4 后端调用失败:别忘了 ABAP 权限和连接上下文
插件在 ADT 里跑,不代表它就自动拥有后端访问权。调用 ADT 服务刷新远端对象时,连接走的是当前登录用户的会话。如果用户没有对应开发包或对象的显示权限,你的功能就会“没反应”或抛后端口组件异常。
这个权限问题往往等到部署后才发现,因为开发者自己一般都有超集权限。建议在功能设计阶段就把权限需求写清楚:哪个对象类型、需要哪种 Activity,并写进 README。
6.5 常见问题速查表
| 症状 | 最常见原因 | 处理方向 |
|---|---|---|
| 菜单没出现 | 插件未加载 / 表达式过滤错误 | 查 Plug-ins 视图是否加载;查 activeEditorId 值 |
| 点击菜单报 NoClassDefFoundError | ADT API 版本不匹配 | 打开错误日志定位缺失类,约束依赖版本 |
| 拿到 null 对象 | selection 来自错误上下文 | 改用 activeEditor 取 editor input |
| 后端调用无响应 | 用户权限不足 / 连接断开 | 检查 ADT 当前用户会话,看后端日志 |
| 图标不显示 | 路径写错或图标尺寸过大 | 检查 plugin.xml 里 icon 路径,用 16x16 PNG |
| 快捷键没反应 | 被其他命令占用 | Preferences → Keys 搜索并换组合键 |
7. 这个能力的影响范围与后续扩展空间
7.1 谁会从自定义 Action 里受益
我做完第一个 Action 之后,最直接的变化是团队里几个不太懂技术的测试同事也开始愿意用 ADT 做基础操作了,因为“多了一个按钮,按下就行”。工具的价值往往不是替高手省时间,而是拉低新手的参与门槛。
潜在受益人群大致有三类:每天高频操作的 ABAP 开发工程师;需要给特定项目定制开发规范的架构师;以及经常在 ADT 里做诊断和重复配置的 Basis。只要这部分人愿意花一周时间学会写插件,后面就能持续沉淀一整套内部效率工具。
7.2 值得继续扩展的几个方向
我目前已经在尝试把 Action 做成了“自带配置面板”的工具,而不是写死逻辑。比如注释头模板的字段顺序、是否包含操作人,都做成工作台首选项,这样每个开发者可以按自己的习惯配置,团队规范依然能在默认值层面把控。
这个思路扩展下去,至少还有这几个方向可以做:
- 把 Action 绑定成代码模板生成器,一键插入 CDS 或 ABAP 类的标准代码骨架;
- 结合本地静态解析,做个“当前对象的引用列表”右键菜单,快速跳转引用位置;
- 把常用的传输请求号记录到一个本地历史文件,在下一次请求说明里自动补全。
7.3 收手建议:别把插件做成大杂烩
自定义 Action 是一把好用的刀,但不代表所有功能都该削成插件往里塞。我见过有人把整个代码生成框架都搬进 ADT 插件里的,最后光启动加载就要几十秒,同事们怨声载道。
以我的经验,一个插件保持三五个以内的高频 Action 最理想,超过十个就应该拆分成独立插件,各自解决一类问题。存量的功能优于新增的功能,稳定性优于丰富度。给 ADT 做工具,第一原则是别破坏别人的 IDE,第二原则才是效率提升。
最后再分享一个小技巧:开发过程中多利用 Eclipse 自带的插件间谍视图(Plug-in Spy),在目标菜单位置按下快捷键,它能直接告诉你当前这个菜单项是由哪个插件、哪个扩展点贡献的,排查菜单不出现和重复菜单的问题效率极高。我自己写插件后做的最频繁的一个动作,就是用插件间谍去偷看 ADT 自带的菜单到底是怎么组织的——看了几遍以后,很多操作模式的疑问都消失了。自定义 ADT Action 这件事,只要你理解了它是“在成熟平台上做有序插件”,剩下的其实都是水磨工夫。