I have a web application running on a Servlet 3.0 container (Jetty 9.0.4) using JSF 2.2 (Mojorra 2.1.3) & CDI 1.1 (Weld 2.0.3). No full-fledged application server is used. In this application I also have a JAX-RS 2.0 (Jersey 2.2) resource class serving REST requests. I have integrated JAXB binding and also JSON marshalling (Jackson 2.2). I use Maven 3.0.5 for the build management. These are the relevant parts of my project setup:
Maven pom.xml:
...
<dependencies>
<!-- Servlet 3.0 API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Contexts and Dependency Injection for Java EE -->
<dependency>
<groupId>org.jboss.weld.servlet</groupId>
<artifactId>weld-servlet</artifactId>
<version>2.0.3.Final</version>
</dependency>
<!-- JavaServer Faces -->
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.2.2</version>
</dependency>
<!-- JAX-RS RESTful Web Services -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.2</version>
</dependency>
<!-- JSON Mapping Framework -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
...
Deployment Descriptor web.xml:
...
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/jsf/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>my.package.config.RestApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<listener>
<listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>
<listener>
<listener-class>com.sun.faces.config.ConfigureListener</listener-class>
</listener>
<resource-env-ref>
<description>Object factory for the CDI Bean Manager</description>
<resource-env-ref-name>BeanManager</resource-env-ref-name>
<resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
</resource-env-ref>
...
JAX-RS root resource class:
@Path("/person")
public class PersonController
{
@Inject
private PersonService personService;
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public List<Person> getAllPersons()
{
return personService.getAll();
}
@GET
@Path("/{index}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Person getPerson(@PathParam("index") int index)
{
return personService.get(index);
}
@POST
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public void savePerson(Person person)
{
personService.add(person);
}
}
JAX-RS application configuration:
public class RestApplication extends ResourceConfig
{
public RestApplication()
{
// For JSON binding
register(new JacksonFeature());
register(new ApplicationBinder());
packages("my.package.controller");
}
}
JAX-RS injection binding configuration:
public class ApplicationBinder extends AbstractBinder
{
@Override
protected void configure()
{
// Means something like: bind the field at an injection point of type PersonService to an instance of type PersonService
bind(PersonService.class).to(PersonService.class);
}
}
And finally the JSF managed bean:
@Named
@SessionScoped
public class PersonBean implements Serializable
{
private static final long serialVersionUID = 1L;
@Inject
private PersonService personService;
private Person newPerson = new Person();
public List<Person> getAll()
{
return personService.getAll();
}
public Person getNewPerson()
{
return newPerson;
}
public void setNewPerson(Person newPerson)
{
this.newPerson = newPerson;
}
public Gender[] getGenders()
{
return Gender.values();
}
public String saveNewPerson()
{
personService.add(newPerson);
newPerson = new Person();
return "index";
}
}
At the end, I want to be able to use the same application scoped service instances in the REST resource classes as well as in the JSF beans, but I can't get CDI and JAX-RS work together.
The JSF/CDI part works fine, but the injection into the REST resource classes does not really work. I read some articles, where they showed two different approaches to combine CDI and JAX-RS: The first one is to annotate the REST resource class with @ManagedBean
in order for the class to be instantiated by the CDI container and to be managed by the JAX-RS container:
@ManagedBean
@Path("/person")
public class PersonController
{
@Inject
private PersonService personService;
...
The second approach is to give the class a CDI scope, e.g. @RequestScoped
in order for the class to be instantiated and managed by the CDI container.
@Path("/person")
@RequestScoped
public class PersonController
{
@Inject
private PersonService personService;
...
None of the approaches work for me. I always end up with the following exception:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=PersonService,parent=PersonController,qualifiers={}),position=-1,optional=false,self=false,unqualified=null,5643079)
at org.jvnet.hk2.internal.ThreeThirtyResolver.resolve(ThreeThirtyResolver.java:74)
at org.jvnet.hk2.internal.ClazzCreator.resolve(ClazzCreator.java:208)
at org.jvnet.hk2.internal.ClazzCreator.resolveAllDependencies(ClazzCreator.java:231)
at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:328)
at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:454)
at org.glassfish.jersey.process.internal.RequestScope.findOrCreate(RequestScope.java:158)
at org.jvnet.hk2.internal.Utilities.createService(Utilities.java:2296)
at org.jvnet.hk2.internal.ServiceLocatorImpl.getService(ServiceLocatorImpl.java:590)
at org.jvnet.hk2.internal.ServiceLocatorImpl.getService(ServiceLocatorImpl.java:577)
at org.glassfish.jersey.internal.inject.Injections.getOrCreate(Injections.java:172)
But this error disappears when changing the injection binding configuration to:
bind(PersonServiceImpl.class).to(PersonService.class);
Now injection somehow works, but for every REST request I get a new instance of the PersonServiceImpl
injected, even if this service is application scoped. To me this is an indicator, that the JAX-RS component is totally separated from the CDI stuff and lives in a completely different environment or container as the CDI / JSF stuff does.
So I really wonder how to make these two concepts work together in a pure servlet 3.0 container.
I solved my problem.
The problem is, that Jersey JAX-RS implementation uses the HK2 dependency injection framework and this framework is simply not aware of the CDI beans. And by following the idea of the accepted answer in this post, I make the CDI beans available for the HK2 injection bindings and the injection of my application scoped beans works fine now.
But I really wonder why it is so cumbersome to bring together two constituent parts of Java EE.
Update: As G. Demecki mentioned in a comment, this is solution is no longer needed! But it helped me out at the time of asking this question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With