Spring MVC系列(11)-拦截器使用详解及流程分析
拦截器简介什么是拦截器Spring中的拦截器(Interceptor) ,用于拦截控制器方法的执行,可以在方法执行前后,添加自定义逻辑,类似于AOP编程思想。实际应用中,可以使用拦截器实现,认证授权、日志记录、字符编码转换,敏感词过滤等等。和过滤器有什么区别过滤器也能实现拦截功能,具体和拦截器有什么不同呢1. 触发机制过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求
拦截器简介
什么是拦截器
Spring中的拦截器(Interceptor) ,用于拦截控制器方法的执行,可以在方法执行前后,添加自定义逻辑,类似于AOP编程思想。
实际应用中,可以使用拦截器实现,认证授权、日志记录、字符编码转换,敏感词过滤等等。
和过滤器有什么区别
过滤器也能实现拦截功能,具体和拦截器有什么不同呢
1. 触发机制
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
2. 实现原理
过滤器和拦截器底层实现方式大不相同,过滤器是基于函数回调的,拦截器则是基于Java的反射机制(动态代理)实现的。
3. 使用范围
过滤器实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。
4. 拦截的请求范围
过滤器几乎可以对所有进入容器的请求起作用。
拦截器只会对Controller中请求或访问static目录下的资源请求起作用。
核心类
HandlerInterceptor接口
Spring MVC提供了HandlerInterceptor接口来实现拦截器功能,我们可以实现这个接口,然后注册拦截器,以添加常见的预处理行为,而无需修改每个控制器方法。
HandlerInterceptor定义了三个方法,可在控制器方法执行前后添加自定义逻辑。
public interface HandlerInterceptor {
/**
* 在 HandlerMapping 确定合适的处理程序对象之后,但在 HandlerAdapter 调用处理程序之前调用
*/
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
/**
* 拦截处理程序的执行。在 HandlerAdapter 实际上调用处理程序之后调用,但在 DispatcherServlet 呈现视图之前调用
*/
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
/**
* 请求处理完成后的回调,即渲染视图后。将在处理程序执行的任何结果上调用,从而允许进行适当的资源清理。
*/
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
AsyncHandlerInterceptor
AsyncHandlerInterceptor异步处理程序拦截器,继承自HandlerInterceptor接口,主要是扩展了一个afterConcurrentHandlingStarted方法。
/**
*处理程序被并发执行时,代替 {@code postHandle} 和 {@code afterCompletion} 被调用。
* 实现可以使用提供的请求和响应,但应该
* 避免以与并发处理程序执行冲突的方式修改它们。这种方法的典型用途是清理线程局部变量。
*/
default void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
}
WebRequestInterceptor
WebRequestInterceptor也是拦截器接口,不过它拦截方法传入的参数是WebRequest。
WebRequest是Spring定义的接口,它是对HttpServletRequest的封装。对WebRequest 进行的操作都将同步到HttpServletRequest 中。
public interface WebRequestInterceptor {
// 拦截请求处理程序,在其调用之前的执行。允许准备上下文资源(例如 Hibernate Session)并将它们公开为请求属性或线程本地对象
void preHandle(WebRequest request) throws Exception;
// 拦截请求处理程序的执行成功调用之后,就在视图呈现之前(如果有的话)。允许在成功的处理程序执行后修改上下文资源(例如,刷新休眠Session)。
void postHandle(WebRequest request, @Nullable ModelMap model) throws Exception;
// 请求处理完成后的回调,即渲染视图后。将在处理程序执行的任何结果上调用,从而允许进行适当的资源清理。
void afterCompletion(WebRequest request, @Nullable Exception ex) throws Exception;
}
InterceptorRegistration
InterceptorRegistration类,用来协助拦截器进行注册登记,可以为每个拦截器配置匹配路径、包含路径等。
它的属性如下:
// 注册的拦截器
private final HandlerInterceptor interceptor;
// 包含路径
@Nullable
private List<String> includePatterns;
// 排除路径
@Nullable
private List<String> excludePatterns;
// 匹配器
@Nullable
private PathMatcher pathMatcher;
// 顺序
private int order = 0;
InterceptorRegistry
InterceptorRegistry翻译过来是拦截器注册表,它的主要作用是维护了应用程序所有的拦截器列表。
public class InterceptorRegistry {
// 拦截器注册集合
private final List<InterceptorRegistration> registrations = new ArrayList<>();
// 添加Handler拦截器,
public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) {
InterceptorRegistration registration = new InterceptorRegistration(interceptor);
this.registrations.add(registration);
return registration;
}
// 添加WebRequestInterceptor 拦截器
public InterceptorRegistration addWebRequestInterceptor(WebRequestInterceptor interceptor) {
WebRequestHandlerInterceptorAdapter adapted = new WebRequestHandlerInterceptorAdapter(interceptor);
InterceptorRegistration registration = new InterceptorRegistration(adapted);
this.registrations.add(registration);
return registration;
}
}
自定义拦截器案例
1. 准备工作
添加一个用户对象,封装用户信息、权限值等。
@Data
public class User {
String username;
String password;
Integer age;
String name;
Long userId;
String permissionCode;
}
编写一个登陆接口,模拟登陆,将用户信息保存到Session 中。
@GetMapping("/user/login")
public Object login(HttpSession httpSession) {
User user = new User();
user.setName("吃个桃桃");
user.setAge(15);
user.setUserId(1111L);
user.setPermissionCode("add:user");
httpSession.setAttribute("user", user);
return user;
}
2. 编写拦截器
自定义拦截器,实现HandlerInterceptor 接口,模拟访问时,对用户是否登陆,是否具有权限访问进行校验。
public class MvcPermissionInterceptor implements HandlerInterceptor {
/**
* 前置处理
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 可以通过handler获取执行方法的相关信息, 方法名,注解等。
// 1. 放行路径,直接放行,或者在注册时使用excludePathPatterns排除
String requestURI = request.getRequestURI();
if ("test".equals(requestURI)) {
System.out.println(requestURI + "放行!!!");
return true;
}
Map<String, Object> result = new HashMap<>();
// 2. 判断用户是有已登录
User user = (User) request.getSession().getAttribute("user");
if (user == null) {
printMsg(result, response, HttpStatus.UNAUTHORIZED.value(), 401, "未登录!!!");
return false;
}
// 3. 检查用户权限
String permissionCode = user.getPermissionCode();
if (!"add:user".equals(permissionCode)) {
printMsg(result, response, HttpStatus.FORBIDDEN.value(), 403, "没权限");
return false;
}
System.out.println("已登录,并拥有权限");
return true;
}
/**
* 响应错误信息
*/
private void printMsg(Map<String, Object> result, HttpServletResponse response, int statusCode, int code, String msg) throws IOException {
response.setStatus(statusCode);
response.setContentType("application/json; charset=UTF-8");
result.put("code", code);
result.put("msg", msg);
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), result);
response.setHeader("Cache-Control", "No-Cache");
response.flushBuffer();
}
/**
* 后置处理
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
/**
* 完成处理
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
3. 注册拦截器
在WebMvcConfigurer配置类中,重写addInterceptors方法,添加拦截器,并配置匹配路径。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MvcPermissionInterceptor()).addPathPatterns("/**").excludePathPatterns("/v1/user/login");
}
}
4. 测试
直接访问接口,返回未登录信息。
使用登录接口登录,然后再访问资源接口,发现能正常访问,并打印了拦截器相关执行日志。
执行流程源码分析
首先看下大致流程,可以看到拦截器是在DispatcherServlet中进行执行的,所以将断点打在DispatcherServlet的doDispatch方法中。
1. 获取拦截器
doDispatch方法中会调用getHandler去获取当前请求的拦截器,所有的拦截器都在HandlerExecutionChain类中。
2. 调用前置方法
HandlerExecutionChain处理器执行链,接着会调用拦截器的前置方法,当返回false时,请求直接return了。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
applyPreHandle会循环拦截器。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 循环拦截器
for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
// 执行每个拦截器
if (!interceptor.preHandle(request, response, this.handler)) {
// 拦截器返回false时,调用已执行完毕拦截器的afterCompletion方法
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
return true;
}
3. 后置方法
所有拦截器执行完毕,且都返回ture后,处理器方法开始执行(controller方法)。
处理器方法执行完毕后,开始调用applyPostHandle执行控制器后置方法。
mappedHandler.applyPostHandle(processedRequest, response, mv);
applyPostHandle很简单,就是循环拦截器,执行其postHandle方法。
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
for(int i = this.interceptorList.size() - 1; i >= 0; --i) {
HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
执行完成方法
后置方法执行完毕后,DispatcherServlet进入到processDispatchResult方法,最终会调用HandlerExecutionChain的triggerAfterCompletion循环执行拦截器的完成方法。
到此,整个拦截器就执行完毕了。
多个拦截器执行顺序
再添加一个拦截器,然后发现其执行顺序好像有点乱。
1. 当拦截器都通过时
若每个拦截器的preHandle()都返回true时:
- 此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关
- preHandle()会按照配置的顺序执行;
- postHandle()和afterComplation()会按照配置的反序执行
2. 当某个拦截器返回false时
若某个拦截器的preHandle()返回了false时:
- preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行
更多推荐
所有评论(0)