(转)Jersey2.x对REST请求处理流程的分析

一个REST请求,始于一个RESTful Web Service资源的地址,终于一个可接受的对资源的表述(比如JSON)。

因此,流程分析的关键点有2个:

  • 将请求地址对应到资源类的相应方法上,并触发该方法。
  • 将返回值转换成请求所需要的表述,并返回给客户端。

我们使用Eclipse的断点调试服务器端(代码对应本例https://github.com/feuyeux/jax-rs2-guide/),使用cURL脚本作为客户端。

1 cURL测试

curl -H “Accept:application/json” http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/books/book?id=1

该脚本发送了一个REST GET请求,地址是:

http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/books/book?id=1

对应的服务器端方法是:

com.example.resource.BookResource.getBookByQuery(Integer)

要求返回的类型是JSON:

Accept:application/json

2 Jersey2.x流程

2.1 请求地址到REST方法

2.1.1 ServletContainer.service

ServletContainer.service(HttpServletRequest, HttpServletResponse) line: 248

  • baseUri http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/
  • requestUri http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/books/book?id=1

ServletContainer是HttpServlet的子类,位于Jersey容器包(.m2\repository\org\glassfish\jersey\containers\jersey-container-servlet-core\2.2\jersey-container-servlet-core-2.2.jar)。我们知道,Servlet的service方法是请求的入口,作为子类,HTTP任何方法的请求都要先经过该类的service方法。

断点堆栈中,重要的两个变量是请求地址信息的baseUri和requestUri。

dependencies 

Jersey包依赖关系图

ServletContainer.service(URI, URI, HttpServletRequest, HttpServletResponse) line: 372

  • requestContext.header {user-agent=[curl/7.26.0], host=[localhost:8080], accept=[application/json]}

在容器层级,请求上下文变量除了包括请求地址信息外,还包括请求头信息。这里我们关注accept信息。

2.1.2 对应方法

ApplicationHandler.handle(ContainerRequest) line: 982

ServerRuntime.process(ContainerRequest) line: 211 final Runnable task

new Runnable() {
 public void run() {
  final ContainerRequest data = Stages.process(request, requestProcessingRoot, endpointRef);
  final Endpoint endpoint = endpointRef.get();
  • endpoint ResourceMethodInvoker (id=2553)
    public com.example.domain.Book com.example.resource.BookResource.getBookByQuery(java.lang.Integer)

从上面的代码片段,可以看到请求被对应到了一个Endpoint对象,该对象是一个资源方法Invoker,从断点堆栈中可以看到,其内容就是我们期待的那个方法,此后的invoke将调用这个对应REST请求的处理方法。

2.1.3 调用方法

ResourceMethodInvoker.invoke(ContainerRequest, Object) line: 353
dispatcher.dispatch(resource, requestContext);

JavaResourceMethodDispatcherProvider$TypeOutInvoker(AbstractJavaResourceMethodDispatcher).invoke(Object, Object…) line: 158
invokeMethodAction.run();

ResourceMethodInvocationHandlerFactory$1.invoke(Object, Method, Object[]) line: 81

从堆栈中可以轻松定位这个方法:**BookResource.getBookByQuery(Integer) line: 71**

到此,请求地址到方法调用的流程就结束了。在处理业务逻辑方法BookResource.getBookByQuery后,Jersey将开始处理响应信息。

2.2 REST方法到表述

JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(Object, Request) line: 198

  • o Book (id=2686)
  • Response response = Response.ok().entity(o).build();

REST请求的返回信息应包括表述信息和HTTP响应代码(通常一个成功的请求应该得到200 OK)。这就是上面代码所做的事情。在设置好response的entity后,需要将该entity对象转化成请求所接受的表述,流程如下。

2.2.1 写向response

ServerRuntime$Responder.process(ContainerResponse) line: 351ServerRuntime$Responder.writeResponse(ContainerResponse) line: 486

  • entity Book (id=7076)
  • executor WriterInterceptorExecutor (id=7125)

从上面的堆栈中可以看到,这个阶段重点的两个对象是返回对象和写处理对象,就是使用后者将前者恰当地写入response中。

2.2.2 JSON表述

REST的表述不局限于JSON,这里我们以JSON作为常用的表述类型为例,来讨论Jersey的表述处理。 Jersey对JSON的支持有4种方式,在第3章会有详细的讲解,本例使用的是EclipseLink项目的MOXy。其底层依赖是JPA的实现之一(另一个著名的JPA实现是JBoss项目下大名鼎鼎的Hibernate)。

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-moxy</artifactId>
        <version>${jersey.version}</version>
    </dependency>

moxy

MOXy依赖关系图

在下面的堆栈中,可以看到JAXB的身影,因为MOXy同样实现了JAXB,其内部实现是以XML的Marshal方式处理对象(OXM:Object-XML-Mapping),然后转化为JSON数据(XML-2-JSON)。

JsonWithPaddingInterceptor.aroundWriteTo(WriterInterceptorContext) line: 91

WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorContext) line: 188

ConfigurableMoxyJsonProvider(MOXyJsonProvider).**writeTo**(Object, Class<?>, Type, Annotation[], MediaType, MultivaluedMap<string,object style=”margin: 0px; padding: 0px;”>, OutputStream) line: 782

  • object Book (id=7076)
  • type Class (com.example.domain.Book) (id=568)
  • genericType Class (com.example.domain.Book) (id=568)
  • annotations Annotation3
  • mediaType AcceptableMediaType (id=7120)
  • httpHeaders StringKeyIgnoreCaseMultivaluedMap (id=7121)
  • entityStream CommittingOutputStream (id=7124)

开始从对象转化为JSON:

JAXBMarshaller.marshal(Object, OutputStream) line: 395

  • object Book (id=7076)
  • outputStream CommittingOutputStream (id=7124)

XMLMarshaller(XMLMarshaller<abstract_session,context,descriptor,media_type,namespace_prefix_mapper,object_builder style=”margin: 0px; padding: 0px;”>).marshal(Object, OutputStream, ABSTRACT_SESSION, DESCRIPTOR) line: 852

XMLMarshaller(XMLMarshaller<abstract_session,context,descriptor,media_type,namespace_prefix_mapper,object_builder style=”margin: 0px; padding: 0px;”>).marshal(Object, Writer, ABSTRACT_SESSION, DESCRIPTOR) line: 1031

  • object Book (id=7305)
  • writer OutputStreamWriter (id=7310)
    -> BufferedWriter (id=7333)

XMLMarshaller(XMLMarshaller<abstract_session,context,descriptor,media_type,namespace_prefix_mapper,object_builder style=”margin: 0px; padding: 0px;”>).marshal(Object, MarshalRecord, ABSTRACT_SESSION, DESCRIPTOR, boolean) line: 583

  • object Book (id=7305)
  • marshalRecord JSONWriterRecord (id=7342)
  • session DatabaseSessionImpl (id=7351)
  • descriptor XMLDescriptor (id=7353)
  • isXMLRoot false

几经辗转,写入对象变成了MarshalRecord。

JSONWriterRecord.startDocument(String, String) line: 171

TreeObjectBuilder.marshalAttributes(MarshalRecord, Object, CoreAbstractSession) line: 122

XPathObjectBuilder.marshalAttributes(MarshalRecord, Object, CoreAbstractSession) line: 552

这里是可以想见的流程…

ServerRuntime$Responder.processResponse(ContainerResponse) line: 362

最后是将response返回并释放。流程到此结束。

转自:http://feuyeux.iteye.com/blog/1938910

Jersey 过滤器

Jersey的过滤器分为:

ContainerRequestFilter: 请求阶段的过滤

ContainerResponseFilter: 响应阶段的过滤

一、使用注解名称绑定

1.1 创建绑定的注解

使用@NameBinding注解,可以定义一个运行时的自定义注解,该注解可以用于定义类级别名称和泪的方法。例如,我们定义一个用户访问的注解。

@NameBinding            //标识名称绑定的注解
@Target({ElementType.TYPE, ElementType.METHOD}) //表示该注解可以使用在类和方法上。
@Retention(value = RetentionPolicy.RUNTIME)
public @interface UserLogger {
}

上面代码我们定义了一个名称绑定注解UserLogger。

1.2 注解绑定过滤器

创建了注解之后,我们需要将注解和Jersey中的Provider 组件绑定,示例中我们使用的是过滤器。

@Provider
@UserLogger
@Priority(Priorities.USER)
public class LoggerFilter implements ContainerRequestFilter ,ContainerResponseFilter{
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
System.out.println("访问请求日志过滤器执行了>>>>>>>>>>>>");
}
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
System.out.println("访问响应日志过滤器执行了>>>>>>>>>>>");
}
}

