1. 简介

本文将深入解析 Jersey 框架中过滤器(Filters)和拦截器(Interceptors)的工作原理,并对比二者的核心差异。我们将基于 Jersey 3 进行演示,并在 Tomcat 10 服务器上测试应用。

2. 应用配置

首先在服务器上创建一个简单的资源类:

@Path("/greetings")
public class Greetings {
    @GET
    public String getHelloGreeting() {
        return "hello";
    }
}

接着创建对应的服务器配置类:

@ApplicationPath("/*")
public class ServerConfig extends ResourceConfig {

    public ServerConfig() {
        packages("com.baeldung.jersey.server");
    }
}

如需深入了解 Jersey API 开发,可参考这篇文章。若想学习使用 Jersey 创建 Java 客户端,可查看客户端指南

3. 过滤器

过滤器主要用于修改请求和响应的属性(例如 HTTP 头),可在服务端和客户端使用。⚠️ 无论资源是否找到,过滤器都会执行

3.1 实现服务端请求过滤器

通过实现 ContainerRequestFilter 接口并注册为 Provider 创建请求过滤器:

@Provider
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {
    
    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {
        if (ctx.getLanguage() != null && "EN".equals(ctx.getLanguage()
          .getLanguage())) {
 
            ctx.abortWith(Response.status(Response.Status.FORBIDDEN)
              .entity("Cannot access")
              .build());
        }
    }
}

此过滤器通过调用 abortWith() 拒绝语言为 "EN" 的请求。注意该过滤器在资源匹配后执行

若需在资源匹配前执行,添加 @PreMatching 注解:

@Provider
@PreMatching
public class PrematchingRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {
        if (ctx.getMethod().equals("DELETE")) {
            LOG.info("Deleting request");
        }
    }
}

访问资源时,执行顺序如下:

2018-02-25 16:07:27,800 [http-nio-8080-exec-3] INFO  c.b.j.s.f.PrematchingRequestFilter - prematching filter
2018-02-25 16:07:27,816 [http-nio-8080-exec-3] INFO  c.b.j.s.f.RestrictedOperationsRequestFilter - Restricted operations filter

3.2 实现服务端响应过滤器

实现 ContainerResponseFilter 接口,在响应中添加自定义头:

@Provider
public class ResponseServerFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext, 
      ContainerResponseContext responseContext) throws IOException {
        responseContext.getHeaders().add("X-Test", "Filter test");
    }
}

ContainerRequestContext 参数在此处为只读,因为正在处理响应。

3.3 实现客户端过滤器

客户端过滤器与服务端类似,实现 ClientRequestFilter 为请求添加属性:

@Provider
public class RequestClientFilter implements ClientRequestFilter {

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        requestContext.setProperty("test", "test client request filter");
    }
}

创建 Jersey 客户端测试该过滤器:

public class JerseyClient {

    private static String URI_GREETINGS = "http://localhost:8080/jersey/greetings";

    public static String getHelloGreeting() {
        return createClient().target(URI_GREETINGS)
          .request()
          .get(String.class);
    }

    private static Client createClient() {
        ClientConfig config = new ClientConfig();
        config.register(RequestClientFilter.class);

        return ClientBuilder.newClient(config);
    }
}

✅ 需在客户端配置中注册过滤器。

实现客户端响应过滤器(ClientResponseFilter):

@Provider
public class ResponseClientFilter implements ClientResponseFilter {

    @Override
    public void filter(ClientRequestContext requestContext, 
      ClientResponseContext responseContext) throws IOException {
        responseContext.getHeaders()
          .add("X-Test-Client", "Test response client filter");
    }
}

ClientRequestContext 同样为只读参数。

4. 拦截器

拦截器主要用于处理HTTP 消息体的编组(marshalling)和解组(unmarshalling),可在服务端和客户端使用。⚠️ 拦截器仅在存在消息体时执行,且在过滤器之后

拦截器分两类:ReaderInterceptorWriterInterceptor,服务端和客户端通用。

新增资源用于测试拦截器:

@POST
@Path("/custom")
public Response getCustomGreeting(String name) {
    return Response.status(Status.OK.getStatusCode())
      .build();
}

客户端新增测试方法:

