Tuesday, 4 October 2011

Looking into the Magic of Dependency Injection Using Annotations - Part 3

This is the third in a short series of blogs that looks into implementing dependency injection using annotations.If you’re familiar with Spring or EJB3, you’ll know the sort of thing I’m talking about: you write you class; add a few annotations, for example @Autowired or @Ejb; deploy it to your web-server and then et volia it’s all glued together and works, as if by magic...

This blog isn’t going to write a fully formed DI factory using annotations, but it will give you a few hints in to the kinds of techniques used by the Guys at Spring and the EJB3 Team.

In order to write a DI factory like this, you need to complete a few specific tasks. These include figuring out which classes on your class-path are annotated, instantiating those classes and gluing them all together. My last two blogs demonstrated how to find all the .class files located either directly on the file system or in JAR files. In today’s scenario, and using the code from my previous blogs, we’ve already located some classes and are ready to start checking them for annotations. In order to demonstrate this I’ve written a couple of suitable annotations. The first one is called @MyComponent, which is a type level annotation that’s only applied to whole classes. This annotation tells us which classes we’ll be instantiating as part of our application:

@Documented
@Retention
(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
public @interface MyComponent {

}

The second annotation that’s needed is one that’ll take care of the dependency injection. This is called @MyAutoWire, which as the name suggests is a method and field level annotation that tells us which classes to inject into what methods or fields:

@Documented
@Retention
(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD })
public @interface MyAutoWire {

}

Before getting down to the nitty-gritty we’ll also need a class that’s marked with these annotations that we can use for testing:

@MyComponent
public class ClassWithAttributes {

 
private String str1;

 
private int value1;

 
@MyAutoWire
 
private InjectedClass injectedClass;

 
@MyAutoWire
 
public void setValue1(int value1) {
   
this.value1 = value1;
 
}
}

Now we can get down to business. The code below is an annotation checker that contains three straight forward methods all of which use reflection.

public class AnnotationChecker {

 
private final Class<?> instance;

 
public AnnotationChecker(Class<?> instance) {
   
this.instance = instance;
 
}

 
public boolean isMyComponent() {

   
return instance.isAnnotationPresent(MyComponent.class);
 
}

 
private boolean isNotNull(Object obj) {
   
return obj != null;
 
}

 
public List<Method> getAutoWireMethods() {

   
List<Method> methodList = new ArrayList<Method>();
    Method
[] methods = instance.getDeclaredMethods();
   
for (Method method : methods) {
     
if (isNotNull(method.getAnnotation(MyAutoWire.class))) {
       
methodList.add(method);
     
}
    }

   
return methodList;
 
}

 
public List<Field> getAutoWireFields() {

   
List<Field> fieldList = new ArrayList<Field>();
    Field
[] fields = instance.getDeclaredFields();
   
for (Field field : fields) {
     
if (isNotNull(field.getAnnotation(MyAutoWire.class))) {
       
fieldList.add(field);
     
}
    }

   
return fieldList;
 
}
}

The first method is isMyComponent(...). This simply returns true if a class is annotated with @MyComponent. The second method, getAutoWireMethods(...) returns a list of all methods in a class annotated with @MyAutoWire(...), whilst the final method, getAutoWireFields(...) returns a list of all fields annotated with @MyAutoWire. In order or demonstrate this code, I’ve included my JUnit Tests below:

public class AnnotationCheckerTest {

 
@Test
 
public void testIsAnnotatedWithMyComponent() {

   
final AnnotationChecker instance = new AnnotationChecker(
       
ClassWithAttributes.class);
    assertTrue
(instance.isMyComponent());

 
}

 
@Test
 
public void testIsNotAnnotatedWithMyComponent() {

   
assertFalse(new AnnotationChecker(String.class).isMyComponent());
 
}

 
@Test
 
public void testGetAutoWireMethodsWithAnnotationPresent() {

   
final AnnotationChecker instance = new AnnotationChecker(
       
ClassWithAttributes.class);
    List<Method> methods = instance.getAutoWireMethods
();
    assertEquals
(1, methods.size());

   
// Test that its the right method
   
Method method = methods.get(0);
    String name = method.getName
();
    assertEquals
("setValue1", name);

   
// Check that it is actually the correct annotation type
   
MyAutoWire myComponent = method.getAnnotation(MyAutoWire.class);
    assertNotNull
(myComponent);
 
}

 
@Test
 
public void testGetAutoWireMethodsWithNoMethodsAnnotated() {

   
final AnnotationChecker instance = new AnnotationChecker(String.class);
    List<Method> methods = instance.getAutoWireMethods
();
   
// return empty array rather than risk a NullPointerException
   
assertEquals(0, methods.size());
 
}

 
@Test
 
public void testGetAutoWireFieldsWithAFieldAnnotated() {

   
final AnnotationChecker instance = new AnnotationChecker(
       
ClassWithAttributes.class);
    List<Field> fields = instance.getAutoWireFields
();
    assertEquals
(1, fields.size());

   
// Test that its the right method
   
Field field = fields.get(0);
    String name = field.getName
();
    assertEquals
("injectedClass", name);

   
// Check that it is actually the correct annotation type
   
MyAutoWire myComponent = field.getAnnotation(MyAutoWire.class);
    assertNotNull
(myComponent);
 
}

 
@Test
 
public void testGetAutoWireFieldsWithNofieldsWired() {

   
final AnnotationChecker instance = new AnnotationChecker(String.class);
    List<Field> fields = instance.getAutoWireFields
();
    assertEquals
(0, fields.size());
 
}
}

All that’s left to do is to use this class with the code from the previous two blogs together with a little jiggery-pokery to wire the whole lot together. More on that later...

No comments: