赞
踩
Spring中的拦截器(Interceptor) ,用于拦截控制器方法的执行,可以在方法执行前后,添加自定义逻辑,类似于AOP编程思想。
实际应用中,可以使用拦截器实现,认证授权、日志记录、字符编码转换,敏感词过滤等等。
过滤器也能实现拦截功能,具体和拦截器有什么不同呢
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
过滤器和拦截器底层实现方式大不相同,过滤器是基于函数回调的,拦截器则是基于Java的反射机制(动态代理)实现的。
过滤器实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。
过滤器几乎可以对所有进入容器的请求起作用。
拦截器只会对Controller中请求或访问static目录下的资源请求起作用。
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异步处理程序拦截器,继承自HandlerInterceptor接口,主要是扩展了一个afterConcurrentHandlingStarted方法。
/**
*处理程序被并发执行时,代替 {@code postHandle} 和 {@code afterCompletion} 被调用。
* 实现可以使用提供的请求和响应,但应该
* 避免以与并发处理程序执行冲突的方式修改它们。这种方法的典型用途是清理线程局部变量。
*/
default void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
}
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类,用来协助拦截器进行注册登记,可以为每个拦截器配置匹配路径、包含路径等。
它的属性如下:
// 注册的拦截器
private final HandlerInterceptor interceptor;
// 包含路径
@Nullable
private List<String> includePatterns;
// 排除路径
@Nullable
private List<String> excludePatterns;
// 匹配器
@Nullable
private PathMatcher pathMatcher;
// 顺序
private int order = 0;
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; } }
添加一个用户对象,封装用户信息、权限值等。
@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;
}
自定义拦截器,实现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"); } }
在WebMvcConfigurer配置类中,重写addInterceptors方法,添加拦截器,并配置匹配路径。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MvcPermissionInterceptor()).addPathPatterns("/**").excludePathPatterns("/v1/user/login");
}
}
直接访问接口,返回未登录信息。
使用登录接口登录,然后再访问资源接口,发现能正常访问,并打印了拦截器相关执行日志。
首先看下大致流程,可以看到拦截器是在DispatcherServlet中进行执行的,所以将断点打在DispatcherServlet的doDispatch方法中。
doDispatch方法中会调用getHandler去获取当前请求的拦截器,所有的拦截器都在HandlerExecutionChain类中。
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;
}
所有拦截器执行完毕,且都返回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循环执行拦截器的完成方法。
到此,整个拦截器就执行完毕了。
再添加一个拦截器,然后发现其执行顺序好像有点乱。
若每个拦截器的preHandle()都返回true时:
若某个拦截器的preHandle()返回了false时:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。