public static Response getCustomGreeting() {
    return createClient().target(URI_GREETINGS + "/custom")
      .request()
      .post(Entity.text("custom"));
}

4.1 实现 ReaderInterceptor

ReaderInterceptor 用于处理入站流,修改服务端请求或客户端响应。服务端实现示例:

@Provider
public class RequestServerReaderInterceptor implements ReaderInterceptor {

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext context) 
      throws IOException, WebApplicationException {
        InputStream is = context.getInputStream();
        String body = new BufferedReader(new InputStreamReader(is)).lines()
          .collect(Collectors.joining("\n"));

        context.setInputStream(new ByteArrayInputStream(
          (body + " message added in server reader interceptor").getBytes()));

        return context.proceed();
    }
}

✅ 必须调用 proceed() 执行链中下一个拦截器。

4.2 实现 WriterInterceptor

WriterInterceptor 处理出站流,用于客户端请求或服务端响应。客户端实现示例:

@Provider
public class RequestClientWriterInterceptor implements WriterInterceptor {

    @Override
    public void aroundWriteTo(WriterInterceptorContext context) 
      throws IOException, WebApplicationException {
        context.getOutputStream()
          .write(("Message added in the writer interceptor in the client side").getBytes());

        context.proceed();
    }
}

✅ 同样需调用 proceed()。拦截器执行完毕后,将调用消息体写入器。

必须在客户端配置中注册拦截器

private static Client createClient() {
    ClientConfig config = new ClientConfig();
    config.register(RequestClientFilter.class);
    config.register(RequestWriterInterceptor.class);

    return ClientBuilder.newClient(config);
}

5. 执行顺序

下图展示了客户端到服务端请求中过滤器和拦截器的执行时机: Jersey 执行流程

核心要点

  1. 过滤器始终先执行
  2. 拦截器在调用消息体读取器/写入器前执行

按本文示例,执行顺序为:

  1. RequestClientFilter
  2. RequestClientWriterInterceptor
  3. PrematchingRequestFilter
  4. RestrictedOperationsRequestFilter
  5. RequestServerReaderInterceptor
  6. ResponseServerFilter
  7. ResponseClientFilter

可通过 @Priority 注解精确控制顺序(用 Integer 指定优先级):

@Provider
@Priority(Priorities.AUTHORIZATION)
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {
    // ...
}

请求按升序执行,响应按降序执行。示例使用了预定义的授权优先级。

6. 名称绑定

前述过滤器/拦截器为全局绑定(对所有请求生效)。也可绑定到特定资源方法,称为名称绑定(Name Binding)。

6.1 静态绑定

创建包含 @NameBinding 元注解的自定义注解:

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloBinding {
}

在资源方法上使用:

@GET
@HelloBinding
public String getHelloGreeting() {
    return "hello";
}

绑定过滤器到该注解:

@Provider
@Priority(Priorities.AUTHORIZATION)
@HelloBinding
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {
    // ...
}

❌ 此过滤器仅对 getHelloGreeting() 生效,不再作用于其他资源。

6.2 动态绑定

通过 DynamicFeature 在启动时配置绑定。新增资源:

@GET
@Path("/hi")
public String getHiGreeting() {
    return "hi";
}

实现动态绑定:

@Provider
public class HelloDynamicBinding implements DynamicFeature {

    @Override
    public void configure(ResourceInfo resourceInfo, FeatureContext context) {
        if (Greetings.class.equals(resourceInfo.getResourceClass()) 
          && resourceInfo.getResourceMethod().getName().contains("HiGreeting")) {
            context.register(ResponseServerFilter.class);
        }
    }
}

✅ 将 getHiGreeting() 方法绑定到 ResponseServerFilter

⚠️ 需移除该过滤器的 @Provider 注解,否则会同时作为全局过滤器和绑定过滤器执行两次。

7. 总结

本文深入探讨了 Jersey 3 中过滤器和拦截器的工作机制及在 Web 应用中的使用方法。关键要点总结:

组件 作用范围 执行时机 典型用途
过滤器 全局/绑定 始终执行 修改头、鉴权、日志
拦截器 全局/绑定 仅当存在消息体时执行 消息体转换、加密

完整示例代码请查看 GitHub 仓库


原始标题:Jersey Filters and Interceptors