【问题标题】:Guice inject based on annotation value基于注解值的Guice注入
【发布时间】:2015-04-17 10:38:28
【问题描述】:

我想使用 goolge/guice 基于我提供的注释类注入一个值。

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 。它们可能永远不会同时出现在同一个班级中。但我不知道何时使用其中一个。我在一个类中使用了这种方法,因为它们是由一些依赖项/库提供的,这个注解将用于一些(可插入的)子模块。

我的Guice模块

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

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

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

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

【问题讨论】:

  • 我想这个问题有解决方案,但我现在手头没有。您可以考虑是否真的需要一个具有两种可能行为的限定符,或者您是否只使用 XmlConfig 和 JSonConfig 注释并显式绑定。

标签: java annotations guice inject


【解决方案1】:

如果你知道@AutoConfig(provider = JsonConfigProvider) ConfigLoader&lt;?&gt; jsonConfig 将准确地返回jsonConfigProvider.get() 的结果,并且JsonConfigProvider 显然有一个公共无参数构造函数供newInstance 工作,你为什么不直接要求JsonConfigProvider首先?

从根本上说,Guice 只是一个带有精美包装的Map&lt;Key, Provider&gt;。坏消息是,这使得像“bind Foo&lt;T&gt; for all T”这样的变量绑定无法简洁地表达,这包括你的“bind @Annotation(T) Foo for all T”。好消息是您仍有两种选择。

分别绑定每个提供者

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

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

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

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。您可能希望在此处从注释实例中提取类文字,并将 Field 和 Class 作为构造函数参数传递给 MembersInjector/InjectionListener。
  3. 在您编写的 MembersInjector 或 InjectionListener 中,实例化提供程序并将字段设置为提供程序提供的实例。

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

【讨论】:

    【解决方案2】:

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

    //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 解决方案,但它显然只能绑定到提供程序

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-02
      • 2012-08-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多