About Peter Daum

Peter is senior Java developer in the telecommunication industry. He works in the area of business and operations support systems and is always happy to share his knowledge and experience. He is interested in everything related to Java and software craftsmanship.

Tutorial: Writing your own CDI extension

Today I will show you how to write a CDI extension.

CDI provides a easy way for extending the functionality, like

  • adding own scopes,
  • enabling java core classes for extension,
  • using the annotation meta data for augmentation or modification,
  • and much more.

In this tutorial we will implement an extension that will inject properties from a property file, as usual I will provide the sources at github [Update: sources @ github].

The goal

Providing an extension that allows us to do the following

@PropertyFile("myProps.txt")
public class MyProperties {
  @Property("version")
  Integer version;
 
  @Property("appname")
  String appname;
}

where version and appname are defined in the file myProps.txt.

Preparation

At first we need the dependency of the CDI api

dependencies.compile "javax.enterprise:cdi-api:1.1-20130918"

Now we can start. So let’s

Getting wet

The basics

The entry point for every CDI extension is a class that implements  javax.enterprise.inject.spi.Extension

package com.coderskitchen.propertyloader;
 
import javax.enterprise.inject.spi.Extension;
public class PropertyLoaderExtension implements Extension {
//  More code later
}

Additionally we must add the full qualified name of this class to a file named  javax.enterprise.inject.spi.Extension in META-INF/services directory.

javax.enterprise.inject.spi.Extension

 	
com.coderskitchen.propertyloader.PropertyLoaderExtension

These are the basic steps for writing a CDI extension.

Background information
CDI uses Java SE’s service provider architecture, that’s why we need to implement the marker interface and adding the file with the FQN of the implementing class.

Diving deeper

Now we must choose the right event to listen for.

Background information
The CDI specs defines several events which are fired by the container during the initialization of the application.
For example the BeforeBeanDiscovery is fired before the container starts with the bean discovery.

For this tutorial we need to listen for the ProcessInjectionTarget event. This event is fired for every single java class, interface or enum that is discovered and that is possibly instantiated by the container during runtime.
So let’s add the observer for this event:

public <T> void initializePropertyLoading(final @Observes ProcessInjectionTarget<T> pit) {
}

The ProcessInjectionTarget grants access to the underlying class via the method getAnnotatedType and the instance in creation via getInjectionTarget. We use the annotatedType for getting the annotations on the class to check if @PropertyFile is available. If not, we will return directly as a short circuit.

The InjectionTarget is later used for overwriting the current behavior and setting the values from the properties file.

public <T> void initializePropertyLoading(final @Observes ProcessInjectionTarget<T> pit) {
            AnnotatedType<T> at = pit.getAnnotatedType();
            if(!at.isAnnotationPresent(PropertyFile.class)) {
                    return;
            }
    }

For the sake of this tutorial we assume that the properties file is located directly in the root of the classpath. With this assumption we can add the following code to load the properties from the file

PropertyFile propertyFile = at.getAnnotation(PropertyFile.class);
String filename = propertyFile.value();
InputStream propertiesStream = getClass().getResourceAsStream("/" + filename);
Properties properties = new Properties();
try {
    properties.load(propertiesStream);
    assignPropertiesToFields(at.getFields, properties); // Implementation follows
} catch (IOException e) {
    e.printStackTrace();
}

Now we can assign the property values to the fields. But for CDI we have to do this in a slightly different way. We should use the InjectionTarget and override the current AnnotatedType. This allows CDI to ensure that all things could happen in a proper order.

For achieving this we use a final Map<Field, Object> where we can store the current assignments for later usage in the InjectionTarget. The mapping is done in the method assignPropertiesToFields.

