JAX-RS 标准中有两种“拦截”组件,分别是 Filter 和 Interceptor,前者主要面向 Header,后者针对 Body。

Filter

介绍

JAX-RS 根据“角色”和“对象”两个维度分别定义了四种过滤器:

  • ClientRequestFilter:过滤客户端请求
  • ClientResponseFilter:过滤客户端响应
  • ContainerRequestFilter:过滤服务端请求
  • ContainerResponseFilter:过滤服务端响应

Demo

定义 Filter

通过遍历上下文的 Header Map,可以方便的检查、修改 HTTP Header 中的内容,以 ContainerRequestFilter 为例:

@PreMatching
public class MyFilter implements ContainerRequestFilter {
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        MultivaluedMap<String, String> headers = requestContext.getHeaders();
        for (MultivaluedMap.Entry<String, List<String>> entry : headers.entrySet()) {
            StringBuilder sb = new StringBuilder();
            for (String s : entry.getValue()) {
                sb.append(s).append(" ");
            }
            System.out.println(entry.getKey() + ": " + sb.toString());
        }
    }
}

注册到 Provider

providerFactory.getContainerRequestFilterRegistry().registerSingleton(new MyFilter());

启动服务

请求 echo 接口:

echo

@PreMatching

语义

@PreMatching 注解使 Filter 在 Resource 匹配进行之前生效,意思是 在 Container 决定这个 Request 会被分派到哪个 Resource 之前,对 Request 进行过滤

Filter

场景

实际使用场景可以是为了修改某些“请求=>资源”的映射关系,比如统一添加 Header。

验证对比

给上面的 MyFilter 添加 @PreMatching 注解,添加断点,debug 启动服务,请求一个不存在的 endpoint:

nothing

发现断点可以走到:

breakpoint

去掉 @PreMatching,断点无法走到,验证成功。

终止请求

Filter 可以通过 ContainerRequestContext.abortWith(Response) 来中止请求,比如鉴权的场景。

Interceptor

介绍

JAX-RS 定义了两种 Interceptor,分别是 ReaderInterceptor 和 WriterInterceptor,用于拦截处理请求、响应中的消息体。

RESTEasy 3.0 已经不建议使用非 JAX-RS 2.0 标准的 ClientExecutionInterceptor。

在 RESTEasy 中,Interceptor 是在与其对应的 Reader 或 Writer 的 同一个调用栈 执行的,ReaderInterceptor 包装了 MessageBodyReader 的调用,而 WriterInterceptor 包装了 MessageBodyWriter 的调用。

Interceptor 的一般使用场景是编解码、生成签名等。

这里多说一句,即使文档中推荐 Filter 和 Interceptor 各司其职,但是 Filter 的方法入参仍然支持 set InputStream 以达到修改 Body 的目的。

Demo

定义 Interceptor

以使用 ReaderInterceptor “打印请求长度”为例:

public class MyInterceptor implements ReaderInterceptor {
    @Override
    public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
        InputStream inputStream = context.getInputStream();
        System.out.println(inputStream.available());
        context.setInputStream(inputStream);
        return context.proceed();
    }
}

MyInterceptor 实现的 aroundReadFrom() 方法会包裹对 MessageBodyReader.readFrom() 方法的调用。

方法中应该显式调用 ReaderInterceptorContext.proceed() 来触发调用链中的下一个拦截器。在最后的调用结束后,RESTEasy 会触发下一阶段,即 MessageBodyReader.readFrom()

注意,仅当 MessageBodyReader 被需要时,Interceptor 生效,换句话说,如果请求体为空,是不会走 ReaderInterceptor 的。

注册到 Provider

providerFactory.getServerReaderInterceptorRegistry().registerSingleton(new MyInterceptor());

添加入参映射

public Response echo(MyObject myObject) {}
@Data
public class MyObject implements Serializable {
    private String msg;
}

发送请求

{
    "msg": "success"
}

查看结果

interceptor

Interceptor 会在 Filter 之后被调用。