Kirill G.
Kirill G.

Reputation: 960

Java EE Dependency Injection in Websphere Liberty profile

I am trying to use CDI in my very simple web app that runs in Websphere Liberty profile installed via Docker.

However the injection fails unless I specify a scope annotation (e.g. @ApplicationScoped) on the injected bean, though according to a lot of online tutorials (e.g. this), Java EE specs do not require this.

Below is the code that fails:

HelloWorldServlet.java

package my.simple.app;

import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/HelloWorld")
public class HelloWorldServlet extends HttpServlet {

    static String PAGE_HEADER = "<html><head /><body>";
    static String PAGE_FOOTER = "</body></html>";

    @Inject
    HelloService helloService;

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println(PAGE_HEADER);
        writer.println("<h1>" + helloService.createHelloMessage("World") + "</h1>");
        writer.println(PAGE_FOOTER);
        writer.close();
    }

}

HelloService.java

package my.simple.app;

public class HelloService {
    String createHelloMessage(String name) {
        return "Hello " + name + "!";
    }
}

server.xml (Docker image is websphere-liberty:javaee7)

<server description="default servlet engine">

    <httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="9080" httpsPort="9443" />

    <!-- Enable features -->
    <featureManager>
        <feature>servlet-3.1</feature>
        <feature>cdi-1.2</feature>
    </featureManager>

</server>

However I get this error

Error 404: javax.servlet.UnavailableException: SRVE0319E: For the [my.simple.app.HelloWorldServlet] servlet, my.simple.app.HelloWorldServlet servlet class was found, but a resource injection failure has occurred. The @Inject java.lang.reflect.Field.helloService reference of type my.simple.app.HelloService for the null component in the app.war module of the app application cannot be resolved.

However once I add @ApplicationScoped to HelloService it all starts working.

What I am doing wrong?

Solution:

In CDI1.2 (which I am using) by default only annotated beans are discovered. To make all beans be discovered, and explicit discovery mode needs to be enabled in beans.xml

Links:

Upvotes: 2

Views: 2841

Answers (3)

Emily Jiang
Emily Jiang

Reputation: 161

Although what Scott suggests fixes your problem, here is the full picture about how it works.

In CDI 1.2, all applications are CDI enabled by default.

If there is an empty beans.xml or a beans.xml with the bean-discovery-mode="all", all classes are opted in for beans.

In the absence of the beans.xml or beans.xml with bean-discovery-mode="annotated", every class will be scanned for beans. Only the classes annotated with bean defining annotations (https://docs.jboss.org/cdi/spec/1.2/cdi-spec.html#bean_defining_annotations) are treated as beans.

In your first example, since you don't have beans.xml, the scanning for bean-defining annotations was performed. As no bean defining annotation was found, cdi was disabled. As a consequence, the injection failed.

To enable the injection, there are two solutions: 1. Annotate the class HelloService with a bean defining annotations (e.g. any scopes: ApplicationScoped, RequestScoped, SessionScoped, ConversationScoped, Dependent etc). This will make HelloService a CDI bean and then Injection will succeed. 2. Add an empty beans.xml or beans.xml with beans-discovery-mode="all" under WEB-INF for .war files or META-INF for .jar files

For more information about bean archives, please refer to https://docs.jboss.org/cdi/spec/1.2/cdi-spec.html#bean_archive.

Upvotes: 3

Scott Kurz
Scott Kurz

Reputation: 5320

You can force CDI to treat the servlet as a bean and perform injection by changing the bean discovery mode to all.

This article provides some useful background and an example of this:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
   http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
   bean-discovery-mode="all">
</beans>

Or, in WDT, you can generate this by right-clicking the project and selecting Java EE Tools -> Generate CDI Beans Deployment Descriptor Stub, and be sure to select all from the drop-down "Bean discovery mode" selection.

The downside is the performance hit since the app will take longer to start up, but that's a tradeoff you could make to avoid recompiling.

Upvotes: 5

Anton Kolyaev
Anton Kolyaev

Reputation: 316

I'm not familiar enough with EJB, but you could try to add @Stateless or @Service annotation to your Service class

package my.simple.app;
@Stateless
//@Service
public class HelloService {
    String createHelloMessage(String name) {
        return "Hello " + name + "!";
    }
}

Edit: Or if you can't modify HelloService as you mentioned after you could use Producer Method http://docs.oracle.com/javaee/6/tutorial/doc/gjdid.html

Upvotes: 0

Related Questions