原文地址:https://jersey.java.net/documentation/latest/filters-and-interceptors.html#d0e9818
当你想要修改请求或响应参数,如http header。 可以使用过滤器来完成任务。例如,希望添加一个响应头 X-Powered-By 到每个生成的响应中。可以使用responsefilter来完成添加。Filter分为服务器端与客户端filter。
服务器端的Filter有
ContainerRequestFilter
ContainerResponseFilter
客户端Filter有
ClientRequestFilter
ClientResponseFilter
例10.1演示了一个简单的 containerresponse filter ,这个filter会将header添加到每个response中。
例10.1
@Provider
@PreMatching
public class PoweredByResponseFilter implements ContainerResponseFilter{
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
}
}
ResponseFilter必须继承于ContainerResponseFilter,而且必注册为Provider,通过@Provider注解定义的Filter。 ResponseFilter 会被执行,即使资源方法没有执行。Filter()方法有两个参数,ContainerRequest以及ContainerResponse。可能分别用来读取请求参数以及写入响应参数。
下面演示Request filter的使用。
例10.2
@Provider
@PreMatching
public class AuthorizationRequestFilter implements ContainerRequestFilter {
public void filter(ContainerRequestContext requestContext) throws IOException {
SecurityContext securityContext = requestContext.getSecurityContext();
if (securityContext == null || !securityContext.isUserInRole("privileged")) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).entity("User cannot access the resource.").build());
}
}
}
Request Filter与 Response Filter类似。但filter方法中的参数为ContainerRequestContext.Requestfilter在资源方法执行前执行及在response创建前执行。Request filter能够操作请求参数,包括Request Header或Request entity。
Pre-matching仅处理实际请求的资源方法已经被选择,filter才会执行。请求匹配是寻找对应资源方法的过程,这些方法基于请求路径与其它请求参数来执行。Post-matching request filters在当资源方法被选择后被调用,这种filter不会影响资源方法匹配过程。(因为pre-matching可以对请求修改,因此会影响匹配)
将server request filter注解为pre-matching filter.pre-matching filters 是request filters,是在请求匹配开始前执行。因此pre-matching request filters 能够影响处理请求的方法(能对传入的对象修改)。
@Provider
@PreMatching
public class PreMatchingFilter implements ContainerRequestFilter{
public void filter(ContainerRequestContext requestContext) throws IOException {
if(requestContext.getMethod().equals("GET")){
requestContext.setMethod("POST");
}
}
}
PreMatchingFilter仅仅是一个简单的pre-matching filter,来将http get请求方法改为post。当希望将put与post一同处理时,可以通过修改请求方法来完成。接下来处理post请求的方法将会被执行。
Filter主要用来修改request 与 response 参数,比如http headers,URI,或http 请求方法。拦截器主要用来操作实体,操作是通过实体输入输出流来完成。例如对请求实体进行编码。有两种拦截器,ReaderInterceptor 与WriterInterceptor。 Reader拦截器用来操作输入实体流(inbound entity streams)。所以,使用reader拦截器,你可以在服务器端操作请求实体流,在客户端操作响应实体流。(这个实体是从server response读取)。Writer 拦截器,writer 拦截器用于将实体写入”wire”中。在服务端这表示,写出响应实体。在客户端表示为发送到服务器端的请求写请求实体。Writer 与 Reader拦截器 在 消息 readers或writers被执行前执行,其目的是包装将被用在消息reader与writer中的实体流。
下面演示writer拦截器,对整个实体进行GZIP压缩。
public class GZIPWriterInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
final OutputStream outputStream = context.getOutputStream();
context.setOutputStream(new GZIPOutputStream(outputStream));
context.proceed();
}
}
GZIPReaderInterceptor通过GZIPInputStream包装原始的输入流。此后对实体流的读取或得到压缩后的流。aroundReadFrom拦截方法必须返回一个实体。实体是从ReaderInterceptorContext的proceed方法中返回。Proceed方法在内部调用包装的拦截器,这个拦截器必须返回一个实体。被在调用链中的上一个拦截器调用的proceed方法会将用message bodyreader,该方法会反序列化实体,并返回结果。如果需要,每次拦截器都能改变实体,但在大多数的情况下,拦截器仅返回从proceed方法中返回的实体。
上文已经提交,拦截器用来操作实体。与WriterInterceptorContext暴漏的方法类似,ReaderInterceptorContext也引入一些用来修改请求或响应属性的方法。例如,HTTP 头,URIs,以及HTTP 方法。
接下来看一下过滤器与拦截器的执行上下文。下面的步骤描述了JAX-RS客户端发出POS请求到服务器端。服务器接收到实体,并返回响应。GZIP reader与writer拦截器注册在客户端与服务器端。过滤器也注册在服务器端与客户端,并用来修改请求、响应头。
1. 客户端请求调用: 在客户端发起POST请求。
2. ClientRequestFilters: 客户端请求过滤器执行,修改请求头。
3. Client WriterInterceptor:在客户端注册的writer interceptor在MessageBodyWriter执行前执行。它通过GZipOutputStream对实体输出流进行封装。
4. Client MessageBody writer: 客户端的message body writer被执行,它将实体写入写的GZipOutput流中。这个流将数据压缩,并将数据发送到”wire”中。
5. Server:服务器收到请求。收到的数据实体是压缩过的数据,如果直接读,则读取的是压缩的数据。
6. Server pre-matching ContainerRequestFilters: ContainerRequestFilters执行,并可以修改请求,来匹配不同的处理方法。
7. Server: matching: 资源方法匹配完成。(根据请求找到请求处理方法)
8. Server: post-matching ContainerRequestFilters:ContainerRequestFilters post matching filters被执行。这包括所有全局filters(没有命名绑定)的执行及绑定名称的filters的执行。
9. Server ReaderInterceptor:reader 拦截器在服务器端执行,GZIPReaderInterceptor对输入流进行包装,转换为GZipInputStream并将其添加到上下文中。
10. Server MessageBodyReader: server message body reader 被执行,并通过GZipInputStream对实体数据解压缩。这表示,reader将读取解压缩后的数据。
11. 服务器端资源方法被执行:反序列化的实习对象以参数的形式传递给资源方法。方法以response实体的形式返回这个实体。
12. Server ContainerResponseFilters被执行,响应过滤器在服务器端被执行,并将response headers修改。
13. Server WriterInterceptor 在服务器端被执行,并通过GZIPOuptutStream对原始输出流进行包装。
14. Server MessageBodyWriter:message body writer在服务器端被执行,并将实体序列化,写入GZIPOutputStream中。GZIPOutputStream会将数据压缩,随后压缩的数据被发送到客户端。
15. 客户端接收到响应:响应包含压缩的实体数据。
16. Client ClientResponseFilters:客户端相应过滤器被执行,并且修改响应头。
17. 客户端响应返回。the javax.ws.rs.core.Response从请求调用中返回。
18. 客户端代码调用response.readEntity():从响应中提取出实体部分。
19. Client ReaderInterceptor:客户端reader 拦截器在readEntity被调用时执行。拦截器通过GZIPInputStream对实体输入流封装。
20. Client MessageBodyReaders:客户端消息体reader被调用,并从GZIPInputStream中读取解压缩后的数据,并将这些数据反序列化。
21. 客户端:方法readEntity()返回实体。
需要注意的是在上述的场景中,reader与writer拦截器仅仅当实体存在时才会被调用。(当没有实体流要被写时,封装实体流没有意义)。
message body reader有同样的行为。拦截器在message body reader/writer之前执行,在实体被读或写之前拦截器可以对实体进行包装。
当拦截器并没有在messagebodyreader/writers执行前执行,将会出现异常。当对使用内部缓冲区的客户端response的实体读取多次,数据仅仅会被拦截一次,然后解码后的数据被存储于缓冲区中。
过滤器与拦截器可以指定名称。绑定后拦截器或过滤器仅仅对特殊的特定的资源方法执行。如果没有进行命名保定,那么过滤器或拦截器被称为全局过滤器或拦截器。
可以使用@NameBinding注解,将过滤器或拦截器可以被分配给一个资源方法。这个注解以元注解的形式使用,可以注解其它注解。例子如下:
...
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.zip.GZIPInputStream;
import javax.ws.rs.GET;
import javax.ws.rs.NameBinding;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
...
// @Compress annotation is the name binding annotation
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {}
@Path("helloworld")
public class HelloWorldResource {
@GET
@Produces("text/plain")
public String getHello() {
return "Hello World!";
}
@GET
@Path("too-much-data")
@Compress
public String getVeryLongString() {
String str = ... // very long string
return str;
}
}
// interceptor will be executed only when resource methods
// annotated with @Compress annotation will be executed
@Compress
public class GZIPWriterInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
final OutputStream outputStream = context.getOutputStream();
context.setOutputStream(new GZIPOutputStream(outputStream));
context.proceed();
}
}
上述代码定义了注解@Compress,并用在方法getVeryLongString()上,以及GZIPWriterInterceptor拦截器上。拦截器仅在被注解的方法执行时才执行。应用程序中可以有多个命名绑定注解。当provider(拦截器与过滤器)通过命名注解注解后,它仅仅在标注有对应注解的方法被执行后才执行。
动态绑定是一种以动态的方法将拦截器与过滤器与处理方法关联的方式。10.5中的绑定使用了静态方法,当改变绑定时,需要重新编译代码。通过动态绑定,你可以在程序初始化时实现绑定的代码。
...
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.container.DynamicFeature;
...
@Path("helloworld")
public class HelloWorldResource {
@GET
@Produces("text/plain")
public String getHello() {
return "Hello World!";
}
@GET
@Path("too-much-data")
public String getVeryLongString() {
String str = ... // very long string
return str;
}
}
// This dynamic binding provider registers GZIPWriterInterceptor
// only for HelloWorldResource and methods that contain
// "VeryLongString" in their name. It will be executed during
// application initialization phase.
public class CompressionDynamicBinding implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if (HelloWorldResource.class.equals(resourceInfo.getResourceClass())
&& resourceInfo.getResourceMethod()
.getName().contains("VeryLongString")) {
context.register(GZIPWriterInterceptor.class);
}
}
}
如果有多个拦截器或过滤器,那么可以通过优先级指定执行的顺序。通过注解@Priority可以定义优先级。注解接受一个整数作为优先级。过滤器与拦截器执行顺序是按优先级(升序)顺序执行。因此@Priority(1000)或在@Priority(2000)之前执行。而优先级用在响应处理时候,顺序正好相反。@Priority(2000)将在@Priority(1000)之前执行。
...
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
...
@Priority(2000)
public class ResponseFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
}
}