Over the past years, the Spring Framework has given us many tools to simplify ease of application development.
One of the nicer features is Spring’s PropertyPlaceholderConfigurer, a BeanPostProcessor
implementation that allows you to inject values from property files into your managed beans, making application configuration a breeze.
Setting up one is a little bit of xml configuration:
|
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:com/foo/jdbc.properties"/> </bean> |
And then you can inject property values from your property file:
|
@Value("${jdbc.dbName}") public void setDatabaseName(String dbName) { ... } |
JEE doesn’t have a PropertyPlaceHolderConfigurer
right out of the box so we have to come up with a different solution.
What JEE offers are producer methods. A producer method is a method that constructs an object of an arbitary type which can then be used by Context Dependency Injection (CDI) as a candidate for injection.
Producer methods use the @Produces
annotation for indicating the kind of object it will produce.
The kind is indicated by the produced type and, optionally, a qualifier annotation.
Say that you want to indicate that some properties should be injected in your client class. We will put an custom annotation @ConfigurationValue
on each field to indicate the field value should come from a property.
|
@Inject @ConfigurationValue(value="jdbc.dbName") private String myDatabaseName; @Inject @ConfigurationValue private String myProp; |
where you want to explicitly specify the property key on the first field to be populated, and just use the field name as a property key on the second field.
The qualifier @ConfigurationValue
used by the object that requires configuration would look as follows:
|
@Qualifier @Retention(RUNTIME) @Target({ TYPE, METHOD, FIELD, PARAMETER }) public @interface ConfigurationValue { /** * Property key that will be searched when injecting the value. */ String value() default ""; /** * Defines if value for the given key must be defined. */ boolean required() default true; } |
The meta-annotation @Qualifier
here indicates this annotation can be used for JEE’s CDI qualifying mechanism.
Now, we have to write a producer method that responds to fields using the @ConfigurationValue
annotation.
An example of a producer method that retrieves its property values from a properties file could be the one below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
public class ConfigurationValueProducer { //We could use Spring's PropertyResolver, or the JEE one from the faces package. We use the last one here. //You could replace your property loading with any way you see fit. @Inject PropertyResolver resolver; @Produces @ConfigurationValue public String getStringConfigValue(InjectionPoint ip) { // Trying with explicit key defined on the field String key = ip.getAnnotated() .getAnnotation(ConfigurationValue.class).value(); boolean isKeyDefined = !key.trim().isEmpty(); boolean valueRequired = ip.getAnnotated() .getAnnotation(ConfigurationValue.class).required(); if (isKeyDefined) { return resolver.getValue(key); } // Falling back to fully-qualified field name resolving. String fqn = ip.getMember().getDeclaringClass().getName() + "." + ip.getMember().getName(); key = fqn; String value = resolver.getValue(fqn); // No luck... so perhaps just the field name? if (value == null) { key = ip.getMember().getName(); value = resolver.getValue(key); } // No can do - no value found but you've said it's required. if (value == null && valueRequired) { throw new IllegalStateException("No value defined for field: " + fqn + " but field was marked as required."); } return value; } |
Of course, this only works for strings now, but it would be easy to extend in such a way that you would support numerical types as well, through multiple producer methods.