Contexts and Dependency Injection in Java EE


08 Oct 2020  Sergio Martin Rubio  8 mins read.

CDI (Contexts and Dependency Injection) is one of the core features of Java EE. CDI allows you to glue the different componentes of your Java EE application in a loosely coupled way.

Commonly you have a web tier, enterprise tier and persistence tier and you can use CDI to join these three layers.

As the name says CDI provides a context feature and a dependency injection feature. The context is used to bind stateful componentes whereas the dependency injection allows you to inject a class instance without having to do the initialization by yourself.

CDI Beans

You can create your CDI beans by adding one of the bean scope annotation to your Java class.

@ApplicationScoped
public class MyBean {

    public String getMessage() {
        return "Hello Bean";
    }
}

The scope of a bean dictates when a bean instance is created and when is destroyed. Types:

  • Dependent pseudo-scoped: This is the default scope and the lifecycle of the bean annotated with @Dependent is bound to the bean that injects the dependent bean.
  • Application: A class annotated with @ApplicationScoped is created only once for the application.
  • Request: A class annotated with @RequestScoped is created once in each request and shared throughout the request.
  • Session: Beans annotated with @SessionScoped are shared during the all the requests that belong to the same HTPP session.
  • Conversation: A bean annotated with @ConversationScoped can track a conversation with a client. A conversation can include multiple linked HTTP requests where a JSF generates a URL containing a conversation ID.

Dependency Injection

You can inject a bean with the @Inject annotation in three different ways:

  • Constructor: You have to add the @Inject annotation to the class constructor
@WebServlet(urlPatterns = "/bean")
public class MyController extends HttpServlet {

    private final MyBean myBean;

    @Inject
    public MyController(MyBean myBean) {
        this.myBean = myBean;
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");

        PrintWriter out = response.getWriter();
        out.println("<h1>" + myBean.getMessage() + "</h1>");
    }
}
  • Setter method: You can create a setter method annotated with @Inject so you can inject your bean.
@WebServlet(urlPatterns = "/bean")
public class MyController extends HttpServlet {

    private MyBean myBean;

    @Inject
    public void setMyBean(MyBean myBean) {
        this.myBean = myBean;
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");

        PrintWriter out = response.getWriter();
        out.println("<h1>" + myBean.getMessage() + "</h1>");
    }
}
  • Field declaration: Add @Inject to a field.
@WebServlet(urlPatterns = "/bean")
public class MyController extends HttpServlet {

    @Inject
    private MyBean myBean;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");

        PrintWriter out = response.getWriter();
        out.println("<h1>" + myBean.getMessage() + "</h1>");
    }
}

There are different advantages and disadvantages depending on the dependency injection strategy used.

DI Strategy Advantages Disadvantages
Constructor Safer More boilerplate
Setter Method More concise More boilerplate and unsafe
Field Declaration Less boilerplate Unsafe

Dependency Resolution

You can declare multiple implementations of the same bean however you need to somehow differentiate them. Java EE provides the @Qualifier annotation to inject the desired bean implementation.

public interface Foo {
    String getName();
}
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface FirstFoo {
}
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface SecondFoo {
}
@FirstFoo
@ApplicationScoped
public class FirstFooImpl implements Foo {

    @Override
    public String getName() {
        return "First Foo";
    }
}
@SecondFoo
@ApplicationScoped
public class SecondFooImpl implements Foo {

    @Override
    public String getName() {
        return "Second Foo";
    }
}
@WebServlet(urlPatterns = "/qualifier")
public class MyQualifierController extends HttpServlet {

    private final Foo secondFoo;

    @Inject
    public MyQualifierController(@SecondFoo Foo secondFoo) {
        this.firstFoo = firstFoo;
    }

	@Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");

        PrintWriter out = response.getWriter();
        out.println("<h1>" + secondFoo.getName() + "</h1>");
    }
}

If we replace @FirstFoo with @SecondFoo it will inject FirstFooImpl instead.

If not qualifier is used it will use either an implementation with @Default annotation or if there is a single implementation it will use that one.

You can also inject multiple implementations at once combining @Inject with @Any annotation.

@WebServlet(urlPatterns = "/inject-list")
public class MyInjectListController extends HttpServlet {

    private final Instance<Foo> foos;

    @Inject
    public MyInjectListController(@Any Instance<Foo> foos) {
        this.foos = foos;
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");

        PrintWriter out = response.getWriter();
        foos.forEach(foo -> out.println("<h1>" + foo.getName() + "</h1>"));
    }

}

You can also use @Named to define a bean implementation and behaves in the same was as @Qualifer but additionally it allows you to give a name to the implementation.

CDI vs EJB

Both CDI bean and EJB can be injected and the both are complementary. CDI bean is heavily focused in separation of concerns whereas EJB main purpose is to provide container services. CDI can be seen as a simplified version of EJB, since EJB provides all the dependency injection features and container features. As as rule of thumb you will usually create a CDI bean and include EJB features when is required.

By default all session beans have @Dependant. You can also combine session beans with CDI scopes but there is some incompatibilities like a @Stateless EJB cannot have an @ApplicationScoped annotation or a @Singleton is incompatible with @RequestScoped.

Examples

Image by Alfonso Cerezo from Pixabay