Jersey 请求/响应拦截器
拦截器主要用来操作实体,操作是通过实体输入输出流来完成。例如对请求实体进行编码。有两种拦截器,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 方法。
与过滤器类似, 拦截器也可以通过命名注解的方式进行绑定, 或者实现DynamicFeature的方式进行动态绑定.
10.5 命名绑定
过滤器与拦截器可以指定名称。绑定后拦截器或过滤器仅仅对特殊的特定的资源方法执行。如果没有进行命名保定,那么过滤器或拦截器被称为全局过滤器或拦截器。
可以使用@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.6 动态绑定
动态绑定是一种以动态的方法将拦截器与过滤器与处理方法关联的方式。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);
- }
- }
- }
10.4 过滤器拦截器的执行顺序
接下来看一下过滤器与拦截器的执行上下文。下面的步骤描述了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的实体读取多次,数据仅仅会被拦截一次,然后解码后的数据被存储于缓冲区中。
10.7 优先级
如果有多个拦截器或过滤器,那么可以通过优先级指定执行的顺序。通过注解@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 :-)");
- }
- }