@Provider 注解Jersey注解,Jersey扫描到该注解,就会创建对应的组件对象。 @UserLogger 就是我们自定义的名称绑定注解 @Priority 是用于表示该过滤器的执行顺序,其中参数为long类型,对于请求过滤器,该数值越小越先执行,响应过滤器则相反。 ContainerRequestFilter 为请求过滤器 ContainerResponseFilter 为响应过滤器 在请求和响应的过滤方法中,我们简单的打印输出。 需要注意的是,我们创建了过滤器之后需要在Jersey中进行声明,我们在Jerysey的ResourceConfig 子类中,注册该过滤器,注册有两种方式,一种是扫描包的形式,这时需要在过滤器上加上@Provider注解,另一种是直接注册该过滤器。

packages("com.xxxx.xxxx.xxxx.filter"); //扫描包的形式 过滤器所在的包

register(LoggerFilter.class); //直接注册过滤器

1.3 注解绑定接口

上面我们创建好注解和过滤器之后,需要将在我们需要使用过滤器的接口方法上使用注解。

@Path("/test")
public class TestResource {
@GET
@Path("/1")
public String test1(){
return "不带过滤器";
}
@GET
@Path("/2")
@UserLogger
public String test2(){
return "带过滤器";
}
}

我们创建了两个接口,一个路径是/test/1 没有使用过滤器,一个路径是/test/2使用注解,按照我们的设计,当访问./test/1时,日志过滤器不起作用,访问/test/2时日志过滤器起作用。

