Guice 注入基于注解值

Guice inject based on annotation value

我想使用 goolge/guice 根据我提供的注释 class 注入一个值。

AutoConfig 注释

@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER, ElementType.FIELD })
public @interface AutoConfig {
    // default null not possible
    Class<? extends Provider<? extends ConfigLoader<?>>> provider() default XMLAutoConfigProvider.class;
}

这是我的注释,它允许配置应该用于注释字段的配置类型。

用例:

@AutoConfig()
ConfigLoader<?> defaultConfig;

@AutoConfig(provider = JsonConfigProvider)
ConfigLoader<?> jsonConfig;

我想要两个配置,一个 default/xml 一个 json 一个。它们可能永远不会同时出现在同一个 class 中。但是我不知道什么时候使用一个或另一个。我使用了带有 class 的方法,因为它们是由某些 dependencies/libs 提供的,并且此注释将用于某些(可插入的)子模块。

MyGuiceModule

public class MyGuiceModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(new TypeLiteral<ConfigLoader<?>>() {})
            .annotatedWith(AutoConfig.class)
            .toProvider(autoConfig.provider());
    }
}

这是关键部分,我无法想象如何实现它。

所以基本上我只想使用注释中指定的提供程序 class。 也没有必要在这里使用提供程序 class。因为 autoConfig.provider().newInstance() 基本上是我所需要的。 (我需要在新实例上使用 setter 但这就是我想在这个地方做的所有事情)

总而言之,我真正想做的就是使用 get(AutoConfig autoConfig) 或在构造函数中将注释(或其值推送给提供者)。 目前我只使用构造函数注入我想在新生成的配置实例上设置的配置文件值。

如果您知道 @AutoConfig(provider = JsonConfigProvider) ConfigLoader<?> jsonConfig 将 return 您正是 jsonConfigProvider.get() 的结果,并且 JsonConfigProvider 显然有一个 public 无参数构造函数用于 newInstance 工作,你为什么不首先要求 JsonConfigProvider

从根本上说,Guice 只是一个 Map<Key, Provider> 的精美包装。坏消息是,这使得像 "bind Foo<T> for all T" 这样的变量绑定无法简洁地表达,这包括你的 "bind @Annotation(T) Foo for all T"。好消息是你还有两个选择。

分别绑定每个提供商

尽管您无法在提供期间检查注释(或告诉 Guice 为您这样做),但如果您绑定注释​​ instance[=,Guice 将使用其 equals 方法比较注释70=] 而不是注释 class(就像使用 Names.named("some-name") 那样)。这意味着您可以将 ConfigLoader<?> 与模块中的每个预期注释绑定。当然,这也意味着您必须在配置时有一个可用的 ConfigLoader Provider 列表,但是如果您将它们用作注释参数,它们无论如何都必须是编译时常量。

此解决方案也适用于构造函数注入,但对于字段,您将需要 @Inject@AutoConfig(...),并且 AutoConfig 将需要保留其 @BindingAnnotation 元注释。

为此,您将不得不编写注释的实现,就像 Guice 对 NamedImpl 所做的那样。请注意,equalshashCode 的实现必须与 java.lang.Annotation 中提供的 Java 相匹配。那么这只是一个(冗余)绑定的问题:

for(Class<ConfigLoader<?>> clazz : loaders) {
  bind(ConfigLoader.class).annotatedWith(new AutoConfigImpl(clazz))
      .toProvider(clazz);
}

equals 的定义由您决定,这意味着您可以(并且应该)绑定 @AutoConfig(ConfigEnum.JSON) 并将 Guice 绑定保留在您的模块中,而不是在整个代码库中指定您请求的实现.

使用自定义注入

您还可以使用 custom injections 来搜索您的注入类型以查找自定义注释,例如 @AutoConfig。此时,您将使用 Guice 作为平台 来解释 @AutoConfig 而不是 @Inject,这意味着该构造函数注入将不起作用,但您可以根据注入的实例、字段名称、字段注释、注释参数或其任意组合来控制您的注入。如果您选择这种风格,您可以从 AutoConfig 中删除 @BindingAnnotation

使用 the wiki article linked above 中的示例作为您的模板,但您至少需要:

  1. 在 Binder 或 AbstractModule 上使用 bindListener 来匹配需要此自定义注入的类型。
  2. 在您绑定的 TypeListener 中,搜索 @AutoConfig-annotated 字段的注入类型,如果它们有任何匹配的方法,则将这些匹配的方法绑定到 MembersInjector 或 InjectionListener。您可能希望从此处的注释实例中提取 class 文字,并将 Field 和 Class 作为构造函数参数传递给 MembersInjector/InjectionListener.
  3. 在你写的MembersInjector或InjectionListener中,实例化提供者并将字段设置为提供者提供的实例。

这是一个非常强大的功能,它进一步允许您——例如——根据您注入的实例或根据字段名称自动提供配置。但是,请谨慎使用它并大量记录它,因为对于您的同事来说,Guice 提供的注释不是 @Inject 可能会违反直觉。还要记住,这不适用于构造函数注入,因此从字段注入重构为构造函数注入将导致 Guice 抱怨它缺少实例化 class.

所需的绑定。

我遇到了类似的问题。我想使用接收枚举参数的自定义注释来选择实现。经过大量研究、调试和测试,我得出以下解决方案:

//enum to define authentication types
public enum AuthType {
    Ldap, Saml
}

//custom annotation to be used in injection
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@BindingAnnotation
public @interface Auth {
    AuthType value();
}

//defintion of authenticator
public interface Authenticator {
    public void doSomehting();
}


//Authenticator implementations 
public class LdapAuthenticator implements Authenticator {

    @Override
    public void doSomehting() {
        // doing ldap stuff
    }

}

public class SamlAuthenticator implements Authenticator {

    @Override
    public void doSomehting() {
        // doing saml stuff
    }

}

public class MyModule extends AbstractModule {

    // annotate fields to bind to implementations
    private @Auth(AuthType.Ldap) Authenticator ldap;
    private @Auth(AuthType.Saml) Authenticator saml;

    @Override
    protected void configure() {
        //bind the implementation to the annotation from field
        bindAnnotated("ldap", LdapAuthenticator.class);
        bindAnnotated("saml", SamlAuthenticator.class);

    }

    private void bindAnnotated(String fieldName, Class<? extends Authenticator> implementation) {
        try {
            //get the annotation from fields, then bind it to implementation                
            Annotation ann = MyModule.class.getDeclaredField(fieldName).getAnnotation(Auth.class);
            bind(Authenticator.class).annotatedWith(ann).to(implementation);
        } catch (NoSuchFieldException | SecurityException e) {
            throw new RuntimeException(e);
        }
    }
}


//usage:  add @Auth(<AuthType>) to the dependency

public class ClientClass {

    private Authenticator authenticator;

    @Inject
    public ClientClass(@Auth(AuthType.Ldap) Authenticator authenticator) {
        this.authenticator = authenticator;
    }
}

查看 Binder

的文档

我测试了 Jeff Bowman 解决方案,但它显然只能绑定到提供商