Skip to main content

Applicaction configuration in Java EE 6 using CDI - a simple example

Posted by jjviana on May 18, 2010 at 10:22 AM PDT

CDI (Contexts and Dependency Injection for the Java EE platform) is defined in JSR-299 and enhances support for dependency injection in Java EE 6. The more I use CDI the more I like it...However, I couldn't find a simple example of how to configure your application with CDI by reading configuration attributes from a file. Here is an example of how it can be done:

Specifying injection points

Injection points are places where CDI will inject values automatically. They can be used to receive references to container objects like PersistenceUnit or Transaction manager, and also to receive configuration values.

One specifies a injection point by annotating a field, constructor parameter or method parameter with the @Inject annotation. When the container finds this annotation it will try to find (or construct) a value to be stored at this point.

Just using @Inject is not enough for configuration, because the container will not know where we want it to get the configuration from. We will need to create a configuration factory, but before that we need to define an additional annotation to provide a hint about where the value will be coming from. This annotation is called a Qualifier annotation exactly because it qualifies the information to be injected. In this example we will create the @Config annotation:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface Config {

}

 

Defining a configuration injection point is then just the matter of placing the @Inject and @Config annotation in bean fields. For instance, lets inject a configuration value in a stateless session bean:

 

package mypackage;
@Stateless
class MyStatelessBean {
     @Inject @Config
     private String serverAddress;

        
     public void myBusinessMethod() {
         // Use the serverAddress configuration defined above

    }
}

Creating a configuration factory

So far we have not specified where the value for the serverAddress field will come from. In order to do that we must create a producer method. This is a very powerful CDI concept that allows one to create methods that produce values to be injected.

To create a producer method we just need to create a simple Java class containing a method annotated with the @Produces and @Config annotation and the right return type:

 

public class ConfigurationFactory {
   
  
    public @Produces @Config String getConfiguration(InjectionPoint p) {
            // .... insert config lookup logic here,....
    }

     public @Produces @Config Double getConfigurationDouble(InjectionPoint p) {

         String val=getConfiguration(p);
         return Double.parseDouble(val);
        
     }

This class is of course not fully implemented yet. A CDI bean can have multiple producer methods, annotated with the @Produces annotation plus whatever qualifier annotations are needed to specify the kind of value being produced. The first method in this class produces String values to be injected in injection points specified with the @Config annotation. The second method prouces Double values (by simply parsing the result of the String value). We could create methods of any required return type.

Still we haven't defined where the configuration values are going to come from. This is one of the most powerful aspects of this method: the configuration values could come from anywhere: a flat file, a database, a xml file, a network service etc. The CDI container is not concenred where the producer method is going to get the values from as long as it returns the correct type.

So, to complete this example, lets create a configuration factory that loads values from a simple java.util.Properties file. The complete exemple is listed below:

 

public class ConfigurationFactory {
 
    private volatile static Properties configProperties;
    public static final String propertiesFilePath="/etc/application.properties";
    public synchronized static Properties getProperties() {

        if(configProperties==null) {
            configProperties=new Properties();
            try {
                configProperties.load(new FileInputStream(propertiesFilePath);
            } catch (IOException ex) {
                Logger.getLogger(ConfigurationFactory.class.getName()).log(Level.SEVERE, null, ex);
                throw new RuntimeException(ex);
            }

        }

        return configProperties;
    }

   
   

    public @Produces @Config String getConfiguration(InjectionPoint p) {

        String configKey=p.getMember().getDeclaringClass().getName()+"."+p.getMember().getName();
        Properties config=getProperties();
        if(config.getProperty(configKey)==null) {
            configKey=p.getMember().getDeclaringClass().getSimpleName()+"."+p.getMember().getName();
            if(config.getProperty(configKey)==null)
                configKey=p.getMember().getName();
        }
        System.err.println("Config key= "+configKey+" value = "+config.getProperty(configKey));

        return config.getProperty(configKey);
    }

     public @Produces @Config Double getConfigurationDouble(InjectionPoint p) {

         String val=getConfiguration(p);
         return Double.parseDouble(val);
        
     }

    
}

Notice the use of the InjectionPoint instance. This instance is provided to the producer method by the container and contains information about the specific injection point being resolved.

Since we are looking up configuration values from properties files, I decided to use the class name together with the field name to create the property key.

The configuration file for this application should  be a property file  located in /etc/application.properties. Change this location to suit your own needs, I personally like having the application configuration located outside the application deployment directory.

The property configuration for MyStatelesBean inside this file would be:

mypackage.MyStatelessBean.serverAddress= ....

But since most applications have beans with globally distinct names the ConfigurationFactory class also lets you specify the property name as:

MyStatelessBean.serverAddress= ....

Better yet, if your property name is globally unique within the application you can make it even simpler:

serverAddress= ....

This example uses simple primitive properties but it should be possible to extend this idea to load more complex configuration objects from XML files or databases.

 

Related Topics >>

Comments

I wonder how to write

I wonder how to write @Config("SERVER_ADDRESS") String serverAddress.
http://john-ament.blogspot.com/2010/03/writing-property-loader-in-java-e...

Great post!  I'm not sure if adding attributes to CDI ...

Great post!

I'm not sure if adding attributes to CDI binding annotations is allowed. I have tested this scenario using additiona annotation @ConfigName("my.name")

if( p.getAnnotated().isAnnotationPresent(ConfigVariable.class))

{

configKey = p.getAnnotated().getAnnotation(ConfigVariable.class).value();

}

else

{

configKey = p.getMember().getDeclaringClass().getName() + "." + p.getMember().getName();

}

The following tutorial is adding three attributes : key, ...

The following tutorial is adding three attributes : key, default value and mandatory.

package com.ubiteck.cdi; import ...

package com.ubiteck.cdi;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.enterprise.util.Nonbinding;
import javax.inject.Qualifier;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectedConfiguration {
    /**
     * Bundle key
     * @return a valid bundle key or ""
     */
    @Nonbinding String key() default "";
    /**
     * Is it a mandatory property
     * @return true if mandator
     */
    @Nonbinding boolean mandatory() default false;
    /**
     * Default value if not provided
     * @return default value or ""
     */
    @Nonbinding String defaultValue() default "";
}