二、动态绑定

上面介绍的名称保定的形式需要通过自定义注解的形式来实现过滤器绑定,而动态绑定则不需要新增注解,而是需要编码的形式,实现动态绑定。动态绑定需要实现动态特征接口javax.ws.rs.container,DynamiFeature,定义扩展点方法,请求方法类型等匹配信息,在运行期,一旦Provider匹配到当前处理类或方法,面向切面的Provider方法就是触发。

2.1 实现动态绑定特征

public class LoggerDynaimcFeature implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
String name = resourceInfo.getResourceMethod().getName();
if("test2".equals(name)){
context.register(LoggerFilter.class);
}
}
}

上面的代码实现的是当我们访问的是test2方法时,就会注册LoggerFilter,实现该过滤器的方法。    我们需要在同样我们需要注册该动态特征来    

  1. register(LoggerDynaimcFeature .class);

   这时我们重新启动项目时,分别访问test/1和test/2,就会看到只有test2时,日志过滤器才会起作用。    三. 名称绑定和动态绑定对比    动态绑定相比于名称绑定,不需要自定义注解,使用纯编码的形式实现。    名称绑定相比于动态绑定使用范围更广,因为我们使用注解的方式,可以对任意资源,任意方法进行控制。而使用动态绑定的形式,我们需要在动态特征类进行对应的匹配,适用范围较窄。

Jersey 方法拦截器

如果我们需要控制用户对接口的访问,例如登陆控制,权限控制等,就需要使用方法拦截器。
    由于Jersey中的AOP是基于HK2框架实现的,所以对应的拦截器的功能也是由HK2框架实现。
    现在我们模拟实现一个登陆拦截器的功能。
3.1 创建自定义注解
  1. @Documented
  2. @Target({ElementType.METHOD, ElementType.TYPE})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. public @interface LoginTest {
  5. }
 
3.2 方法拦截器
  1. @LoginTest
  2. public class LoginTestMethodInterceptor implements MethodInterceptor {
  3. @Override
  4. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  5. return "没有权限访问";
  6. }
  7. }
    
3.3 方法拦截器的绑定
    Jersey在调用方法拦截器的时候,需要InterceptionService的实现。
   该接口中有三个方法,在执行对应的接口方法之前会调用getMethodInteceptors()方法,获取对应的拦截器,并执行拦截器。
