bigywater.tech
Java · 422

JAX-RS Filter:在 HTTP 资源前后插入处理

JAX-RS 过滤器是在 HTTP 资源前后插入处理的标准方式。这篇用一个真实的 Jersey 服务,把请求与响应链的顺序、PreMatching、abortWith、异常映射、名字绑定和请求体读取这些容易混淆的行为逐个量出来。

升序 vs 降序
请求过滤器与响应过滤器按 @Priority 的执行顺序(100 → 400 与 300 → 100 → 10)
403 / 500
abortWith 与过滤器抛异常的 HTTP 状态;两种情况响应过滤器都照常运行
0 字节
请求过滤器读取了请求体但没放回时,资源方法收到的 body 长度(放回之后是 11)

执行顺序

实测 · 本机执行输出
  GET /hello → HTTP 200,响应体 hello
        请求:PreMatching A(100) 路径=hello
        请求:PreMatching B(200) 方法=GET
        请求:Post First(100)
        请求:Post A(300) 已匹配资源方法=hello
        请求:Post Last(400)
        资源:hello() 被调用
        响应:Resp(300) 状态=200
        响应:Resp(100) 状态=200,实体=hello
        响应:TraceEmit(10) 写出 X-Trace(响应过滤器按优先级从大到小执行,10 最后)

PreMatching

实测 · 本机执行输出
  GET /legacy/hello(PreMatching A 改写路径) → HTTP 200,响应体 hello
        请求:PreMatching A(100) 路径=legacy/hello
          PreMatching A 把 legacy/hello 改写成 hello
        请求:PreMatching B(200) 方法=GET
        请求:Post First(100)
        请求:Post A(300) 已匹配资源方法=hello
        请求:Post Last(400)
        资源:hello() 被调用
        响应:Resp(300) 状态=200
        响应:Resp(100) 状态=200,实体=hello
        响应:TraceEmit(10) 写出 X-Trace(响应过滤器按优先级从大到小执行,10 最后)
  POST /echo + X-Method-Override(PreMatching B 把 POST 改成 PUT) → HTTP 200,响应体 put:abc
        请求:PreMatching A(100) 路径=echo
        请求:PreMatching B(200) 方法=POST
          PreMatching B 把 POST 改成 PUT
        请求:Post First(100)
        请求:Post A(300) 已匹配资源方法=put
        请求:Post Last(400)
        资源:put() 被调用,body 长度 3
        响应:Resp(300) 状态=200
        响应:Resp(100) 状态=200,实体=put:abc
        响应:TraceEmit(10) 写出 X-Trace(响应过滤器按优先级从大到小执行,10 最后)
  GET /nope(没有匹配的资源) → HTTP 404,响应体 
        请求:PreMatching A(100) 路径=nope
        请求:PreMatching B(200) 方法=GET
        响应:Resp(300) 状态=404
        响应:Resp(100) 状态=404
        响应:TraceEmit(10) 写出 X-Trace(响应过滤器按优先级从大到小执行,10 最后)

abortWith 与异常

实测 · 本机执行输出
  GET /hello + X-Mode: deny(Post First 调用 abortWith) → HTTP 403,响应体 被过滤器拒绝
        请求:PreMatching A(100) 路径=hello
        请求:PreMatching B(200) 方法=GET
        请求:Post First(100)
          Post First 调用 abortWith(403)
        响应:Resp(300) 状态=403
        响应:Resp(100) 状态=403,实体=被过滤器拒绝
        响应:TraceEmit(10) 写出 X-Trace(响应过滤器按优先级从大到小执行,10 最后)
  GET /hello + X-Mode: throw(Post Last 抛异常) → HTTP 500,响应体 映射后的异常:请求过滤器抛出
        请求:PreMatching A(100) 路径=hello
        请求:PreMatching B(200) 方法=GET
        请求:Post First(100)
        请求:Post A(300) 已匹配资源方法=hello
        请求:Post Last(400)
          Post Last 抛出 IllegalStateException
        响应:Resp(300) 状态=500
        响应:Resp(100) 状态=500,实体=映射后的异常:请求过滤器抛出
        响应:TraceEmit(10) 写出 X-Trace(响应过滤器按优先级从大到小执行,10 最后)
  GET /boom(资源抛异常,有 ExceptionMapper) → HTTP 500,响应体 映射后的异常:资源抛出
        请求:PreMatching A(100) 路径=boom
        请求:PreMatching B(200) 方法=GET
        请求:Post First(100)
        请求:Post A(300) 已匹配资源方法=boom
        请求:Post Last(400)
        资源:boom() 抛出 IllegalStateException
        响应:Resp(300) 状态=500
        响应:Resp(100) 状态=500,实体=映射后的异常:资源抛出
        响应:TraceEmit(10) 写出 X-Trace(响应过滤器按优先级从大到小执行,10 最后)

