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),可在服务端和客户端使用。⚠️ 拦截器仅在存在消息体时执行,且在过滤器之后。
拦截器分两类:ReaderInterceptor
和 WriterInterceptor
,服务端和客户端通用。
新增资源用于测试拦截器:
@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. 执行顺序
下图展示了客户端到服务端请求中过滤器和拦截器的执行时机:
核心要点:
- 过滤器始终先执行
- 拦截器在调用消息体读取器/写入器前执行
按本文示例,执行顺序为:
RequestClientFilter
RequestClientWriterInterceptor
PrematchingRequestFilter
RestrictedOperationsRequestFilter
RequestServerReaderInterceptor
ResponseServerFilter
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 仓库。