首页 > 技术文章 > RESTFul系列文章 >

Jersey 开发RESTful(十四) Jersey的过滤器

更新时间:2018-06-02 | 阅读量(1,287)

【原创文章,转载请注明原文章地址,谢谢!】 >在REST应用中,也会出现针对一组请求需要在请求之前或者之后做统一处理的情况,比如登录检查,版本校对,额外的版权信息等,通过过滤器能够统一的处理。 ##服务器端过滤器(Server Filter) Jersey中的过滤器分为两块,针对服务器端的过滤器和针对客户端的过滤器,先介绍服务器端的过滤器。 我们知道Servlet中的过滤器Filter,是一种双向的过滤器,即一个过滤器可以对请求进行一次过滤,然后调用执行链,让请求向下运行,然后再返回响应的时候,再次通过过滤器,在这个时候就可以对响应进行处理。而在JAX-RS中,过滤器是单向的,换句话说,要针对请求进行过滤,要选择针对请求的过滤器,要针对响应进行过滤,就要选择针对响应的过滤器: javax.ws.rs.container.ContainerRequestFilter:针对请求的过滤器; javax.ws.rs.container.ContainerResponseFilter:针对响应的过滤器; ####基本使用 先来做一个最简单的过滤器测试: 首先实现一个ContainerRequestFilter: public class MyRequestTestFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { System.out.println("===my request test filter==="); } } 我们的代码很简单,在filter方法中打印一行数据; 然后再实现一个ContainerResponseFilter: public class MyResponseTestFilter implements ContainerResponseFilter { @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { System.out.println("===my response filter test==="); } } 同样,在filter方法中打印一行数据。注意,现在先不用去关心filter方法的ContainerRequestContext,ContainerResponseContext等参数,后面介绍。 完成两个过滤器之后,进行代码测试,先创建一个资源: @Path("filter1") @GET public String resource1() { return "success"; } 注意,要让过滤器生效,需要在启动Jersey之前,注册我们的过滤器: public RestApplication() { this.packages("cn.wolfcode.jersey"); this.register(MultiPartFeature.class); this.register(MyRequestTestFilter.class).register(MyResponseTestFilter.class); } 完成一个测试代码: @Test public void test() { String responseText = ClientBuilder.newClient() .target("http://localhost:8082/webapi").path("filter/filter1") .request(MediaType.TEXT_PLAIN).get(String.class); System.out.println(responseText); } 运行测试,在服务端能够看到输出: ===my request test filter=== ===my response filter test=== ####更多细节 首先来看下两个过滤器的执行方式,在上面的测试代码中,当我们正常完成一个资源的请求,其执行流程为: requestFilter--->resource-->responseFilter 但是如果我们执行以下测试: @Test public void test() { String responseText = ClientBuilder.newClient() .target("http://localhost:8082/webapi").path("filter/filter2") .request(MediaType.TEXT_PLAIN).get(String.class); System.out.println(responseText); } 我们请求了一个不存在的资源地址,返回404,但是服务端输出: ===my response filter test=== 意思就是,requestFilter一定要资源请求到了之后,才会执行,而responseFilter是只要有响应返回即可执行,我们可以对404等异常响应处理。 其次,在ContainerRequestFilter中,filter方法提供了一个ContainerRequestContext参数,我们来看看这个类的功能: #####ContainerRequestContext ContainerRequestContext:包装了请求相关的内容,比如请求URI,请求方法,请求实体,请求头等等信息,这是一个可变的类,可以在请求过滤器中被修改; 我们在本节中不做过多的例子,我们来分析一个Jersey内提供的ContainerRequestFilter——HttpMethodOverrideFilter。我们前面提到过,如果客户端是通过网页表单请求,但是我们知道表单只有POST和GET两种请求方式,怎么模拟PUT,DELETE等请求方式呢?在SpringMVC中,可以通过表单提交一个_method域,在该域中设置要模拟的请求类型,比如DELETE,在服务端配置一个HttpMethodOverrideFilter过滤器即可。 同理,在Jersey中,也存在这种请求问题。Jersey就是使用HttpMethodOverrideFilter来完成请求方法的转化。我们来看看代码(为了方便查看,代码进行了简化,完整代码请查看源代码即可): public final class HttpMethodOverrideFilter implements ContainerRequestFilter { @Override public void filter(final ContainerRequestContext request) { if (!request.getMethod().equalsIgnoreCase("POST")) { return; } final String header = getParamValue(Source.HEADER, request.getHeaders(), "X-HTTP-Method-Override"); final String query = getParamValue(Source.QUERY, request.getUriInfo().getQueryParameters(), "_method"); final String override; if (header == null) { override = query; } else { override = header; if (query != null && !query.equals(header)) { // inconsistent query and header param values throw new BadRequestException(); } } if (override != null) { request.setMethod(override); if (override.equals("GET")) { if (request.getMediaType() != null && MediaType.APPLICATION_FORM_URLENCODED_TYPE.getType().equals(request.getMediaType().getType())) { final UriBuilder ub = request.getUriInfo().getRequestUriBuilder(); final Form f = ((ContainerRequest) request).readEntity(Form.class); for (final Map.Entry> param : f.asMap().entrySet()) { ub.queryParam(param.getKey(), param.getValue().toArray()); } request.setRequestUri(request.getUriInfo().getBaseUri(), ub.build()); } } } } } 可以简单的来看看执行流程: 1,请求比如是POST方法(调用getMethod获取本次请求方法); 2,两种方式可以完成请求方法的指定,第一种可以通过设置请求头中的X-HTTP-Method-Override属性,第二种可以通过添加_method查询参数,(这里分别调用getHeaders方法和getUriInfo方法获取头信息和请求URI信息); 3,设置请求方法,通过调用request.setMethod方法完成; 4,如果请求方式是POST,但是想转成GET方式,就需要把提交的表单内容变成URI的查询参数,具体参考代码即可。 #####ContainerResponseContext 对于ContainerResponseFilter来说,filter方法里面提供了两个参数: ContainerRequestContext和ContainerResponseContext,第一个ContainerRequestContext我们前面已经说过,但是注意,在ResponseFilter中,requestContext已经是不能修改的!!ContainerResponseContext包装了响应相关的内容,这个对象在responseFilter中是可以修改的,比如下面的例子: public class PoweredByResponseFilter implements ContainerResponseFilter { @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { responseContext.getHeaders().add("X-Powered-By", "wolfcode.cn"); } } 在这段代码中,我们通过responseContext为每一个响应都添加了一个额外的X-Powered-By响应头信息。更多的ContainerResponseContext方法,请查看API文档。 ####PreMatch和PostMatch 针对requestFilter,我们前面演示的所有的requestFilter都属于PostMatch,这也是默认的匹配时机,即过滤器只有当一个资源方法被正确的找到并准备执行的时候,过滤器执行过滤。换句话说,PostMath过滤器不能影响资源方法的匹配过程。所以我们上面看到的HttpMethodOverrideFilter在默认情况下(PostMatch)是不可能生效的,因为方法的匹配是在资源匹配之前完成的。 在Jersey中提供了@PreMatching注解,只需要在对应的requestFilter类上添加该标签,请求过滤器会在执行资源匹配之前提前运行。 所以,我们之前介绍的HttpMethodOverrideFilter,完整的类声明如下: @PreMatching @Priority(Priorities.HEADER_DECORATOR + 50) public final class HttpMethodOverrideFilter implements ContainerRequestFilter { 可以看到,确实是标记为@PreMatching注解,那么就可以在该requestFilter中使用request.setMethod修改请求方法了。如果我们是在PostMatch请求过滤器中调用setMethod方法,那么过滤器会抛出IllegalArgumentException异常。 关于优先级@Priority标签,下文介绍。 ##客户端过滤器(Client Filter) 和服务端过滤器类似,Jersey也提供了两种客户端过滤器: javax.ws.rs.client.ClientRequestFilter:客户端请求过滤器; javax.ws.rs.client.ClientResponseFilter:客户端响应过滤器; 一个客户端过滤器示例代码: public class CheckRequestFilter implements ClientRequestFilter { @Override public void filter(ClientRequestContext requestContext) throws IOException { if (requestContext.getHeaders( ).get("Client-Name") == null) { requestContext.abortWith( Response.status(Response.Status.BAD_REQUEST) .entity("Client-Name header must be defined.") .build()); } } } 该代码起一个演示作用,可以看到,首先CheckRequestFilter实现了ClientRequestFilter,即客户端请求拦截器,作用于客户端向服务端发送请求的过程,在filter方法中,判断请求头中是否包含Client-Name这个属性,如果没有包含,则直接调用requestContext的abortWith方法,传入一个状态为Response.Status.BAD_REQUEST(400)的响应,换句话说,该过滤器直接阻止了客户端向服务端的请求发送。 测试代码: @Test public void testClientFilter() { String responseText = ClientBuilder.newClient() .register(CheckRequestFilter.class) .target("http://localhost:8082/webapi").path("filter/filter1") .request(MediaType.TEXT_PLAIN) .header("Client-Name", "wolfcode.cn").get(String.class); System.out.println(responseText); } 当然,在什么地方注册这个客户端过滤器无所谓,在ClientConfig,ClientBuilder或者WebTarget中都可以。 如果按照正常的流程,在header中增加Client-Name,能够正常执行,返回success;如果把测试代码修改为: @Test public void testClientFilter() { String responseText = ClientBuilder.newClient() .register(CheckRequestFilter.class) .target("http://localhost:8082/webapi").path("filter/filter1") .request(MediaType.TEXT_PLAIN) .get(String.class); System.out.println(responseText); } 那么测试失败,直接抛出400的异常提示。 ![image.png](http://upload-images.jianshu.io/upload_images/807144-4307e33783ae1696.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/440) 关于ClientRequestContext,ClientResponseContext这两个参数,可以参考服务端过滤器的使用,参考API即可。 ####其他 关于Filter的Name Binding,动态绑定和优先级,因为和拦截器(Interceptor)是相同的,所以这三个内容我们在下一节Interceptor中介绍。 ![WechatIMG7.jpeg](http://upload-images.jianshu.io/upload_images/807144-fd8c521ac6a36c1e.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
叩丁狼学员采访 叩丁狼学员采访
叩丁狼头条 叩丁狼头条
叩丁狼在线课程 叩丁狼在线课程