如何使用ServiceLoader获取@MenuProvider注解的所有实例
Java模块化下ServiceLoader加载重复注解的问题解决
需求背景
我正在开发一个支持附加模块扩展的模块化项目,需要允许第三方模块提供自定义菜单、菜单项和工具栏按钮。为此我先创建了名为Menu.API的公共模块,定义了相关注解API:
@MenuProvider 注解定义
package com.my.menu.api; @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.CLASS) @Repeatable(RepeatableMenus.class) public @interface MenuProvider { String name(); String text(); int position(); }
重复注解容器 @RepeatableMenus 定义
package com.my.menu.api; @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface RepeatableMenus { MenuProvider[] value(); }
Menu.API 模块声明
模块的module-info.java内容如下:
module Menu.API { requires java.base; // 仅为保证完整性 exports com.my.menu.api; }
问题出现
API开发完成后,我创建了第二个模块Menu.Platform,在启动类App上标注了多个@MenuProvider注解来声明系统默认菜单:
package com.my.platform; // 为简洁起见省略导入语句 @MenuProvider(name = "fileMenu", text = "文件", position = Integer.MIN_VALUE) @MenuProvider(name = "editMenu", text = "编辑", position = Integer.MIN_VALUE + 1) @MenuProvider(name = "toolsMenu", text = "工具", position = Integer.MAX_VALUE - 1000) @MenuProvider(name = "helpMenu", text = "帮助", position = Integer.MAX_VALUE) public class App { private final MainFrame mainFrame; private final JMenuBar menuBar; private final JToolBar toolBar; private final JPanel statusPanel; private final JLabel statusLabel; public static MenuProvider provider() { // 不知道怎么返回类上标注的4个@MenuProvider注解实例 } public App() { configureWindowParts(); } private void configureWindowParts() { // 窗口组件初始化逻辑无问题 } private void initialize(String[] args) { // 暂不需要使用args参数 createMenuBar(); // 其余初始化逻辑 } private void createMenuBar() { ServiceLoader<MenuProvider> menusLoader = ServiceLoader.load(MenuProvider.class); // PostionableMenu是JMenu的子类,实现了Comparable接口,支持按position参数排序 List<PositionableMenu> menus = new ArrayList<>(); for (MenuProvider p : menusLoader) { PositionableMenu menu = new PositionableMenu(p.name(), p.text(), p.position()); menus.add(menu); } Collections.sort(menus); menus.foreach(m -> { menuBar.add(m); }); } }
最初的Menu.Platform模块声明如下:
module Menu.Platform { requires java.desktop; requires Menu.API; uses com.my.menu.api.MenuProvider; uses com.my.menu.api.RepeatableMenus; export com.my.platform; provides com.my.menu.api.MenuProvider with com.my.platform.App; }
此时编译触发以下报错:
the service implementation type must be a subtype of the service interface type, or have a public static no-args method named "provider" returning the service implementation
如果不在App类中添加public static MenuProvider provider()方法就会触发该错误,但添加该方法后又遇到新的问题:通过反射App.class.getAnnotationsByType(MenuProvider.class)拿到的是注解数组,而provider方法仅支持返回单个实例,修改返回类型为数组会再次触发编译错误。
解决方案
Java的可重复注解本质是用容器注解承载所有注解实例,因此可以直接把容器注解RepeatableMenus作为服务类型,provider方法返回该容器注解实例即可拿到所有@MenuProvider注解。
第一步:修改provider方法
public static RepeatableMenus provider(){ RepeatableMenus provider = App.class.getAnnotation(RepeatableMenus.class); return provider; }
第二步:修改模块声明
调整module-info.java,将服务提供的类型改为RepeatableMenus:
module Menu.Platform { requires java.desktop; requires Menu.API; uses com.my.menu.api.MenuProvider; uses com.my.menu.api.RepeatableMenus; export com.my.platform; provides com.my.menu.api.RepeatableMenus with com.my.platform.App; }
修改完成后即可正常加载到类上标注的所有@MenuProvider实例,后续其他附加模块也可以复用这套逻辑扩展自定义菜单。
内容的提问来源于stack exchange,提问作者Sean Carrick
相关产品推荐
相关产品推荐

