代码之家  ›  专栏  ›  技术社区  ›  Thorbjørn Ravn Andersen

如何用焊接法方便地注入管柱常数?

  •  15
  • Thorbjørn Ravn Andersen  · 技术社区  · 14 年前

    在这种情况下,我们以映射到正在运行的程序的形式提供外部配置。我发现JSR-330依赖注入提供了一种更清晰的方法来在代码中使用配置映射,而不是传递映射或使用JNDI来获取它。

    @Inject @Named("server.username") String username;
    

    用Guice我可以用

    bindConstant().annotatedWith(Names.named(key)).to(value);
    

    我希望在Weld中也能做到这一点(将“server.username”绑定到例如“foobar”),并且我知道最可能的机制是beans.xml,但是我更喜欢简单的“feed this map to Weld,please”代码替代方案。做这件事的好方法是什么?


    @Provider 方法,然后在配置映射中查找每个配置字符串。这允许特定于方法的行为(包括默认值)、提供javadoc的能力以及将所有这些方法放在同一个类中的能力。同时,它也适用于开箱焊接。我正在考虑在博客上写一个更完整的解释。

    5 回复  |  直到 11 年前
        1
  •  12
  •   SplinterReality    14 年前

    我现在想要那笔赏金。弄清楚这一点让我对焊接的内部有了不少了解,这里有一个最有趣的教训:@Named是一个限定符,如果你想与之匹配,就必须这样对待它。

    我确实有一个警告:如果你的应用程序中缺少任何值,它将在部署或加载时失败。这对您可能是可取的,但它确实意味着“默认”值是不可能的。

    注入点的指定与上述完全相同,下面是使其工作所需的扩展代码:

    @ApplicationScoped
    public class PerformSetup implements Extension {
    
        Map<String, String> configMap;
    
        public PerformSetup() {
            configMap = new HashMap<String, String>();
            // This is a dummy initialization, do something constructive here
            configMap.put("string.value", "This is a test value");
        }
    
        // Add the ConfigMap values to the global bean scope
        void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
            // Loop through each entry registering the strings.
            for (Entry<String, String> configEntry : configMap.entrySet()) {
                final String configKey = configEntry.getKey();
                final String configValue = configEntry.getValue();
    
                AnnotatedType<String> at = bm.createAnnotatedType(String.class);
                final InjectionTarget<String> it = bm.createInjectionTarget(at);
    
                /**
                 * All of this is necessary so WELD knows where to find the string,
                 * what it's named, and what scope (singleton) it is.
                 */ 
                Bean<String> si = new Bean<String>() {
    
                    public Set<Type> getTypes() {
                        Set<Type> types = new HashSet<Type>();
                        types.add(String.class);
                        types.add(Object.class);
                        return types;
                    }
    
                    public Set<Annotation> getQualifiers() {
                        Set<Annotation> qualifiers = new HashSet<Annotation>();
                        qualifiers.add(new NamedAnnotationImpl(configKey));
                        return qualifiers;
    
                    }
    
                    public Class<? extends Annotation> getScope() {
                        return Singleton.class;
                    }
    
                    public String getName() {
                        return configKey;
                    }
    
                    public Set<Class<? extends Annotation>> getStereotypes() {
                        return Collections.EMPTY_SET;
                    }
    
                    public Class<?> getBeanClass() {
                        return String.class;
                    }
    
                    public boolean isAlternative() {
                        return false;
                    }
    
                    public boolean isNullable() {
                        return false;
                    }
    
                    public Set<InjectionPoint> getInjectionPoints() {
                        return it.getInjectionPoints();
                    }
    
                    @Override
                    public String create(CreationalContext<String> ctx) {
                        return configValue;
    
                    }
    
                    @Override
                    public void destroy(String instance,
                            CreationalContext<String> ctx) {
                        // Strings can't be destroyed, so don't do anything
                    }
                };
                abd.addBean(si);
            }
        }
    
        /**
         * This is just so we can create a @Named annotation at runtime.
         */
        class NamedAnnotationImpl extends AnnotationLiteral<Named> implements Named {
            final String nameValue;
    
            NamedAnnotationImpl(String nameValue) {
                this.nameValue = nameValue;
            }
    
            public String value() {
                return nameValue;
            }
    
        }
    }
    

    @ApplicationScoped
    public class App {
    
        @Inject
        @Parameters
        List<String> parameters;
    
        @Inject
        @Named("string.value")
        String stringValue;
    
        public void printHello(@Observes ContainerInitialized event) {
            System.out.println("String Value is " + stringValue);
        }
    
    }
    

    最后,不要忘记/META-INF/services/javax.enterprise.inject.spi.Extension,将weldtest替换为您使用的类路径:

    weldtest.PerformSetup
    

    这应该能让这一切顺利。如果你遇到任何困难,请告诉我,我会把我的测试项目发给你。

        2
  •  11
  •   SplinterReality    14 年前

    对赏金不太感兴趣,但如果还摆在桌上的话,我会接受的。这与我在$DAYJOB上使用的一些代码非常相似,所以这不是理论,而是我在生产代码中使用的代码,但是为了保护罪犯而进行了修改。我还没有尝试编译修改过的代码,所以请注意,我可能在更改名称等方面犯了一些错误,但是这里涉及的原则都经过了测试并有效。

    首先,您需要一个值持有者限定符。使用@Nonbinding可防止WELD只与具有相同值的限定符匹配,因为我们希望此特定限定符的所有值都与单个注入点匹配。通过将限定符和值保持在同一个注释中,您不能意外地“忘记”其中一个。(接吻原则)

    @Qualifier
    @Retention(RUNTIME)
    @Target({METHOD, FIELD, PARAMETER, TYPE})
    public @interface ConfigValue {
        // Excludes this value from being considered for injection point matching
        @Nonbinding 
        // Avoid specifying a default value, since it can encourage programmer error.
        // We WANT a value every time.
        String value();
    }
    

    接下来,需要一个知道如何获取地图的生产者方法。您可能应该有一个包含producer方法的命名bean,这样您就可以使用getter/setters显式地初始化该值,或者让bean为您初始化它。

    @Named
    public class ConfigProducer {
        //@Inject // Initialize this parameter somehow
        Map<String,String> configurationMap;
    
        @PostConstructor
        public void doInit() {
             // TODO: Get the configuration map here if it needs explicit initialization
        }
    
        // In general, I would discourage using this method, since it can be difficult to control exactly the order in which beans initialize at runtime.
        public void setConfigurationMap(Map<String,String> configurationMap) {
            this.configurationMap = configurationMap;
        }
    
        @Produces
        @ConfigValue("")
        @Dependent
        public String configValueProducer(InjectionPoint ip) {
            // We know this annotation WILL be present as WELD won't call us otherwise, so no null checking is required.
            ConfigValue configValue = ip.getAnnotated().getAnnotation(ConfigValue.class);
            // This could potentially return a null, so the function is annotated @Dependent to avoid a WELD error.
            return configurationMap.get(configValue.value());
        }
    }
    

    用法很简单:

    @Inject
    @ConfigValue("some.map.key.here")
    String someConfigValue;
    
        3
  •  0
  •   covener    14 年前

    可能可以将其实现为一个@Dependent Producer方法,该方法本身会注入一个@InjectionPoint,这将允许您对要注入的字段进行反思——这将允许您查看字段上的一个自定义注释(而不是限定符)成员,以找出要返回的值

    @Inject @ConfigMapQualifier @Val("user.name") String user;
    
    ...
    
    @Produces @ConfigMapQualifier configProducr(...) { 
    ...
    @Inject InjectionPoint ip;
    
    // use e.g. ip/getJavaMember() then reflection to figure out the @Val value membr.
    
        4
  •  0
  •   Thorbjørn Ravn Andersen    14 年前

    将实现自定义焊接 InjectionServices 不是这里的选择吗?

        5
  •  0
  •   Thorbjørn Ravn Andersen    14 年前

    @Resource(name = "server.username", type = java.lang.String.class)
    private String injectTo;
    

    Javadoc公司: http://download.oracle.com/javase/6/docs/api/javax/annotation/Resource.html