private <T> void assignPropertiesToFields(Set<AnnotatedField<? super T>> fields, Properties properties) {
        for (AnnotatedField<? super T> field : fields) {
            if(field.isAnnotationPresent(Property.class)) {
                Property property = field.getAnnotation(Property.class);
                String value = properties.getProperty(property.value());
                Type baseType = field.getBaseType();
                fieldValues.put(memberField, value);
            }
        }

As a last step we will now create a new InjectionTarget to assign the field values to all newly created instance of the underlying class.

final InjectionTarget<T> it = pit.getInjectionTarget();
    InjectionTarget<T> wrapped = new InjectionTarget<T>() {
      @Override
      public void inject(T instance, CreationalContext<T> ctx) {
        it.inject(instance, ctx);
        for (Map.Entry<Field, Object> property: fieldValues.entrySet()) {
          try {
            Field key = property.getKey();
            key.setAccessible(true);
            Class<?> baseType = key.getType();
            String value = property.getValue().toString();
            if (baseType == String.class) {
              key.set(instance, value);
            }  else if (baseType == Integer.class) {
              key.set(instance, Integer.valueOf(value));
            } else {
              pit.addDefinitionError(new InjectionException("Type " + baseType + " of Field " + key.getName() + " not recognized yet!"));
            }
          } catch (Exception e) {
            pit.addDefinitionError(new InjectionException(e));
          }
        }
      }
 
      @Override
      public void postConstruct(T instance) {
        it.postConstruct(instance);
      }
 
      @Override
      public void preDestroy(T instance) {
        it.dispose(instance);
      }
 
      @Override
      public void dispose(T instance) {
        it.dispose(instance);
      }
 
      @Override
      public Set<InjectionPoint> getInjectionPoints() {
        return it.getInjectionPoints();
      }
 
      @Override
      public T produce(CreationalContext<T> ctx) {
        return it.produce(ctx);
      }
    };
    pit.setInjectionTarget(wrapped);

That’s all for doing the magic. Finally here is the complete code of the ProperyLoaderExtension.

PropertyLoaderExtension

package com.coderskitchen.propertyloader;
 
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.InjectionException;
import javax.enterprise.inject.spi.AnnotatedField;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.inject.spi.ProcessInjectionTarget;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
 
public class PropertyLoaderExtension implements Extension {
 
  final Map<Field, Object> fieldValues = new HashMap<Field, Object>();
 
  public <T> void initializePropertyLoading(final @Observes ProcessInjectionTarget<T> pit) {
    AnnotatedType<T> at = pit.getAnnotatedType();
    if(!at.isAnnotationPresent(PropertyyFile.class)) {
      return;
    }
    PropertyyFile propertyyFile = at.getAnnotation(PropertyyFile.class);
    String filename = propertyyFile.value();
    InputStream propertiesStream = getClass().getResourceAsStream("/" + filename);
    Properties properties = new Properties();
    try {
      properties.load(propertiesStream);
      assignPropertiesToFields(at.getFields(), properties);
 
    } catch (IOException e) {
      e.printStackTrace();
    }
 
    final InjectionTarget<T> it = pit.getInjectionTarget();
    InjectionTarget<T> wrapped = new InjectionTarget<T>() {
      @Override
      public void inject(T instance, CreationalContext<T> ctx) {
        it.inject(instance, ctx);
        for (Map.Entry<Field, Object> property: fieldValues.entrySet()) {
          try {
            Field key = property.getKey();
            key.setAccessible(true);
            Class<?> baseType = key.getType();
            String value = property.getValue().toString();
            if (baseType == String.class) {
              key.set(instance, value);
            }  else if (baseType == Integer.class) {
              key.set(instance, Integer.valueOf(value));
            } else {
              pit.addDefinitionError(new InjectionException("Type " + baseType + " of Field " + key.getName() + " not recognized yet!"));
            }
          } catch (Exception e) {
            pit.addDefinitionError(new InjectionException(e));
          }
        }
      }
 
      @Override
      public void postConstruct(T instance) {
        it.postConstruct(instance);
      }
 
      @Override
      public void preDestroy(T instance) {
        it.dispose(instance);
      }
 
      @Override
      public void dispose(T instance) {
        it.dispose(instance);
      }
 
      @Override
      public Set<InjectionPoint> getInjectionPoints() {
        return it.getInjectionPoints();
      }
 
      @Override
      public T produce(CreationalContext<T> ctx) {
        return it.produce(ctx);
      }
    };
    pit.setInjectionTarget(wrapped);
  }
 
  private <T> void assignPropertiesToFields(Set<AnnotatedField<? super T>> fields, Properties properties) {
    for (AnnotatedField<? super T> field : fields) {
      if(field.isAnnotationPresent(Propertyy.class)) {
        Propertyy propertyy = field.getAnnotation(Propertyy.class);
        Object value = properties.get(propertyy.value());
        Field memberField = field.getJavaMember();
        fieldValues.put(memberField, value);
      }
    }
  }
}

Downloads

The complete source code will be available on git hub until Monday evening. The jar archive is available here PropertyLoaderExtension.

Final notes

This tutorial has shown how simply you can add a new features to the CDI framework. Within a few lines of code a working property loading and injection mechanism was added. The events that are fired during the lifecycles of an application provides a loose coupled and powerful approach to add new features, intercept bean creation or changing the behavior.

The property injection could also be achieved by introducing a set of producer methods, but this approach requires one producer method per field type. With this general approach you just have to add new converters for enabling injection of other types of values.
 

Reference: Tutorial: Writing your own CDI extension from our JCG partner Peter Daum at the Coders Kitchen blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

Leave a Reply


9 × = nine



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close