public class JerseyInterceptor implements InterceptionService {
private static Map<Annotation, MethodInterceptor> map = new HashMap<>();
static{
Annotation[] annotations = LoginTestMethodInterceptor.class.getAnnotations();
for(Annotation annotation : annotations){
map.put(annotation, new LoginTestMethodInterceptor());
}
}
@Override
public Filter getDescriptorFilter() {
return new Filter() {
public boolean matches(Descriptor descriptor) {
return true;
}
};
}
@Override
public List<MethodInterceptor> getMethodInterceptors(Method method) {
Annotation[] annotations = method.getAnnotations();
List<MethodInterceptor> list = new ArrayList<>();
for (Annotation annotation :annotations){
if(map.get(annotation) != null){
list.add(map.get(annotation));
}
}
return list;
}
@Override
public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> constructor) {
return null;
}
}
上面代码可以看到,我们是将注解与拦截器绑定,通过方法上的注解,获取方法对应的拦截器,并执行拦截器。 我们创建的拦截服务实现,需要与拦截服务进行绑定,这时需要AbstracctBinding对象的实现‘。
public class JerseyBinding extends AbstractBinder {
@Override
protected void configure() {
this.bind(JerseyInterceptor.class).to(InterceptionService.class).in(Singleton.class);
}
}

为了使绑定生效,我们需要定义特征类,用于注册该绑定

 
  1. public class JerseyFeature implements Feature {
  2. @Override
  3. public boolean configure(FeatureContext context) {
  4. context.register(new JerseyBinding());
  5. return true;
  6. }
  7. }
同样我们需要在ResourceConfig中注册该特征类
  1. register(JerseyFeature.class);
3,4 测试
    测试接口如下
 
  1. @Path("/test")
  2. public class TestResource {
  3. @Path("/1")
  4. @GET
  5. public String test1(){
  6. return "不带拦截器";
  7. }
  8. @Path("/2")
  9. @GET
  10. @LoginTest
  11. public String test2(){
  12. return "不带拦截器";
  13. }
  14. }
我们访问test/1的时候可以看到不带拦截器,访问test/2的时候可以看到没有访问权限

Jersey 请求/响应拦截器

拦截器主要用来操作实体,操作是通过实体输入输出流来完成。例如对请求实体进行编码。有两种拦截器,ReaderInterceptor 与WriterInterceptor。 Reader拦截器用来操作输入实体流(inbound entity streams)。所以,使用reader拦截器,你可以在服务器端操作请求实体流,在客户端操作响应实体流。(这个实体是从server response读取)。Writer 拦截器,writer 拦截器用于将实体写入”wire”中。在服务端这表示,写出响应实体。在客户端表示为发送到服务器端的请求写请求实体。Writer 与 Reader拦截器 在  消息 readers或writers被执行前执行,其目的是包装将被用在消息reader与writer中的实体流。

下面演示writer拦截器,对整个实体进行GZIP压缩。

[java]  view plain  copy
  1. public class GZIPWriterInterceptor implements WriterInterceptor {  
  2.    
  3.     @Override  
  4.     public void aroundWriteTo(WriterInterceptorContext context)  
  5.                     throws IOException, WebApplicationException {  
  6.         final OutputStream outputStream = context.getOutputStream();  
  7.         context.setOutputStream(new GZIPOutputStream(outputStream));  
  8.         context.proceed();  
  9.     }  
  10. }  

GZIPReaderInterceptor通过GZIPInputStream包装原始的输入流。此后对实体流的读取或得到压缩后的流。aroundReadFrom拦截方法必须返回一个实体。实体是从ReaderInterceptorContext的proceed方法中返回。Proceed方法在内部调用包装的拦截器,这个拦截器必须返回一个实体。被在调用链中的上一个拦截器调用的proceed方法会将用message bodyreader,该方法会反序列化实体,并返回结果。如果需要,每次拦截器都能改变实体,但在大多数的情况下,拦截器仅返回从proceed方法中返回的实体。

上文已经提交,拦截器用来操作实体。与WriterInterceptorContext暴漏的方法类似,ReaderInterceptorContext也引入一些用来修改请求或响应属性的方法。例如,HTTP 头,URIs,以及HTTP 方法。

 与过滤器类似, 拦截器也可以通过命名注解的方式进行绑定, 或者实现DynamicFeature的方式进行动态绑定.

10.5 命名绑定

过滤器与拦截器可以指定名称。绑定后拦截器或过滤器仅仅对特殊的特定的资源方法执行。如果没有进行命名保定,那么过滤器或拦截器被称为全局过滤器或拦截器。

可以使用@NameBinding注解,将过滤器或拦截器可以被分配给一个资源方法。这个注解以元注解的形式使用,可以注解其它注解。例子如下:

