资讯详情

资讯详情

为ABAP开发工具(ADT)打造自定义右键菜单Action:Eclipse插件开发实战

做了几年ABAP开发总有几个动作让人抓狂新建类要重复敲注释模板想给选中方法生成一段调用记录还得打开一个独立工具手工粘贴。于是我盯上了日常工作的IDE——ABAP Development ToolsADT准备给它加一个属于自己的IDE Action。这篇东西不聊空泛概念就讲我怎么把一个自定义Action从Eclipse插件工程开始一步步做出来最终让它出现在右键菜单里选中ABAP对象后能正确执行。适合每天被重复劳动折腾、又想亲手改造IDE的ABAP开发者如果你只是想把某个操作变成快捷键或菜单项也可以照这条路径走。1. 为什么要给ADT塞一个自己的IDE Action痛点与选型思考1.1 重复上百次的操作不应该靠手工硬扛ABAP开发里真正折磨人的往往不是复杂业务逻辑而是那些“没有标准入口”的机械操作。比如给一个类生成方法清单注释给一组方法写调用日志或者把接口实现类的空方法批量补齐。ADT的右键菜单里提供了很多功能但长尾需求永远轮不到官方一个一个做。我在实际项目里最常遇到的是开了一堆新类每个类的方法结构都差不多需要为它们生成统一的测试入口。标准ADT确实可以打开对象、看属性、看方法但“按照当前类生成一段ABAP代码”这种事IDE没有提供。手工复制粘贴又容易漏字段尤其是方法多的时候错一个签名就得回头改一遍。这种场景就是IDE Action的用武之地。IDE Action说白了就是把“我想在IDE里做某件事”这个意图变成一个真正可点击的菜单项、可触发的命令、可绑定的快捷键。你在ADT里选中一个类节点右键一点你自己的逻辑就跑了然后结果直接出现在编辑器或状态栏里。它就像给IDE装了一个私人的宏按钮但这个按钮能理解ABAP上下文比普通宏灵活得多。1.2 为什么是Eclipse插件而不是外部脚本在动手之前我其实纠结过几条路。外部脚本Python或者Shell可以用它打开ABAP编辑器、模拟点击、抓取文本但这条路有个致命问题脚本活在IDE外面拿不到用户当前“选中了什么”这个上下文。你想对一个类生成代码必须先在脚本里重新定位这个类还要处理ABAP系统连接、登录态、窗口焦点任何一环变了脚本就崩。ABAP端的自定义报表也能生成代码但那是从后端库里读对象同样不知道你此刻在ADT里选中的是哪个方法、哪个光标位置。它更适合批量离线处理不适合“选中即用”的交互方式。回头看Eclipse插件路线就顺理成章了。ADT本身基于EclipseEclipse暴露了一整套命令、菜单、选择模型、视图机制。插件可以监听用户的Selection可以直接用ADT已经建立的连接去访问后端元数据还能把结果写进编辑器、剪贴板、状态栏。本质上就是在一个已经登录、已经打开ABAP项目的IDE里做事很多基础设施不需要重造。1.3 先把Action、Command、Handler三个名词掰扯清楚Eclipse发展过程中有两套“Action”相关概念。老教程里会看到IAction、ObjectActionDelegate那是Eclipse 3.x早期的方案。新版本更推荐Command和Handler架构ADT也是基于新平台工作所以我们实际上要做的不是写一个Action类而是定义一个Command、注册一个Handler、把它贡献到菜单里。它们的关系可以类比成餐厅点餐你心里的“要一杯热美式”是Action菜单上印着的“咖啡-编号01”是Command后厨里真正做咖啡的师傅是Handler菜单页就是MenuContribution。用户点击CommandEclipse找到对应的Handler并执行一个Command可以换不同Handler也可以同时绑定到右键菜单、工具栏和快捷键都不用重复写业务逻辑。这块想清楚很重要。我见过同事直接把业务代码写进Action类结果想换菜单位置或者加快捷键时就得复制一份逻辑非常痛苦。用CommandHandler方式业务逻辑只活在Handler里菜单和快捷键只是两个入口。2. 先把开发环境搭成能干活的样子ADT插件开发环境准备2.1 认清ADT的真身一套Eclipse插件集合很多人以为ADT是个独立IDE其实它是在Eclipse平台之上叠加的一组插件。只不过安装包里已经把Eclipse内核、ABAP连接、对象模型、编辑器都整合好了。所以“给ADT写插件”和“给Eclipse写插件”在技术上是同一件事只是需要额外依赖ADT暴露的API。因此准备工作分成两块一是让当前Eclipse具备插件开发能力也就是安装PDEPlug-in Development Environment二是确保ADT插件出现在开发环境的Target Platform里否则代码里引用IAdtObjectReference这类ADT核心接口时会直接编译失败。如果你平时用官方ADT安装包装环境多半已经有一个能跑ABAP项目的Eclipse。在此基础上通过Help菜单的Install New Software补上Eclipse PDE插件开发套件就行不需要把整个Eclipse重装一遍。2.2 版本搭配JDK、Eclipse与Target Platform版本匹配是这类插件开发最容易忽略的坑。我使用的组合是较新的Eclipse 2024系列搭配JDK 17开发机上的ABAP后端连接正常。JDK版本过低会导致新版本Eclipse无法启动过高又可能被某些插件拒绝。个人建议JDK 17起步如果你的ADT版本较旧至少要满足其自带的最低JDK要求。之后去打开Preferences里的Target Platform配置。最简单的方式是勾选“Running Platform”让它使用当前Eclipse运行环境里已经加载的插件。这样做有一个前提当前Eclipse本身必须已经安装了ADT。如果Target Platform里没有ADT的插件列表后面所有依赖都会是空的。我用的是2024系列EclipseTarget Platform直接指向当前运行实例省去了单独维护Target Definition的麻烦。如果你需要同时兼容多个Eclipse版本再考虑建一个独立的Target Definition文件把Eclipse Platform、PDE、ADT插件打包进去不然版本一变ADT API也跟着变菜单表达式、编辑器ID都可能对不上。2.3 新建Plug-in Project时的几个关键项环境准备好后在Eclipse里选择File New Other Plug-in Project。工程名我用的是com.example.adt.action这个ID要全局唯一后续Command ID、Handler类名都建立在它基础上。模板选空插件工程就行不要选那些带视图、带编辑器的模板一上来全是无关代码。这里有一个关键点这个插件工程跟你的ABAP传输请求没有任何关系它属于Eclipse本地工程不需要绑定ABAP包。它最后产出的是一个插件JAR放进Eclipse里就能被加载。建完工程后打开MANIFEST.MF的Dependencies页至少添加这些依赖org.eclipse.ui、org.eclipse.core.runtime、org.eclipse.core.resources。如果Handler里要操作ADT对象模型还需要导入com.sap.adt.tools.core.model.adtcore这类ADT依赖。依赖不加入编译期是看不到接口的。2.4 第一轮“空跑”验证插件被Eclipse加载在写具体Action之前我建议先跑一个最小插件验证加载链路。在plugin.xml中随便注册一个空的Command和Handler然后使用Run Run Configurations Eclipse Application启动一个Eclipse实例。如果配置准确这个新实例里会同时带有Eclipse、ADT和你的插件。验证加载有两个简单办法一是在Handler的execute方法里写一行业务日志二是在插件自己的启动Bundle Activator里打点。我习惯在Activator的start方法中输出plugin loaded日志出现在Eclipse控制台后说明插件已经被OSGi装载后面每一步排错会轻松很多。容易踩的小坑是启动新实例时没有加载ADT导致你在里面根本没有ABAP项目。这时要回头检查Target Platform里是否包含ADT插件或者启动配置的“Plug-ins From Target Platform”是否选中了ADT相关包。3. 让Action出现在右键菜单里plugin.xml命令注册与菜单贡献3.1 注册Command并指定Handler插件的大部分声明都在plugin.xml里。先定义一个Command给一个唯一ID和一个面向用户的名称再定义对应的Handler并指向你自己的Java类。我做的例子是“生成方法调用日志”逻辑是选中ABAP类后读取类名和方法名生成一段测试程序。下面是一份精简的plugin.xmlextension pointorg.eclipse.ui.commands command idcom.example.adt.action.generateMethodLog nameGenerate Method Log /command /extension extension pointorg.eclipse.ui.handlers handler commandIdcom.example.adt.action.generateMethodLog classcom.example.adt.action.handlers.GenerateMethodLogHandler /handler /extensionHandler类要实现org.eclipse.core.commands.AbstractHandler并重写execute方法。这里千万别用老式的IAction接口虽然Eclipse兼容层还留着但新版命令框架已经不走那条路了。Handler是延迟加载的用户第一次点击命令时Eclipse才会实例化这个类所以平时不占资源。3.2 用menuContribution把Command挂到右键菜单Command定义好了不挂在任何菜单上用户是找不到它的。这一步通过org.eclipse.ui.menus扩展点完成。ADT里的ABAP项目树通常显示在Project Explorer里所以我的菜单贡献目标就指向Project Explorer的弹出菜单extension pointorg.eclipse.ui.menus menuContribution locationURIpopup:org.eclipse.ui.navigator.ProjectExplorer?afteradditions command commandIdcom.example.adt.action.generateMethodLog label生成方法调用日志... iconicons/method_log.png /command /menuContribution /extensionpopup:后面的ID代表目标视图?afteradditions表示把菜单项插在“添加”区之后这是最通用的位置。如果ABAP项目在你的Eclipse里显示在别的视图只需要把视图ID替换掉或者针对多个视图各写一条menuContribution。3.3 图标与菜单文案别让右键菜单看起来像开发测试版菜单项的文字最好用完整的动词短语比如“生成方法调用日志...”不要用Action1这种占位名。图标方面往插件工程里建一个icons目录放16x16和32x32的PNGplugin.xml里直接写相对路径。没有图标也能跑但右键菜单里一堆无图标文字时间长了根本分不清是系统功能还是你塞进去的调试项。如果希望这个菜单项只在选中ABAP对象时出现还要加一个visibleWhen表达式。核心思路是检查当前Selection能否适配成IAdtObjectReferencevisibleWhen with variableselection iterate ifEmptyfalse adapt typecom.sap.adt.tools.core.model.adtcore.IAdtObjectReference/ /iterate /with /visibleWhen这个表达式在实际版本里可能需要调整类型名但思想是一样的只有选中项能满足适配条件时菜单项才会显示。否则你会看到右键一个普通文件也出现“生成方法调用日志”体验很怪。3.4 为什么用menus扩展点而不是老的popupMenus在查资料的时候你大概率会看到很多org.eclipse.ui.popupMenus的旧代码。那个扩展点有它的历史价值但现在的Eclipse和ADT都推荐使用org.eclipse.ui.menus。新方案的好处是命令与菜单分离同一个Command可以挂在右键菜单、主菜单、工具栏、快捷键下并且通过表达式控制可见性。我最早照着旧教程写过一个popupMenus的例子当时确实能跑但后来想增加一个工具栏入口发现要么复制ActionDelegate要么绕一大圈。换成menus扩展点后只需要在新的menuContribution里引用同一个Command IDHandler逻辑原封不动。如果你的插件打算长期维护强烈建议直接走新架构。4. 打一个真实场景Handler从选中ABAP对象拿数据并生成代码4.1 从Eclipse Selection里取到ABAP类菜单能弹出来只是第一步Handler要做的第一件事是拿到用户选中的对象。AbstractHandler的execute方法会收到一个ExecutionEvent里面包裹着当前Eclipse上下文。我习惯用HandlerUtil.getCurrentSelection(event)取出ISelection再判断是不是IStructuredSelectionpublic Object execute(ExecutionEvent event) throws ExecutionException { ISelection selection HandlerUtil.getCurrentSelection(event); if (!(selection instanceof IStructuredSelection)) { return null; } IStructuredSelection ss (IStructuredSelection) selection; Object first ss.getFirstElement(); IAdtObjectReference ref null; if (first instanceof IAdtObjectReference) { ref (IAdtObjectReference) first; } else if (first instanceof IAdaptable) { ref (IAdtObjectReference) ((IAdaptable) first).getAdapter(IAdtObjectReference.class); } if (ref null) { MessageDialog.openError(Display.getCurrent().getActiveShell(), 生成失败, 当前选中的节点不是可识别的ABAP对象。); return null; } return doGenerate(ref); }这段代码的关键是适配器转换。ABAP项目树里的节点可能是IResource、专用导航器节点、或者ADT对象引用直接强转常会失败用IAdaptable适配器转换是通用做法。拿到的IAdtObjectReference里有对象的URI、名称、类型足够后续操作使用。4.2 不重新发明轮子读取类方法列表的两种途径拿到IAdtObjectReference之后需要读取当前类的结构信息。ADT提供两种途径一是选中节点本身如果是类节点直接通过模型接口拿它的方法列表二是通过对象URI请求后端元数据。第一种方式对用户最友好因为用户选中的就是类节点ADT已经把这棵对象树展开在你面前。你可以遍历ADT内部表示的子节点从中找到类型为Method的节点。它的优点是快、不需要额外后端调用缺点是你依赖的接口比较内部化版本升级时需要同步跟进。第二种方式更通用取到ref.getUri()后利用ADT的通信框架发起一个请求读取类的完整元数据。这种方式更接近“程序化访问后端”适用面更广但要处理异步、异常、连接状态代码量明显增加。我个人的建议是先用第一种方式跑通后续有必要再迁移到第二种不要一开始就把所有复杂度都背在身上。这里不贴具体内部API名字因为ADT版本更新速度不慢硬编码一个具体类名反而容易误导。核心思路是拿到引用后要么读取节点模型要么请求URI二者都是通过ADT已经建好的ABAP连接完成不需要自己再登录一次。4.3 生成内容并回写剪贴板、状态栏还是直接开编辑器Handler拿到类名和方法列表后剩下的任务是生成ABAP代码并把结果给用户。这部分我试过三种交互体验差异很大。第一种是把生成结果放进系统剪贴板用户自己去找地方粘贴。代码很少但用户很容易误操作覆盖剪贴板里的其他内容而且无法确认生成结果是否正确。第二种是在状态栏展示一句话只适合极简单的结果比如“已复制类名”不适合整段代码。第三种是直接把生成的ABAP文本打开到一个新的源文件编辑器里。这是我最推荐的方式用户能立刻看到、编辑、保存真正觉得“这个Action是IDE原生的一部分”。打开编辑器需要指定编辑器IDADT自己的ABAP源文件编辑器ID是com.sap.adt.tools.core.ui.sourcePage不同版本可能略有区别实际操作时先手动打开一个ABAP程序再到Window Preferences General Editors File Associations里查一下当前关联的编辑器ID。伪代码大概是IWorkbenchPage page PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); IEditorPart editor page.openEditor( new MyGeneratedFileEditorInput(generatedContent), com.sap.adt.tools.core.ui.sourcePage);这里的MyGeneratedFileEditorInput可以是一个实现了IEditorInput的轻量类把生成的文本存进去。为了不引入额外的文件管理复杂度也可以直接创建一个临时文本文件再打开但这样做会在工作空间里留下垃圾文件我后来改成了纯内存输入效果更干净。4.4 异步、进度条和错误处理Handler里如果只做内存拼接是瞬间完成的。但一旦要请求后端元数据就可能耗时几百毫秒甚至几秒。直接在UI线程里执行会卡住IDE用户会以为Eclipse崩溃了。要习惯把耗时操作包进IRunnableWithProgress或busyCursorWhile里并配上取消机制。我的做法是Handler先启动一个ProgressMonitorDialog后台线程里读取类结构读取完成后在UI线程打开编辑器。同步回UI线程要小心别在非UI线程里直接操作Display、Shell、IWorkbenchPage否则会抛出Invalid thread access异常。错误处理同样重要。ADT访问后端时最常见的异常是连接断了、权限不足、对象不存在。接收到这些异常后建议写进Eclipse的.metadata/.log日志并弹出一个简洁的对话框。不要只printStackTrace因为普通用户看不到控制台也不要吞掉异常否则以后排错就像大海捞针。5. 跑通之后的那些坑调试、部署与热更新5.1 开发期调试Run As Eclipse Application的隐藏问题插件开发期调试和普通Java调试差距不大F6、F5都能用但有几个隐藏问题让人头大。第一个是启动出来的新Eclipse实例可能和当前开发实例的ABAP连接配置不一致导致无法在测试实例里看到同一个ABAP项目。解决办法是先在新实例里添加ABAP项目或者直接把工作区指向一个已有的开发工作区。第二个问题是plugin.xml修改后经常需要重启测试实例才能看到变化。菜单贡献的解析发生在Eclipse启动阶段很多情况下热预览并不生效。我的一般步骤是改完插件源码后点击Run按钮让Eclipse自动增量构建并重启新的Eclipse Application。如果某些改动没有加载可以加上-clean启动参数强制清理OSGi缓存。另外调试时不要让测试实例和开发实例共用一个ABAP项目工作区容易发生文件锁冲突。我通常把测试实例的工作区放在一个独立目录每次启动时会自动创建。5.2 打包安装导出jar放到dropins还是plugins开发调试跑通之后插件终究要被装进正式ADT里使用。Eclipse支持导出可部署插件右键插件工程选择Export Deployable plug-ins and fragments生成一个JAR。这个JAR的安装位置我建议放在Eclipse根目录下的dropins目录而不是直接塞进plugins目录。dropins的好处是Eclipse启动时自动扫描并加载不会跟系统自带插件混淆以后想卸载直接把JAR删掉即可。放到plugins目录虽然也能加载但容易造成插件列表混乱。安装后重启Eclipse理想情况下菜单项就出现了。如果没出现用-clean参数启动一次强制清理缓存。没有签名也没关系内部开发环境使用不受影响。如果要跨机器部署直接把JAR发给同事让它们放到对应Eclipse安装目录的dropins里。5.3 常见问题排查表我把实际运行中遇到的高频问题整理成了一张表排查时对照着看可以少走很多弯路。现象常见原因解决办法右键菜单里没有新Action插件没被加载或locationURI的视图ID不对检查Error Log确认插件是否激活调整popup:后面的视图ID菜单项有但点击没反应commandId与handler的commandId不一致检查plugin.xml里的ID是否完全一致注意大小写编译报找不到IAdtObjectReference缺少ADT相关依赖在MANIFEST.MF的Dependencies页添加ADT依赖包测试实例里没有ABAP项目Target Platform未包含ADT勾选Running Platform并确保当前Eclipse已安装ADT快捷键冲突按了没效果与系统或其他插件绑定冲突修改org.eclipse.ui.bindings里的绑定组合修改plugin.xml后总是旧效果OSGi缓存未刷新使用-clean启动参数或删除测试工作区里的.metadata这张表也回答了一个核心问题IDE Action跑不通绝大多数不是Handler逻辑问题而是插件描述文件、依赖和Target Platform之间的连接问题。先看日志再查ID命中率很高。5.4 完整调用链从右键到代码执行的一次遍历最后梳理一下整个链路方便你对照自己的实现。用户在弹出的ABAP项目树里选中一个类节点右键触发Project Explorer的popup菜单。Eclipse构建菜单时读取org.eclipse.ui.menus扩展点结合visibleWhen表达式判断是否需要显示“生成方法调用日志”菜单项。用户点击菜单项Eclipse根据Command ID找到Handler注册。如果Handler类还未加载OSGi会延迟实例化该类。AbstractHandler.execute(ExecutionEvent)被调用。Handler内部通过HandlerUtil.getCurrentSelection取出当前选中对象。通过IAdaptable适配器拿到IAdtObjectReference得到ABAP类URI和类型信息。Handler读取类的方法列表拼接生成ABAP测试程序文本。在UI线程中打开ADT源文件编辑器把生成文本填充到编辑器输入里。Handler方法返回null表示没有额外的命令执行结果。这条链路里每一步都有明确的插件描述文件或Java类负责。你不需要理解Eclipse全部内部机制只要牢牢抓住Command ID这条线从plugin.xml穿到Handler再从Handler穿到用户界面就能拼出完整画面。在我实际使用中最深刻的体会是别在第一次做的时候就追求完美。先用最简单的命令把链路跑通哪怕Handler里只是弹出一个对话框显示“我收到了选中对象”也比纠结十天半个月、最后卡在某个API细节上强。等链路通了再逐步替换成真正的ABAP代码生成逻辑、加图标、绑快捷键、处理异步和错误整个开发过程会顺畅很多。IDE Action的魅力就在于此你不需要等官方适配每一个长尾需求自己动手几个文件就能把IDE变成趁手的工具。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →