You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用ServiceLoader获取@MenuProvider注解的所有实例

Java模块化下ServiceLoader加载重复注解的问题解决

需求背景

我正在开发一个支持附加模块扩展的模块化项目,需要允许第三方模块提供自定义菜单、菜单项和工具栏按钮。为此我先创建了名为Menu.API的公共模块,定义了相关注解API:

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();
}

模块的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 19:36:03