使用 ServiceLoader 获取注解的所有实例

Using ServiceLoader to get All Instances of an Annotation

全部!

我知道长问题不受欢迎,但这是我能够充分解释我的问题的唯一方法。所以,对于这个问题的长度,我预先表示歉意。

我正在开发一个可通过附加模块扩展的模块化项目。为此,我希望附加模块能够提供自己的菜单、菜单项和工具栏按钮。为此,我创建了一个 API 并在其中添加了一些注释。此 API 位于名为“Menu.API”的模块中,并定义了以下 classes:

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

为了简洁起见,我们将只关注这个注释,因为我相信其余的注释将适用于我能为这个注释获得的任何解决方案。

完成此模块后,我创建了包含以下内容的 module-info.java 文件:

module Menu.API {
    requires java.base;   // Just for the sake of completeness.

    exports com.my.menu.api;
}

因此,在 API 到位后,我创建了第二个名为“Menu.Platform”的模块,其中定义了以下 class:

package com.my.platform;

// Imports removed for brevity.

@MenuProvider(name = "fileMenu", text = "File", position = Integer.MIN_VALUE)
@MenuProvider(name = "editMenu", text = "Edit", position = Integer.MIN_VALUE + 1)
@MenuProvider(name = "toolsMenu", text = "Tools", position = Integer.MAX_VALUE - 1000)
@MenuProvider(name = "helpMenu", text = "Help", 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() {
        // I am not sure how to send back my @MenuProvider annotations.
    }

    public App() {
        configureWindowParts();
    }

    private void configureWindowParts() {
        // No problems here...
    }

    private void initialize(String[] args) {
        // I do not need the args[] variable yet.
        
        createMenuBar();
    
        // ... the rest of the initialization.
    }

    private void createMenuBar() {
        ServiceLoader<MenuProvider> menusLoader = ServiceLoader.load(MenuProvider.class);

        // PostionableMenu is a subclass of JMenu that implements Comparable and provides
        //+ the `position` parameter.
        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);
        });
    }
}

module-info.java:

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 class 中没有 public static MenuProvider provider() 方法时。但是当我将该方法放入我的 App class 时,我不知道如何从该方法中 return 那四 (4) 个 @MenuProvider

非常感谢能为我指明正确方向的任何帮助。提前谢谢大家!

-SC

[编辑] 好吧,我花了很长时间思考如何提出这个问题,以至于在发布后不久就得到了答案...

需要return从provider方法中编辑的内容是通过反射class获得的:

    public static MenuProvider provider(){
        MenuProvider[] provider = App.class.getAnnotationsByType(MenuProvider.class);
        
        // How to return an array? If I change the return type to an
        //+ array, I get errors again because the provider method only
        //+ wants to return a single instance.
    }

现在这是我的难题。因此,当答案像一堆砖头一样击中我时,我正在考虑如何编辑这个问题!我需要 return RepeatableMenus 实例,而不是 returning MenuProviderRepeatableMenus return 的 value 属性 是一个 MenuProvider 的数组。呸!

所以,我更新了 provider 方法如下:

    public static RepeatableMenus provider(){
        RepeatableMenus provider = App.class.getAnnotation(RepeatableMenus.class);
        
        return provider;
    }

而且,我将 module-info.java 文件更改为:

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

现在,我收到了来自 class 的所有 @MenuProvider 个实例。我迫不及待地想从另一个模块中尝试它...

无论如何,谢谢大家可能提供的帮助。

-SC

ServiceLoader 的文档通过一个很好的例子很好地解释了它是如何工作的。

首先,它不是为使用注释而设计的。 服务加载器应该return所有已注册类实现给定接口,而不是所有类都标有给定注释。 从技术上讲,您可以创建 类 来实现您的注释,但这完全违反直觉,并且不会帮助您找到所有标记为 类.

其次,如果您注册的服务实现没有无参数构造函数,或者如果它没有实现服务接口,它必须有一个静态 provider() 方法,return服务接口的实例。 这意味着 您只能 return 单个实例 而不是列表。如果你想有多个服务,你必须有多个 类.

如果你想找到所有 类 标记有你当前设计的给定注释,你将需要一个所谓的类路径扫描器。 例如,ClassGraph

当然,如果您使用的是像 Spring 这样的框架,您应该使用他们的发现工具和他们的注释,如 @Service 或 @Component,而不是重新发明轮子。在这种情况下称为上下文扫描。 鉴于你的问题,我假设你没有使用这样的框架。