如何正确地将CDI Bean注入JAX-RS子资源中
雅加达REST(JAX-RS)使用注释定义了它自己的依赖注入。 @Context注释来定义自己的依赖注入。REST资源也支持 CDI injection如果你在REST资源类上启用CDI(例如,使用一个Bean定义的注解,如 @RequestScoped).
但在JAX-RS子资源上,注入并不是开箱即用的。如何创建子资源,使两种注入机制在子资源中也能工作?我来告诉你,这很容易。
如何做到这一点(对于没有耐心的人来说)
- 通过
@Inject注解将子资源注入 JAX-RS 资源中。 - 使用通过
@Context注解注入的ResourceContext对象来初始化子资源。
@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);
}
}
完整的故事
首先,让我们简单地解释一下什么是子资源。它是一个看起来类似于普通 JAX-RS 资源的类,但它不是独立使用的。相反,这个类的一个实例可以从一个 JAX-RS 资源中返回,以将其映射到该资源的一个子路径。因此,一个 JAX-RS 资源可以将特定子路径的处理委托给另一个类。子资源看起来与其它 JAX-RS 资源一样,但是没有指定 [@Path](https://jakarta.ee/specifications/platform/8/apidocs/javax/ws/rs/Path.html)注释。路径是在返回子资源的资源方法上定义的。
// 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();
}
}
如果你访问路径/request ,响应将包含 "这是一个 JAX-RS 资源"。
如果你访问路径/request/subresource ,响应将包含 "这是一个子资源"。
然而,问题是,通过上面的简单例子,在子资源中没有注入工作。不可能向 SubResource 类注入任何东西,任何这样做的尝试都会导致注入的字段的值为null 。它是作为一个普通的Java对象在getSubresource() 方法中创建的,因此它不被任何容器所管理。在这种情况下 [@RequestScoped](https://jakarta.ee/specifications/platform/8/apidocs/javax/enterprise/context/RequestScoped.html)注解在这种情况下会被忽略,任何标有@Inject 或@Context 注解的东西也会被忽略,并保持null 。
注入在 JAX-RS 资源上起作用,因为它们是由 JAX-RS 容器创建的,而不是使用new 关键字。如果资源类上的 CDI 也被启用,Jakarta EE 运行时将首先把 JAX-RS 资源创建为 CDI Bean,然后把它传递给 JAX-RS 容器,后者再进行自己的注入。
如果使用new 创建一个子资源,这些都不会发生,因此我们必须以不同的方式来创建它。
解决方案
需要两个简单的步骤来增加对JAX-RS子资源中两种类型的注入的支持:
- 将子资源注入到 JAX-RS 资源中。这样就可以进行 CDI 注入
- 使用 JAX-RS 容器提供的ResourceContext对象来初始化子资源。这使得 JAX-RS 注入的值可以用以下方式来表示
@Context
为了正确地创建子资源,RestResource 类应该是这样的。
@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);
}
}
注意:你需要使用
ResourceContext的initResource方法,而不是getResource方法。getResource方法从一个类中创建了一个新的 JAX-RS 子资源,但并不保证它也能为其实现 CDI 注入。尽管一些Jakarta EE运行时在你调用getResource方法时将启用CDI注入,但众所周知,其中一些运行时如OpenLiberty和Payara并没有这样做。在未来,当@Context注入被CDI注入取代时,这一点很可能会得到改善,这一点已经在计划之中。
现在你可以同时使用这两种类型的注入,所有的工作都会如期进行。
@RequestScoped
@Produces(MediaType.TEXT_PLAIN)
public class SubResource {
@Inject
SomeCdiBean bean;
@Context
UriInfo uriInfo
@GET
public String getMessage() {
return bean.getMessage() + ", path: " + uriInfo.getPath();
}
}