Understanding Dependency Injection – Part 2 PostConstruct and Constructor Injection

In the first part of this series I introduced basic IoC functionality. In this part we will cover @PostConstruct methods and Constructor Injection.

Consider the following scenario:

public class Car {
  @Inject
  private Engine engine;  

  public Car() {
    engine.initialize();  
  }
  ...
}  

Since Car has to be instantiated prior to field injection, the injection point engine is still null during the execution of the constructor, resulting in a NullPointerException.

This problem can be solved either by JSR-330 Dependency Injection for Java constructor injection or JSR 250 Common Annotations for the Java @PostConstruct method annotation.

@PostConstruct

JSR-250 defines a common set of annotations which has been included in Java SE 6.

The PostConstruct annotation is used on a method that needs to be executed after
dependency injection is done to perform any initialization. This method MUST be
invoked before the class is put into service. This annotation MUST be supported on all
classes that support dependency injection.
JSR-250 Chap. 2.5 javax.annotation.PostConstruct

The @PostConstruct annotation allows for the definition of methods to be executed after the instance has been instantiated and all injects have been performed.

public class Car {
  @Inject
  private Engine engine;  
  
  @PostConstruct
  public void postConstruct() {
    engine.initialize();  
  }
  ...
}  

Instead of performing the initialization in the constructor, the code is moved to a method annotated with @PostConstruct.

The processing of post-construct methods is a simple matter of finding all methods annotated with @PostConstruct and invoking them in turn.

private  void processPostConstruct(Class type, T targetInstance) {
  Method[] declaredMethods = type.getDeclaredMethods();

  Arrays.stream(declaredMethods)
      .filter(method -> method.getAnnotation(PostConstruct.class) != null) 
      .forEach(postConstructMethod -> {
         try {
           postConstructMethod.setAccessible(true);
           postConstructMethod.invoke(targetInstance, new Object[]{});
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
          Logger.getLogger(Voodoo.class.getName()).log(Level.SEVERE, null, ex);
          throw new RuntimeException(ex);
        }
      });
}

Voodoo.class V2.5

The processing of post-construct methods has to be performed after instantiation and injection has been completed.

public  T instance(Class type) {
  try {
    T newInstance = (T) construct(types.get(type));
    processFields(type, newInstance);
    processPostConstruct(type, newInstance);
    return newInstance;
  } catch (Exception ex) {
    Logger.getLogger(Voodoo.class.getName()).log(Level.SEVERE, null, ex);
    throw new RuntimeException(ex);
  }
}

Voodoo.class V2.5

Constructor Injection

JSR-330 constructor injection allows the use of parameterized constructors.

Injectable constructors are annotated with @Inject and accept zero or more dependencies as arguments. @Inject can apply to at most one constructor per class.

@Inject ConstructorModifiers SimpleTypeName(FormalParameterListopt) Throws ConstructorBody

@Inject is optional for public, no-argument constructors when no other constructors are present. This enables injectors to invoke default constructors.

@Inject Annotation public SimpleTypeName() Throws ConstructorBody

JSR-330 @Inject JavaDoc

public class HotRod {
  private Engine engine;
  
  @Inject
  public HotRod(Engine engine) {
    engine.initialize();
    this.engine = engine;
  }
  ...
}

Instead of directly invoking the no-args constructor for instantiation, the type has to be scanned for constructors annotated with @Inject.

private  T construct(Class type) throws Exception {
  //Find constructors annotated with @Inject. 
  List> injectableConstructors = Arrays.stream(type.getConstructors())
  .filter(constructor -> constructor.getAnnotation(Inject.class) != null)
  .collect(Collectors.toList());

  switch (injectableConstructors.size()) {
    case 0: {
      //No injectable constructor, use default constructor.
      Constructor constructor = type.getConstructor(EMPTY_TYPE_ARRAY);
      return constructor.newInstance(EMPTY_OBJ_ARRAY);
    }
    case 1: {
      //Instantiate with injectable constructor. 
      Constructor constructor = (Constructor) injectableConstructors.get(0);
      Class[] parameterTypes = constructor.getParameterTypes();
      
      Object[] params = Arrays.stream(parameterTypes)
        .map(paramType -> instance(paramType))
        .toArray();
      return constructor.newInstance(params);
    }
    default:
      throw new RuntimeException("Ambigious injectable constructor for " + type);
  }
}

Voodoo.class V2.5

If exactly one injectable constructor is found, VoodooDI attempts to instantiate all required parameters paramType -> instance(paramType) and instantiate the target type with the injectable constructor constructor.newInstance(params).

In this post we extended VoodooDI to support @PostConstruct methods and constructor injection, in the next post of this series we will introduce contextual scopes.

3 thoughts on “Understanding Dependency Injection – Part 2 PostConstruct and Constructor Injection”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.