Abstract
Recently, I was doing some research into JAX-RS and ran into a problem. I attempted to use CDI to @Inject a bean into the JAX-RS resource. It failed miserably. Different attempts produced different failures. Sometimes exceptions occurred during deployment, other times exceptions occurred when invoking the JAX-RS endpoint. After much trial and error, and some asking on Stackoverflow, I found 2 solution. This post describes these 2 solutions to get CDI and JAX-RS working together.
Requirements
I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.
- Java EE 7
- Payara 4.1.1.161
- Java 1.8.0_65_x64
- NetBeans 8.1
- Maven 3.0.5 (Bundled with NetBeans)
Downloads
All of the research & development work I did for this post is available on my GitHub account. Feel free to download or clone the thoth-jaxrs GitHub project.
Exceptions
As soon as I tried to use CDI to @Inject a bean into a JAX-RS @Path resource, my application ran into trouble. I tried resolving the trouble in a lot of different ways but I kept getting either deployment exceptions or runtime exceptions. For reference, here are the exceptions I was typically getting.
Deployment Exception
The deployment exception obviously happened at deployment time. When these happened, the application failed to deploy.
Exception during lifecycle processing
java.lang.Exception: java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: org.apache.catalina.LifecycleException: org.jboss.weld.exceptions.DeploymentException: WELD-001408: Unsatisfied dependencies for type InjectMe with qualifiers @Default at injection point [BackedAnnotatedField] @Inject private org.thoth.jaspic.web.InjectResource.me
Runtime Exception
The runtime exception happened when attempting to invoke the JAX-RS resource with a browser. For this exception, the application deployed without errors, but this one JAX-RS resource wasn’t working.
MultiException stack 1 of 1
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=InjectMe, parent=InjectResource, qualifiers={}, position=-1, optional=false, self=false, unqualified=null, 1000687916))
Resolution 1: beans.xml
This is the first resolution I found. The key factor was adding a beans.xml
file to the web project and configuring beans.xml
with bean-discovery-mode="all"
. This allows CDI to consider all classes for injection. This is not a preferred solution however. For reference, here are all the major files in the project.
beans.xml
<?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>
JAX-RS Application Configuration
import javax.ws.rs.core.Application;
@javax.ws.rs.ApplicationPath("webresources")
public class ApplicationConfig extends Application {
}
JAX-RS Resource
import java.security.Principal;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;
@Path("inject")
public class InjectResource {
@Inject
private InjectMe injectMe;
@GET
@Produces(MediaType.TEXT_HTML)
public String getText(@Context SecurityContext context) {
Principal p = context.getUserPrincipal();
String retval = "";
retval += "<!DOCTYPE html>\n";
retval += "<h3>Thoth</h3>\n";
retval += "<h4>jaxrs-inject</h4>\n";
retval += String.format("<p>injectMe=[%s]</p>\n", injectMe);
return retval;
}
}
Simple bean to inject
import java.io.Serializable;
public class InjectMe implements Serializable {
private static final long serialVersionUID = 158775545474L;
private String foo;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
Resolution 2: Scope Annotations
The second resolution - and the preferred solution - is to annotate the classes with scope annotations. The key factor here is to annotate both the JAX-RS @Path resource and the bean to inject with a scope annotation. This then works with the CDI default discovery mode which is ‘annotated’. Because both these classes are annotated with scope annotations, CDI will automatically discover them without the need for a beans.xml
file. And this is important because we want to avoid having a beans.xml
if possible and configure the application with only annotations. For reference, here are all the major files in the project. In this example, both are annotated with @RequestScope
which makes them discoverable to CDI.
NOTE Thanks to “leet java” and “OndrejM” for responding to my Stackoverflow question. I mistakenly assumed the
@Path
annotation made a class discoverable by CDI and they pointed that out to me, thanks!.
JAX-RS Application Configuration
import javax.ws.rs.core.Application;
@javax.ws.rs.ApplicationPath("webresources")
public class ApplicationConfig extends Application {
}
JAX-RS Resource
import java.security.Principal;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;
@Path("inject")
@RequestScoped
public class InjectResource {
@Inject
private InjectMe injectMe;
@GET
@Produces(MediaType.TEXT_HTML)
public String getText(@Context SecurityContext context) {
Principal p = context.getUserPrincipal();
String retval = "";
retval += "<!DOCTYPE html>\n";
retval += "<h3>Thoth</h3>\n";
retval += "<h4>jaxrs-inject-annotation</h4>\n";
retval += String.format("<p>this=[%s]</p>\n", this);
retval += String.format("<p>injectMe=[%s]</p>\n", injectMe);
return retval;
}
}
Simple bean to inject
import java.io.Serializable;
import javax.enterprise.context.RequestScoped;
@RequestScoped
public class InjectMe implements Serializable {
private static final long serialVersionUID = 158775545474L;
private String foo;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
Summary
This problem and its eventual solution may seem trivial. However, when using tools (like NetBeans) to automatically generate your projects it’s easy to forget about things like annotations and beans.xml
files since so much code is generated for you. So hopefully this will help you save some time if you run into this problem.
References
Remijan, M. (2016, September 22). Is bean-discovery-mode=“all” required to @Inject a bean into a Jersey @Path JAX-RS resource?. Retrieved from http://stackoverflow.com/questions/39648790/is-bean-discovery-mode-all-required-to-inject-a-bean-into-a-jersey-path-jax.
User2764975. (2015 September 18). CDI and Resource Injection with JAX-RS and Glassfish. Retrieved from http://stackoverflow.com/questions/32660066/cdi-and-resource-injection-with-jax-rs-and-glassfish.
No comments:
Post a Comment