名字绑定与请求体

实测 · 本机执行输出
  GET /audited(@Audited 绑定的过滤器) → HTTP 200,响应体 audited
        请求:PreMatching A(100) 路径=audited
        请求:PreMatching B(200) 方法=GET
        请求:Post First(100)
        请求:Post A(300) 已匹配资源方法=audited
        请求:Post Last(400)
        请求:Audit 过滤器(名字绑定 @Audited)
        资源:audited() 被调用
        响应:Resp(300) 状态=200
        响应:Resp(100) 状态=200,实体=audited
        响应:TraceEmit(10) 写出 X-Trace(响应过滤器按优先级从大到小执行,10 最后)
  GET /hello(没有 @Audited,不会运行 Audit 过滤器) → HTTP 200,响应体 hello
        请求:PreMatching A(100) 路径=hello
        请求:PreMatching B(200) 方法=GET
        请求:Post First(100)
        请求:Post A(300) 已匹配资源方法=hello
        请求:Post Last(400)
        资源:hello() 被调用
        响应:Resp(300) 状态=200
        响应:Resp(100) 状态=200,实体=hello
        响应:TraceEmit(10) 写出 X-Trace(响应过滤器按优先级从大到小执行,10 最后)
  PUT /echo,过滤器读取了请求体但没放回 → HTTP 200,响应体 put:
        请求:PreMatching A(100) 路径=echo
        请求:PreMatching B(200) 方法=PUT
        请求:Post First(100)
        请求:BodyPeek 读取了请求体 11 字节
        请求:Post A(300) 已匹配资源方法=put
        请求:Post Last(400)
        资源:put() 被调用,body 长度 0
        响应:Resp(300) 状态=200
        响应:Resp(100) 状态=200,实体=put:
        响应:TraceEmit(10) 写出 X-Trace(响应过滤器按优先级从大到小执行,10 最后)
  PUT /echo,过滤器读取后又把字节放回 → HTTP 200,响应体 put:payload-123
        请求:PreMatching A(100) 路径=echo
        请求:PreMatching B(200) 方法=PUT
        请求:Post First(100)
        请求:BodyPeek 读取了请求体 11 字节
          BodyPeek 把读出来的字节重新放回实体流
        请求:Post A(300) 已匹配资源方法=put
        请求:Post Last(400)
        资源:put() 被调用,body 长度 11
        响应:Resp(300) 状态=200
        响应:Resp(100) 状态=200,实体=put:payload-123
        响应:TraceEmit(10) 写出 X-Trace(响应过滤器按优先级从大到小执行,10 最后)

分步演示

JAX-RS 过滤器
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。Jersey 3.1.9(jersey-container-jdk-http + jersey-hk2)、Jakarta RESTful Web Services 3.1,服务端是 JDK 内置的 com.sun.net.httpserver(端口 9780);每个请求的执行轨迹由过滤器写进请求属性并通过 X-Trace-Utf8 响应头带回客户端
    • 没有验证:RESTEasy / CXF 等其它实现的差异、DynamicFeature 动态绑定、ReaderInterceptor / WriterInterceptor 与过滤器的先后、异步响应(@Suspended)下的过滤器、Servlet 容器(Tomcat / Jetty)中的 Servlet Filter 与 JAX-RS 过滤器的顺序、CORS 过滤器的实现
    京ICP备2026067502号-1京公网安备11010502063487号