16

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

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

让 JSR-330 实现自动填写此字段。

使用 Guice,我可以设置值

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

我希望能够在 Weld 中做同样的事情(将“server.username”绑定到例如“foobar”)并且我知道机制最有可能是 beans.xml,但我更喜欢简单的“将此地图提供给 Weld ,请”代码替代。什么是这样做的好方法?


编辑 2013-10-16:在研究了在编译时而不是运行时工作的 Dagger 之后,我发现我们通常每个程序有 10-20 个,我们可以@Provider接受每个配置字符串的方法,然后在配置中查找地图。这允许特定于方法的行为(包括默认值)、提供 javadoc 的能力以及将所有这些方法放在同一个类中的能力。它也适用于开箱即用的 Weld。我正在考虑在博客条目中写一个更完整的解释。

4

5 回答 5

12

我现在想要那个赏金。弄清楚这一点教会了我很多关于 WELD 的内容,这是最有趣的一课:@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;
        }

    }
}

我通过制作一个 WELD-SE 应用程序测试了这是否有效:

@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

这应该使所有这些工作。如果您遇到任何困难,请告诉我,我会将我的测试项目发送给您。

于 2011-01-25T04:40:08.037 回答
11

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

首先,您需要一个价值持有者限定符。使用 @Nonbinding 来防止 WELD 只匹配具有相同值的限定符,因为我们希望这个特定限定符的所有值都匹配单个注入点。通过将限定符和值保留在同一个注释中,您不能只是偶然“忘记”其中一个。(KISS 原则)

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

接下来,您需要一个知道如何获取 Map 的生产者方法。您可能应该有一个包含生产者方法的命名 bean,因此您可以使用 getter/setter 显式初始化值,或者让 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;
于 2011-01-20T09:37:29.930 回答
0

关于什么

@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

于 2010-11-03T14:25:22.683 回答
0

在这里实施自定义 Weld InjectionServices不是一个选项吗?

于 2010-11-22T11:03:27.983 回答
0

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

@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.
于 2011-01-04T19:35:42.203 回答