过滤器和拦截器可以在客户端和服务器端使用(本文主要介绍服务器端使用)。过滤器可以修改请求和响应,包括修改头、实体和其他请求/响应参数。拦截器主要用于修改实体输入流和输出流。例如,您可以使用拦截器对输出和输入实体流进行压缩和解压缩。
无论是使用filter还是interceptor,都需要注册为provider。
一、过滤器
1、过滤器类型
入站 | 出站 | |
---|---|---|
服务器端 | ContainerRequestFilter | ContainerResponseFilter |
客户端 | ClientRequestFilter | ClientResponseFilter |
在服务器端,入站过滤器还分Pre-matching 和 post-matching:
Pre-matching:由@Pre-matching注解标识,是在启动请求匹配之前执行的请求过滤器,即可以修改请求的资源,直接影响匹配的方法。
post-matching:默认都是后匹配过滤器,即在匹配到了合适的资源方法后,该过滤器将会应用。
2、请求过滤器,实现ContainerRequestFilter接口
(1)post-matching
@Provider
public class AuthorizationRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext request) throws IOException {
String authorization = request.getHeaderString("authorization");
if (authorization == null || "".equals(authorization) || !authorization.startsWith("Basic")
|| !Auth.allow(new String(Base64.getDecoder().decode(authorization.split(" ")[1])))) {
request.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity(new Result(401, "认证失败"))
.type(MediaType.APPLICATION_JSON).build());
return;
}
}
}
(2)Pre-matching
@PreMatching
@Provider
public class MethodRequestFilter implements ContainerRequestFilter{
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if(requestContext.getMethod().equals("PUT") || requestContext.getMethod().equals("DELETE"))
requestContext.setMethod("POST");
}
}
和post-matching的区别就是加上@Pre-matching注解,将在找到实际资源之前进行拦截,所以甚至可以通过调用setRequestUri()方法直接转发请求。
所以在请求过滤器中,Pre-matching 一定会比 post-matching 先执行。
3、响应过滤器,实现ContainerResponseFilter接口
@Provider
public class ResponseFilter implements ContainerResponseFilter{
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
if(responseContext.getStatus() == 401)
responseContext.getHeaders().add("WWW-Authenticate", "Basic realm=\".\"");
}
}
比如对认证没通过的响应进行拦截,要求客户端输入用户名密码进行认证。
所以响应过滤器都是在实际资源处理完成之后执行,即使实际资源没有执行,比如404错误,都会经过响应过滤器。(一定会执行)
响应过滤器的filter方法中有两个参数,request只读,修改无效。
二、拦截器
服务器和客户端使用相同的拦截器API:ReaderInterceptor 和 WriterInterceptor。
ReaderInterceptor拦截器实在消息体读之前执行,WriterInterceptor拦截器在消息体写入之前执行;它们的主要目的是包装将在消息体中读取和写入使用的实体流。
1、读拦截器
@Provider
public class StartLoggerInterceptor implements ReaderInterceptor {
@Override
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
//。。。。
return context.proceed();
}
}
一定要注意两点:
A、必须要调用proceed()方法,该方法在内部调用下一个拦截器。
B、只有有消息体时,才会执行对应的拦截器,如果在请求的资源中并没有获取任何消息体,即使客户端传递了,也不会执行;如果在拦截器中获取了消息体,则处理完后一定要设置消息体,否则不会执行下一个拦截器。
三、绑定机制
默认情况下,无论是手动注册到Application或Configuration中,还是通过注解@Provider,都是全局的配置。
Jersey提供名称绑定和动态绑定两种方式,给我们灵活的进行配置过滤器和拦截器。
1、名称绑定
使用注解@NameBinding,
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NameBinding {
}
该注解只能用在注解上,只需要我们根据实际需要自定义注解即可。
(1)自定义注解
@NameBinding
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Authorization {
}
可以在方法和类上使用。
(2)过滤器配置
@Authorization
@Provider
public class AuthorizationRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext request) throws IOException {
String authorization = request.getHeaderString("authorization");
System.out.println(authorization);
if (authorization == null || "".equals(authorization) || !authorization.startsWith("Basic")
|| !new String(Base64.getDecoder().decode(authorization.split(" ")[1])).equals("wzy:123456")) {
request.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity(new Result(401, "认证失败"))
.type(MediaType.APPLICATION_JSON).build());
return;
}
}
}
使用自定义注解@Authorization进行名称绑定。
(3)使用
@Path("/rest")
@Produces("application/json")
@PerLookup
public class HelloRestController {
@Authorization
@POST
@Path("/jump")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response jump(@FormParam("name") String name){
User user = new User(name,12);
return Response.ok().entity(user).build();
}
@GET
@Path("/get")
@Produces("application/json")
public MyJaxbBean getBean(@FormParam("name") String name){
return new MyJaxbBean(name, 28);
}
}
我们使用@Authorization对jump方法进行名称绑定,另外getBean方法没有,当请求时,只有jump方法才会要求验证用户。
2、动态绑定
需要实现动态特征接口javax.ws.rs.container.DynamicFeature,在运行期对匹配的方法进行绑定。
@Provider
public class AuthDymic implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if (resourceInfo.getResourceMethod().isAnnotationPresent(POST.class)
|| RestController.class.isAssignableFrom(resourceInfo.getResourceClass())) {
context.register(AuthorizationFilter.class);
}
}
static class AuthorizationFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext request) throws IOException {
String authorization = request.getHeaderString("authorization");
System.out.println("auth");
if (authorization == null || "".equals(authorization) || !authorization.startsWith("Basic")
|| !new String(Base64.getDecoder().decode(authorization.split(" ")[1])).equals("wzy:123456")) {
request.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity(new Result(401, "认证失败"))
.type(MediaType.APPLICATION_JSON).build());
return;
}
}
}
}
会对post方法以及RestController类中的方法进行过滤。
四、执行顺序和优先级
当我们定义了更多的过滤器和拦截器,并且希望他们执行都按照自己期望的顺序执行时,可以使用Jersey提供的@Priority注解。该注释接收一个具有优先级的整数参数。
在ContainerRequestFilter、WriterInterceptor、ReaderInterceptor中,将以数字的升序执行,比如@Priority(1)将会比@Priority(2)优先执行;
在ContainerResponseFilter中,将以相反的方向实行,比如@Priority(2)将会比@Priority(1)优先执行。
同时也可以使用javax.ws.rs.Priorities类,该类定义了一些标准化的优先级:
public final class Priorities {
private Priorities() {
// prevents construction
}
/**
* Security authentication filter/interceptor priority.
*/
public static final int AUTHENTICATION = 1000;
/**
* Security authorization filter/interceptor priority.
*/
public static final int AUTHORIZATION = 2000;
/**
* Header decorator filter/interceptor priority.
*/
public static final int HEADER_DECORATOR = 3000;
/**
* Message encoder or decoder filter/interceptor priority.
*/
public static final int ENTITY_CODER = 4000;
/**
* User-level filter/interceptor priority.
*
* This value is also used as a default priority for application-supplied providers.
*/
public static final int USER = 5000;
}