如何正确地将CDI Bean注入JAX-RS子资源中

325 阅读4分钟

如何正确地将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);
    }
}

注意:你需要使用ResourceContextinitResource 方法,而不是getResource 方法。getResource 方法从一个类中创建了一个新的 JAX-RS 子资源,但并不保证它也能为其实现 CDI 注入。尽管一些Jakarta EE运行时在你调用getResource 方法时将启用CDI注入,但众所周知,其中一些运行时如OpenLibertyPayara并没有这样做。在未来,当@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();
    }
}