[java]  view plain  copy
  1. …  
  2. import java.lang.annotation.Retention;  
  3. import java.lang.annotation.RetentionPolicy;  
  4. import java.util.zip.GZIPInputStream;  
  5.    
  6. import javax.ws.rs.GET;  
  7. import javax.ws.rs.NameBinding;  
  8. import javax.ws.rs.Path;  
  9. import javax.ws.rs.Produces;  
  10. …  
  11.    
  12.    
  13. // 声明注解 
  14. //@Compress annotation is the name binding annotation  
  15. @NameBinding  
  16. @Retention(RetentionPolicy.RUNTIME)  
  17. public @interface Compress {}  
  18.    
  19.    
  20. //将注解和业务方法绑定
  21. @Path(“helloworld”)  
  22. public class HelloWorldResource {  
  23.    
  24.     @GET  
  25.     @Produces(“text/plain”)  
  26.     public String getHello() {  
  27.         return “Hello World!”;  
  28.     }  
  29.    
  30.     @GET  
  31.     @Path(“too-much-data”)  
  32.     @Compress  
  33.     public String getVeryLongString() {  
  34.         String str = … // very long string  
  35.         return str;  
  36.     }  
  37. }  
  38.    
  1. // 将注解和拦截器实现类绑定
  2. // interceptor will be executed only when resource methods  
  3. // annotated with @Compress annotation will be executed  
  4. @Compress  
  5. public class GZIPWriterInterceptor implements WriterInterceptor {  
  6.     @Override  
  7.     public void aroundWriteTo(WriterInterceptorContext context)  
  8.                     throws IOException, WebApplicationException {  
  9.         final OutputStream outputStream = context.getOutputStream();  
  10.         context.setOutputStream(new GZIPOutputStream(outputStream));  
  11.         context.proceed();  
  12.     }  
  13. }  

上述代码定义了注解@Compress,并用在方法getVeryLongString()上,以及GZIPWriterInterceptor拦截器上。拦截器仅在被注解的方法执行时才执行。应用程序中可以有多个命名绑定注解。当provider(拦截器与过滤器)通过命名注解注解后,它仅仅在标注有对应注解的方法被执行后才执行。

10.6 动态绑定

动态绑定是一种以动态的方法将拦截器与过滤器与处理方法关联的方式。10.5中的绑定使用了静态方法,当改变绑定时,需要重新编译代码。通过动态绑定,你可以在程序初始化时实现绑定的代码。

[java]  view plain  copy
  1. …  
  2. import javax.ws.rs.core.FeatureContext;  
  3. import javax.ws.rs.container.DynamicFeature;  
  4. …  
  5.    
  6. @Path(“helloworld”)  
  7. public class HelloWorldResource {  
  8.    
  9.     @GET  
  10.     @Produces(“text/plain”)  
  11.     public String getHello() {  
  12.         return “Hello World!”;  
  13.     }  
  14.    
  15.     @GET  
  16.     @Path(“too-much-data”)  
  17.     public String getVeryLongString() {  
  18.         String str = … // very long string  
  19.         return str;  
  20.     }  
  21. }  
  22.    
  23. // This dynamic binding provider registers GZIPWriterInterceptor  
  24. // only for HelloWorldResource and methods that contain  
  25. // “VeryLongString” in their name. It will be executed during  
  26. // application initialization phase.  
  27. public class CompressionDynamicBinding implements DynamicFeature {  
  28.    
  29.     @Override  
  30.     public void configure(ResourceInfo resourceInfo, FeatureContext context) {  
  31.         if (HelloWorldResource.class.equals(resourceInfo.getResourceClass())  
  32.                 && resourceInfo.getResourceMethod()  
  33.                     .getName().contains(“VeryLongString”)) {  
  34.             context.register(GZIPWriterInterceptor.class);  
  35.         }  
  36.     }  
  37. }  

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)之前执行。

[java]  view plain  copy
  1. …  
  2. import javax.annotation.Priority;  
  3. import javax.ws.rs.Priorities;  
  4. …  
  5.    
  6. @Priority(2000)  
  7. public class ResponseFilter implements ContainerResponseFilter {  
  8.    
  9.     @Override  
  10.     public void filter(ContainerRequestContext requestContext,  
  11.                     ContainerResponseContext responseContext)  
  12.                     throws IOException {  
  13.    
  14.         responseContext.getHeaders().add(“X-Powered-By”“Jersey :-)”);  
  15.     }  
  16. }