使用annotation feed Google guice MapBinder

Use annotation to feed Google guice MapBinder

在 Java 项目中,使用 Gradle 5.2 构建,使用 Google Guice。

我使用 MapBinder (http://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/multibindings/MapBinder.html):

MapBinder<String, Snack> mapbinder
         = MapBinder.newMapBinder(binder(), String.class, Snack.class);
     mapbinder.addBinding("twix").toInstance(new Twix());
     mapbinder.addBinding("snickers").toProvider(SnickersProvider.class);
     mapbinder.addBinding("skittles").to(Skittles.class);

这工作正常,但现在,我想要一个 "plugin architecture",所以避免导入所有 Snack class,而是直接在 class 中声明它,例如:

@SnackImpl("Twix")
class Twix extends Snack {

}

如何?

如果没有一些昂贵的class路径扫描,这将是不可能的:如果注入器没有对您的 Twix class 的任何引用,它将无法将其绑定到一个地图,无需扫描 class 路径上的每个 JAR 以搜索 @SnackImpl-注释的 classes。您可以使用 Guava's ClassPath 尝试此操作,但如果您使用基于网络的或自定义的 classloader,这可能根本无法处理。无论如何我都不会推荐它。

另一种方法是使用 Java 基于注释的内置 ServiceLoader framework, which lets individual JARs list out the fully-qualified implementations for a given service (interface). You can even use Google's Auto framework to generate that service file for you

这会列出实现,但您仍需要将它们绑定到 MapBinder 中。幸运的是,MapBinder 不需要单个定义,并且会在模块构建时自动合并多个 MapBinder 定义:

Contributing mapbindings from different modules is supported. For example, it is okay to have both CandyModule and ChipsModule both create their own MapBinder, and to each contribute bindings to the snacks map. When that map is injected, it will contain entries from both modules.

(来自MapBinder docs

考虑到这一点,我建议每个插件包都有自己的 Guice 模块,并在其中注册到 MapBinder,然后使用 ServiceLoader 将这些 Guice 模块添加到主注入器,以便在注入器创建时获取这些模块.

// Assume CandyPluginModule extends AbstractModule

@AutoService(CandyPluginModule.class)
public TwixPluginModule extends CandyPluginModule {
  @Override public void configure() {
    MapBinder<String, Snack> mapBinder
       = MapBinder.newMapBinder(binder(), String.class, Snack.class);
    mapBinder.addBinding("twix").to(Twix.class);
  }
}

你也可以利用超级class:

@AutoService(CandyPluginModule.class)
public TwixPluginModule extends CandyPluginModule {
  @Override public void configureSnacks() {  // defined on CandyPluginModule
    bindSnack("twix").to(Twix.class);
  }
}

或者,您可以直接使用 AutoService 列出 Twix 等实现,然后创建一个模块,将所有 ServiceLoader 实现读取到您的 MapBinder 中,但这可能会限制您的插件的灵活性,并且不会让您获得任何去中心化MapBinder 尚未提供的绑定。