How to properly inject CDI beans into JAX-RS sub-resources
Jakarta REST (JAX-RS) defines it’s own dependency injection using the @Context
annotation. REST resources also support CDI injection
if you enable CDI on the REST resource class (e.g. using a bean-defining annotation like @RequestScoped
).
But injection doesn’t work out of the box on JAX-RS sub-resources. How to create sub-resources so that both injection mechanisms work also in sub-resources? I’ll show you, it’s very easy.
How to do it (for the impatient)
- Inject the sub-resource into the JAX-RS resource via the
@Inject
annotation - Initialize the sub-resource using the ResourceContext object injected via the
@Context
annotation
@Path("request") @RequestScoped public class RestResource { @Inject // inject subresource as CDI bean SubResource<strong> </strong>subResource; @Context // inject from JAX-RS container ResourceContext resourceContext; @Path("subresource") public SubResource getSubresource() { return resourceContext.initResource(subResource); } }
Full story
First, let’s briefly explain what a sub-resource is. It’s a class that looks similar to a usual JAX-RS resource but it’s not used standalone. Instead, an instance of this class can be returned from a JAX-RS resource to map it to a subpath of that resource. Therefore a JAX-RS resource can delegate processing of a specific subpath to another class. Sub resources look like any other JAX-RS resources but don’t specify the @Path
annotation. The path is defined on the resource method that returns the sub resource:
// Sub-resource - no @Path annotation on the class @RequestScoped @Produces(MediaType.TEXT_PLAIN) public class SubResource { @GET public String getMessage() { return "This is a sub-resource."; } }
@Path("request") // defines the path "request" for this resource @RequestScoped @Produces(MediaType.TEXT_PLAIN) public class RestResource { @GET public String getMessage() { return "This is a JAX-RS resource."; } @Path("subresource") // defines the subpath for the sub-resource: "request/subresource" public SubResource getSubresource() { return new SubResource(); } }
If you access path /request
, the response will contain “This is a JAX-RS resource.”
If you access path /request/subresource
, the response will contain “This is a sub-resource.”
However, the catch is that with the simple example above, no injection works in the subresource. it’s not possible to inject anything into the SubResource class, any atempt to do so will result in null
values for injected fields. It’s created as a plain Java object in the getSubresource()
method and thus it’s not managed by any container. The @RequestScoped
annotation is ignored in this case and anything marked with the @Inject
or @Context
annotation would be also ignored and remain null
.
Injection works on JAX-RS resources because they are created by the JAX-RS container and not using the new
keyword. If CDI is also enabled on the resource class, Jakarta EE runtime will first create the JAX-RS resource as a CDI bean and then pass it to the JAX-RS container, which then does its own injection.
None of this happens if a sub resource is created using new
, therefore we must create it in a different way.
Solution
2 simple steps are needed to add support for both types of injection in JAX-RS sub-resources:
- Inject the sub-resource into the JAX-RS resource. This enables the CDI injection
- Initialize the sub-resource using the ResourceContext object provided by the JAX-RS container. This enables JAX-RS injection for values annotated with
@Context
This is how the RestResource
class should look like to create the sub-resource properly:
@Path("request") @RequestScoped @Produces(MediaType.TEXT_PLAIN) public class RestResource { @Inject // inject subresource as CDI bean SubResource<strong> </strong>subResource; @Context // inject from JAX-RS container ResourceContext resourceContext; @GET public String getMessage() { return "This is a JAX-RS resource."; } @Path("subresource") public SubResource getSubresource() { return resourceContext.initResource(subResource); } }
NOTE: You need to use the
initResource
method ofResourceContext
and not thegetResource
method. ThegetResource
method creates a new JAX-RS sub-resource from a class but it’s not guaranteed that it also enables CDI injection for it. Although some Jakarta EE runtimes will enable CDI injection if you call thegetResource
method, it’s known that some of them like OpenLiberty and Payara don’t do it. In the future, this will very probably be improved when the @Context injection will be replaced by CDI injection, which is already planned.
Now you can use both types of injection and all will work as expected:
@RequestScoped @Produces(MediaType.TEXT_PLAIN) public class SubResource { @Inject SomeCdiBean bean; @Context UriInfo uriInfo @GET public String getMessage() { return bean.getMessage() + ", path: " + uriInfo.getPath(); } }
Published on Java Code Geeks with permission by Ondrej Mihalyi, partner at our JCG program. See the original article here: How to properly inject CDI beans into JAX-RS sub-resources Opinions expressed by Java Code Geeks contributors are their own. |