We have a situation where we provide an external configuration in form of a Map to our running programs. I have found that JSR-330 Dependency Injection gives a much cleaner way to use that configuration map in the code instead of passing the map around or to use JNDI to get it.
@Inject @Named("server.username") String username;
lets the JSR-330 implementation fill in this field automatically.
With Guice I can set the value with
bindConstant().annotatedWith(Names.named(key)).to(value);
I would like to be able to do the same in Weld (bind "server.username" to e.g. "foobar") and I understand that the mechanism most likely is beans.xml, but I would prefer a simple "feed this map to Weld, please" code alternative. What would be a good way to do this?
EDIT 2013-10-16: After looking into Dagger which works at compile time and not runtime, I found that with us usually having 10-20 per program we could live with having a @Provider
method for each configuration string which then looks up in the configuration map. This allows for method specific behavior (including default values), ability to provide javadoc, and ability to put all these methods in the same class. Also it works well with Weld out of the box. I am considering writing a fuller explanation in a blog entry.
Not all that interested in the bounty, but I'll take it if it's still on the table. This is VERY similar to some code I'm using at $DAYJOB, and so this isn't theory, it's what I use in production code, but modified to protect the guilty. I haven't tried compiling the modified code, so be warned that I may have made some errors in changing names and such, but the principles involved here have all been tested and work.
First, you need a Value Holder Qualifier. Use @Nonbinding to keep WELD from matching ONLY to qualifiers with identical values, since we want all values of this particular qualifier to match a single injection point. By keeping the qualifier and value in the same annotation, you can't just "forget" one of them by accident. (KISS principle)
@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();
}
Next, you need a producer method which knows how to get the Map. You should probably have a Named bean which holds the producer method, so you can either explicitly initialize the value by using getters/setters, or else have the bean initialize it for you.
We must specify a blank value for the qualifier on the producer method to avoid compile time errors, but it's never used in practice.
@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());
}
}
Usage is simple:
@Inject
@ConfigValue("some.map.key.here")
String someConfigValue;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With