I have a JAX-RS resource class that provides path routing to sub resource classes using @Context ResourceContext to create sub resource instances for each resource type. In this example I am instantiating a reporting sub resource.
Resource
@Context
ResourceContext rc;
@Path("reports")
public ReportsResource reportsResource() {
return rc.initResource(new ReportsResource());
}
The sub resource needs an instance of a ReportService class (defined with the @Stateless annotation), the natural solution would be to @Inject it ...
Report SubResource
@Inject
ReportsService rs;
@GET
@Path("{rptno}")
@Produces(MediaType.APPLICATION_XML)
public Report report(@PathParam("rptno") int rptNumber) throws Exception {
return rs.getReport(rptNumber);
}
My experience using Java EE7 with both Glassfish and WAS Liberty Profile
is that an instance of ReportService rs is not injected, leaving rs as null and causing a NPE.
My assumption is that because the resource class is doing a "new ReportsResource()", CDI has no visibility to the ReportsResource instance and so ReportsResource is not container managed.
This seems to be the same situation as this question Inject EJB into JAX-RS 2.0 subresource when subresource is got via ResourceContext
My solution is somewhat different, I chose to @Inject ReportService in the Resource class, then pass the instance on the ReportsResource constructor.
Modified Resource
@Inject
ReportsSerivce rs;
@Context
ResourceContext rc;
@Path("reports")
public ReportsResource reportsResource() {
return rc.initResource(new ReportsResource(rs));
}
Modified Report Subresource
public class ReportsResource {
private ReportsSerivce rs;
public ReportsResource(ReportsSerivce rs) {
this.rs = rs;
}
@Context
HttpHeaders headers;
@GET
@Path("{rptno}")
@Produces(MediaType.APPLICATION_XML)
public Report report(@PathParam("rptno") int rptNumber) throws Exception {
return rs.getReport(rptNumber);
}
So to my questions
- Is my assumption about why @Inject fails correct?
- Is there any way to make @Inject work in the sub resource?
- Is there a better solution to passing the ReportService instance from Resource to SubResource that is more "CDI/Java EE" like?
If you want to inject CDI beans into JAX-RS resources, I don't recommend using rc.initResource. All it does is injection of fields into existing object, but it uses JAX-RS specific mechanism, similar to how injection worked for EJBs in JavaEE5, when CDI was not available.
It is better to use CDI and remove ResourceContext
from your code.
Example:
Resource
@Inject
private ReportsResource reportsResource;
@Path("reports")
public ReportsResource reportsResource() {
return reportsResource;
}
The master resource should be @RequestScoped
so that it gets recreated for each request. Or you may inject using Intance to get new instance for every method call:
@Inject
private Instance<ReportsResource> reportsResources;
@Path("reports")
public ReportsResource reportsResource() {
return reportsResources.get();
}
Note that injecting the sub resource directly into the root resource may cause a problem. I'm sharing what I've learnt.
@Path("parents")
class ParentsResource {
@Path("/{parentId: \\d+}/{children: children}");
public ChildrenResource resourceChildren() {
return childrenResource;
}
@Inject
private ChildrenResource childrenResource;
}
class ChildrenResource {
@PostConstruct
private void onPostConstruct() {
parentName = children.getMatrixParameters().getFirst("parentName");
}
@PathParam("children");
private PathSegment children; // may or may not be null
private String parentName;
}
Following works.
/parents/1/children
/parents/1/children;parentName=Kwon
And we might got a NullPointerException
when we simple call
/parents
/parents/1
Because the injection of the ChildrenResource
instance itself happens before it injected into the ParentResource
.
In this case, Optional
might help
parentName = ofNullable(children)
.map(v -> v.getMatrixParameters().getFirst("parentName")
.orElse(null);
But using ResourceContext
can be said more proper.
According to the documentation of Jersey (JAX-RS reference implementation) if you want your sub-resource's lifecycle to be managed by container, you have to return the class type and not an instance of it. You can then manage the lifecycle of sub-resource as you wish like any other container managed resource.
For example:
import javax.inject.Singleton;
@Path("/item")
public class ItemResource {
@Path("content")
public Class<ItemContentSingletonResource> getItemContentResource() {
return ItemContentSingletonResource.class;
}
}
@Singleton
public class ItemContentSingletonResource {
// this class is managed in the singleton life cycle
}
more info could be found on docs: https://jersey.java.net/documentation/latest/jaxrs-resources.html#d0e2496
This works for me
Resource
@Path("reports")
public ReportsResource reportsResource() {
return CDI.current().select(ReportsResource.class).get();
}
Subresource
@Inject ReportBean reportBean;
@GET
@Path("/{rptno}")
@Produces(MediaType.APPLICATION_XML)
public Report report(@PathParam("rptno") int rptNumber) throws Exception {
return reportBean.getReport(rptNumber);
}