【问题标题】:CDI Injection not possible in a JAX-RS subresource instance created by ResourceContext initResource在由 ResourceContext initResource 创建的 JAX-RS 子资源实例中无法进行 CDI 注入
【发布时间】:2016-07-07 10:23:31
【问题描述】:

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

资源

@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 的经验 是没有注入ReportService rs的实例,导致rs为null,导致NPE。

我的假设是,因为资源类正在执行“new ReportsResource()”,CDI 对 ReportsResource 实例没有可见性,因此 ReportsResource 不是容器管理的。 这个好像和这个问题Inject EJB into JAX-RS 2.0 subresource when subresource is got via ResourceContext的情况一样

我的解决方案有些不同,我选择在 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 实例从 Resource 传递到更类似于“CDI/Java EE”的 SubResource?

【问题讨论】:

标签: java rest jakarta-ee dependency-injection cdi


【解决方案1】:

如果你想将 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();
}

【讨论】:

  • 我看到将@Inject 与 ReportsResource 结合使用的问题是,此类是许多子资源的前端资源。它必须为每个请求创建每个可能的子资源类的实例,而如果在每个子资源的方法中完成新的,则仅实例化满足请求所需的类。
  • 重点是在JavaEE中你不需要使用JAX-RS特定的ResourceContext.initResource,你可以通过CDI注入来启动资源。我不明白您的意思,因为您可能会使用我的第二个示例中的reportsResources.get() 而不是new,它只为该特定方法调用创建实例——与new 相同,但注入了依赖项。跨度>
  • 我知道使用Instance动态创建资源不是很直观,如果注入作为方法参数会更好,但CDI目前不支持。我的意思是这会更好但不起作用(在 Java EE 7 中):public ReportsResource reportsResource(@Inject ReportsResource rr) { return rr;}
  • 我可能记错了,我的印象是@Inject private ReportsResource reportsResource;创建了一个 ReportsResource 的实例,因此如果我对子资源可能需要的每个类都使用此方法,那么对于我没有为此请求调用的子资源,会有很多未使用的实例。
  • CDI 仅在直接注入时为每个注入点创建ReportsResource 的实例(没有Instance)并且ReportsResource 具有@Dependent 范围,这是默认值。如果您将范围更改为其他内容,例如@RequestScoped,单个实例将在处理请求期间被所有注入点重用,并且将在您第一次在注入实例上调用方法时创建。在那之前,只注入一个空代理。如果使用Instanceget(),实例的创建完全由您控制,但同样,作用域实例将被重用。
【解决方案2】:

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

@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可以说更合适。

【讨论】:

    【解决方案3】:

    根据 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

    【讨论】:

    • 我不确定这是否是标准的一部分,但由于 Jersey 是 JAX-RS 的参考实现,我想说 RESTEasy 在这里获得了错误报告!
    • 该链接已损坏。 Jersey 文档的新链接(“第 3 章。JAX-RS 应用程序、资源和子资源”):eclipse-ee4j.github.io/jersey.github.io/documentation/latest/…
    【解决方案4】:

    这对我有用

    资源

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-12-16
      • 2015-05-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多