【问题标题】:What scope do JAX-RS 2 filters have?JAX-RS 2 过滤器有什么范围?
【发布时间】:2013-07-27 11:21:16
【问题描述】:

我正在使用 RestEasy 3.0.2,它是最早的 JAX-RS 2 实现之一,并在 Tomcat 7 中运行我的应用程序。我还通过 WELD 在我的应用程序中使用注入,它通过其 CDI 适配器与 RestEasy 集成.到目前为止一切正常。

现在,我编写了一个 ContainerRequestFilter 的实现,用于在传入请求到达资源之前对其进行身份验证。 JAX-RS 标准规定,对于每一个资源和每一个使用 @Provider 注释进行注释的其他 JAX-RS 组件,都可以进行注入。

这是我的过滤器实现的简化版本:

@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    @Inject
    AuthenticationProvider authenticationProvider;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        authenticationProvider.authenticate(requestContext);
    }
}

注意:AuthenticationProvider@RequestScoped

一般来说,这个解决方案是有效的。正在注入组件,并且正在按预期处理请求。

但我仍然怀疑过滤器的作用域。如果它是应用程序范围的,那么这显然会导致确定性测试无法发现的“有趣”并发问题。

我查看了各种文档、指南和示例,但我发现没有一个使用过滤器注入或说明过滤器范围。

【问题讨论】:

  • 为什么不实现 javax.servlet.Filter 呢?这似乎很适合您想要实现的目标。
  • JAX-RS 过滤器可用作匹配后过滤器,这意味着您可以通过注释或 DynamicFeature 实现将它们应用于特定资源。没有合理的努力,javax.servlet.Filter 也是不可能的。

标签: jakarta-ee jax-rs cdi resteasy


【解决方案1】:

对于RestEasy,答案在RestEasy documentation concerning CDI integration

没有显式定义范围的 CDI bean 是 @Dependent 默认范围内。这个伪作用域意味着 bean 适应 注入它的 bean 的生命周期。正常范围(请求, session, application) 更适合 JAX-RS 组件,因为它们 明确指定组件的生命周期边界。因此, resteasy-cdi 模块通过以下方式更改默认范围:

如果 JAX-RS 根资源没有明确定义范围,则它是 绑定到请求范围。

如果 JAX-RS Provider 或 javax.ws.rs.Application 子类未定义范围 明确地,它绑定到应用程序范围。

因此,使用 @Provider 注释的 JAX-RS 过滤器是 @ApplicationScoped。

文档还说,JAX-RS 提供程序可以通过将适当的注释与任何范围相关联。所以一般来说,可以自定义 JAX-RS 过滤器的范围。

重要的是要注意将@RequestScoped 对象注入@ApplicationScoped 过滤器是安全的。这是因为 CDI 不注入对实际对象的引用,而是注入代理。当在代理上调用一个方法时,该对象的一个​​单独实例将用于幕后的每个请求。

这里是WELD documentation:

4.9。客户端代理

注入 bean 的客户端通常不会直接引用 一个 bean 实例,除非 bean 是一个依赖对象(范围 @依赖)。

假设绑定到应用程序范围的 bean 持有直接 对绑定到请求范围的 bean 的引用。应用范围 bean 在许多不同的请求之间共享。但是,每个请求 应该看到请求范围 bean 的不同实例——当前 一个!

...

因此,除非 bean 具有默认范围 @Dependent,否则 容器必须通过 代理对象。此客户端代理负责确保 接收方法调用的 bean 实例是 与当前上下文相关联。客户端代理还允许 bean 绑定到上下文,例如要序列化的会话上下文 到磁盘而不递归序列化其他注入的 bean。

我使用以下代码来验证这一点(假设 entityManager 在示例中生成为@RequestScoped):

@Provider
public class OtherTestFilter implements ContainerRequestFilter {

    @Inject
    EntityManager entityManager;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        Session session =  (Session) entityManager.getDelegate();
        System.out.println(session.hashCode());
    }
} 

这会为过滤器处理的每个请求提供不同的 session 哈希值。所以理论和实践在这里匹配。

【讨论】:

  • 实际上,它们的作用域完全取决于您 - 只需在类中添加作用域注释,然后看看会发生什么。
  • 你是对的。我在 RestEasy 文档中找到了相应的信息,证实了您的说法。
【解决方案2】:

它的定义方式,看起来像是 DependentScoped。唯一的另一种可能性是静默 RequestScoped,但这不太可能。

【讨论】:

  • 我还假设过滤器是 DependentScoped 如你所说。至少这是有道理的。但是,这对我的回答背后的意图没有影响,即注射本身是否安全。不幸的是,我担心的不够清楚。无论如何感谢您的回答!
  • DependantScope 每次注入时都会创建一个对象的新实例。只要你注射的东西是安全的,你应该没问题。
  • 我发现过滤器只是在初始化的时候才被实例化,以后不会再创建新的实例了。这是故意的。如果您查看过滤器是如何通过 DynamicFeature 注册的,那么您会注意到作者的目标是将注释处理等昂贵的工作转移到过滤器的注册阶段。这只有在过滤器被重用时才有意义,因为您可以将依赖于资源的信息传递给过滤器的状态,该状态必须在整个生命周期中保留。
  • 根据文档,在 RestEasy 中,默认范围实际上是应用程序范围。详情见我的回答。
【解决方案3】:

JAX-RS 2 过滤器有什么范围?

过滤器等提供者默认为singleton


文档是怎么说的

来自Application 文档:

资源类实例的默认生命周期是每个请求。提供者(直接注册或通过功能注册)的默认生命周期是单例。

来自JAX-RS specification

默认情况下,每个提供程序类的单个实例都会为每个 JAX-RS 应用程序实例化。首先调用构造函数,然后注入任何请求的依赖项,然后可以多次(同时)调用适当的提供程序方法,最后使对象可用于垃圾回收。

JAX-RS 规范还提到了以下关于 CDI 集成的内容:

提供者和Application 子类必须是单例或使用应用程序范围。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-30
    • 1970-01-01
    • 2013-05-08
    • 1970-01-01
    • 2018-01-15
    • 1970-01-01
    • 1970-01-01
    • 2016-09-23
    相关资源
    最近更新 更多