Java EE CDI programmatic dependency disambiguation example - Injection Point inspection

08 June 2013
By Gonçalo Marques
In this tutorial we will see how to implement Java EE CDI dependency disambiguation in a programmatic (or dynamic) way by inspecting injection points. The same applies for programmatic dependency resolution at runtime.

Introduction

In Java EE CDI it is possible to provide multiple implementations of a given interface - or service - to the service clients. In this tutorial we will see how to implement a dynamic mechanism for fetching the correct implementation based on the service client requirements.

This tutorial considers the following environment:

  1. JDK 1.7.0.21
  2. Weld 1.1.10

Weld is the CDI reference implementation


Note: We have already seen in another tutorial how to implement dependency disambiguation by using solely CDI Qualifiers (Java EE CDI dependency disambiguation example). This time we will implement the dependency resolution in a dynamic way so it may serve as an example of what we can do with CDI if our requirements need a more flexible mechanism.

The example service

We start by defining a simple interface and a couple of interface implementations to serve as an example in this tutorial:

The interface

package com.byteslounge.service;

public interface NotificationService {

  void sendNotification();

}


EmailNotificationService implementation

package com.byteslounge.service.impl;

import com.byteslounge.service.NotificationService;

public class EmailNotificationService implements NotificationService {

  @Override
  public void sendNotification() {
    System.out.println("Sending email notification");
  }

}


SmsNotificationService implementation

package com.byteslounge.service.impl;

import com.byteslounge.service.NotificationService;

public class SmsNotificationService implements NotificationService {

  @Override
  public void sendNotification() {
    System.out.println("Sending SMS notification");
  }
	
}

A Java annotation to be used by service clients

Now we define a Java annotation that will provide a way for service clients to define which implementation they want injected. Note that this is not a CDI Qualifier.

A Java annotation to be used by service clients

package com.byteslounge.service;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import com.byteslounge.service.impl.EmailNotificationService;
import com.byteslounge.service.impl.SmsNotificationService;

@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface NotificationServiceType {
  
  ServiceType value();
  
  public enum ServiceType{

    EMAIL(EmailNotificationService.class),
    SMS(SmsNotificationService.class);
    
    Class<? extends NotificationService> clazz;
    
    private ServiceType(Class<? extends NotificationService> clazz){
      this.clazz = clazz;
    }

    public Class<? extends NotificationService> getClazz() {
      return clazz;
    }
  }
}

What are we doing here? We are defining a Java annotation which value - SMS or EMAIL - contains a reference to the respective service implementation type (or class). This annotation will be useful later to provide a way for clients to determine which service implementation they want injected:

Client injection point example

@Inject
@NotificationServiceType(ServiceType.SMS)
private NotificationService notificationService;

A CDI Producer

Now we define a Producer that will serve as a factory of NotificationService implementations. The Producer and will also do dynamic dependency resolution based on the client injection point annotation:

CDI Producer

package com.byteslounge.service.impl;

import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.InjectionPoint;

import com.byteslounge.service.NotificationService;
import com.byteslounge.service.NotificationServiceProducer;
import com.byteslounge.service.NotificationServiceType;

public class NotificationServiceFactory {

  @Produces
  @NotificationServiceProducer
  public NotificationService createNotificationService(
    @Any Instance<NotificationService> instance, InjectionPoint ip){
    
    Annotated annotated = ip.getAnnotated();
    NotificationServiceType notificationTypeAnnotation =
       annotated.getAnnotation(NotificationServiceType.class);

    Class<? extends NotificationService> notificationServiceType =
       notificationTypeAnnotation.value().getClazz();

    return instance.select(notificationServiceType).get();
    
  }
}

This producer will be responsible for providing NotificationService implementations. It takes a couple of parameters:

The first one is of type Instance and parameterized with NotificationService type. It is also annotated with the @Any qualifier so it will be ready to fetch any available implementation of NotificationService type (All CDI managed beans have the @Any qualifier by default).

The second parameter is a reference to the client injection point, ie. the one where the client used @Inject in order to inject a service implementation.

In the producer method body we are basically fetching the @NotificationServiceType annotation used by the client injection point and extracting its information. Based on the annotation value we select the correct bean implementation from the Instance reference.

One should note here that there is an extra annotation being used: @NotificationServiceProducer. This annotation is a CDI Qualifier and is used in order to disambiguate between the producer method and the bean implementations themselves. So in fact, the client injection point becomes:

Client injection point example

@Inject
@NotificationServiceProducer
@NotificationServiceType(ServiceType.SMS)
private NotificationService notificationService;

The extra CDI Qualifier is defined as:

NotificationServiceProducer qualifier

package com.byteslounge.service;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.inject.Qualifier;

@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface NotificationServiceProducer {
	
}

If we didn't used this extra Qualifier - both in the producer method and injection point - the container would not know that the injected implementation should come from the CDI Producer and not from the implementation classes themselves being directly injected.

Conclusion

This tutorial is an example of what can be done by inspecting CDI Injection Points to determine component dependencies and other injection point characteristics.

We used a Java Enum to carry the info about the implementation desired by the client, but we could have done something even more dynamic, like passing a property in the Injection Point that would be used in an even more dynamic dependency resolution strategy.

Downloadable sample

You can find a downloadable sample at the end of this page containing the implementation described above. The sample is configured to be run on Tomcat and the service is injected into a testing servlet.

Download source code from this article

Related Articles

Comments

About the author
Gonçalo Marques is a Software Engineer with several years of experience in software development and architecture definition. During this period his main focus was delivering software solutions in banking, telecommunications and governmental areas. He created the Bytes Lounge website with one ultimate goal: share his knowledge with the software development community. His main area of expertise is Java and open source.

GitHub profile: https://github.com/gonmarques

He is also the author of the WiFi File Browser Android application: