在由 ResourceContext initResource 创建的 JAX-RS 子资源实例中不可能进行 CDI 注



我有一个 JAX-RS 资源类,它使用 ResourceContext 为每个资源类型创建子资源实例@Context提供子资源类的路径路由。在此示例中,我正在实例化报告子资源。

资源

@Context
ResourceContext rc;
@Path("reports")
public ReportsResource reportsResource() {
    return rc.initResource(new ReportsResource());
}

子资源需要 ReportService 类的实例(使用 @Stateless 注释定义),自然的解决方案是@Inject它......

报表子资源

@Inject
ReportsService rs;
@GET
@Path("{rptno}")
@Produces(MediaType.APPLICATION_XML)
public Report report(@PathParam("rptno") int rptNumber) throws Exception {
    return rs.getReport(rptNumber);
}

我在 Glassfish 和 WAS Liberty Profile 中使用 Java EE7 的经验是未注入报告服务 rs 的实例,使 rs 为空并导致 NPE。

我的假设是,由于资源类正在执行"新的ReportsResource()",CDI对ReportsResource实例不可见,因此ReportsResource不是容器管理的。这似乎与这个问题相同的情况 当通过资源上下文获取子资源时,将 EJB 注入 JAX-RS 2.0 子资源

我的解决方案有些不同,我选择在 Resource 类中@Inject ReportService,然后在 ReportsResource 构造函数上传递实例。

修改后的资源

@Inject
ReportsSerivce rs;
@Context
ResourceContext rc;
@Path("reports")
public ReportsResource reportsResource() {
    return rc.initResource(new ReportsResource(rs));
}

修改的报表子资源

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);
    }

所以对于我的问题

  1. 我对@Inject失败原因的假设是否正确?
  2. 有没有办法使@Inject在子资源中工作?
  3. 是否有更好的解决方案将ReportService实例从资源传递到子资源,更像"CDI/Java EE"?
如果你想将

CDI bean 注入到 JAX-RS 资源中,我不建议使用 rc.initResource。它所做的只是将字段注入到现有对象中,但它使用 JAX-RS 特定的机制,类似于 JavaEE5 中 EJB 的注入方式,当 CDI 不可用时。

最好使用 CDI 并从代码中删除ResourceContext

例:

资源

@Inject
private ReportsResource reportsResource;
@Path("reports")
public ReportsResource reportsResource() {
    return reportsResource;
}

@RequestScoped主资源,以便为每个请求重新创建主资源。或者,您可以使用 Intance 注入为每个方法调用获取新实例:

@Inject
private Instance<ReportsResource> reportsResources;
@Path("reports")
public ReportsResource reportsResource() {
    return reportsResources.get();
}

请注意,将子资源直接注入根资源可能会导致问题。我正在分享我学到的东西。

@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;
}

以下作品。

/parents/1/children
/parents/1/children;parentName=Kwon

当我们简单地打电话时,我们可能会得到一个NullPointerException

/parents
/parents/1

因为ChildrenResource实例本身的注入发生在它注入ParentResource之前。

在这种情况下,Optional可能会有所帮助

parentName = ofNullable(children)
        .map(v -> v.getMatrixParameters().getFirst("parentName")
        .orElse(null);

但是使用ResourceContext可以说更合适。

根据 Jersey 的文档(JAX-RS 参考实现),如果您希望子资源的生命周期由容器管理,则必须返回类类型而不是它的实例。然后,您可以根据需要管理子资源的生命周期,就像管理任何其他容器托管资源一样。

例如:

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
}

更多信息可以在文档上找到:https://jersey.java.net/documentation/latest/jaxrs-resources.html#d0e2496

这对

我有用

资源

@Path("reports")
public ReportsResource reportsResource() {
    return CDI.current().select(ReportsResource.class).get();
}

子资源

@Inject ReportBean reportBean;
@GET
@Path("/{rptno}")
@Produces(MediaType.APPLICATION_XML)
public Report report(@PathParam("rptno") int rptNumber) throws Exception {
    return reportBean.getReport(rptNumber);
}

最新更新