拦截器都在用,可为啥这么用?为啥不用filter呢?你得知道这些东西
基本概念
Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等;
快速上手
Interceptor 拦截器示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| package com.isky.visual.interceptor;
import com.alibaba.fastjson.JSONObject; import com.isky.visual.constant.CommonConstant; import com.isky.visual.interceptor.annotation.LoginValidate; import com.isky.visual.result.CodeMsg; import com.isky.visual.result.ResultVo; import com.isky.visual.user.entity.User; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.PrintWriter;
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { boolean validate = false; PrintWriter out = null; try { if(handler instanceof HandlerMethod){ validate = validate(request, response, (HandlerMethod)handler); }else if(handler instanceof ResourceHttpRequestHandler){ validate = true; } if(!validate){ response.setHeader("content-type", "application/json;charset=UTF-8"); out = response.getWriter(); ResultVo<String> error = ResultVo.error(CodeMsg.SESSION_ERROR); out.print(JSONObject.toJSONString(error)); } } catch (Exception e) { e.printStackTrace(); validate = false; } finally { if(null!=out) { out.close(); } } return validate; }
private boolean validate(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler){ RestController annotationController = handler.getBeanType().getAnnotation(RestController.class); if(annotationController == null){ return true; } LoginValidate annotation = handler.getBeanType().getAnnotation(LoginValidate.class); if(annotation != null && !annotation.value()){ return true; } annotation = handler.getMethodAnnotation(LoginValidate.class); if(annotation != null && !annotation.value()){ return true; } HttpSession session = request.getSession(); if(session== null){ return false; } Object user = session.getAttribute(CommonConstant.USER_SESSION_ID); if(user== null || !(user instanceof User)){ return false; } PlatformUserManager.setUser((User)user); return true; }
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
}
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
} }
|
LoginValidate 自定义注解,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.isky.visual.interceptor.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LoginValidate {
boolean value() default true; }
|
从代码注释,我们可以了解到如果我们需要放行某个请求或这个某个Controller 下的所有请求的话,只需要在对应的方法或类上加上对应的注解@LoginValidate(false)
即可,当然对于类等资源的放行也可以这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.isky.visual.config;
import com.isky.visual.interceptor.LoginInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration public class DefaultWebMvcConfigurer{ @Bean public WebMvcConfigurer webMvcConfigurerAdapter(){ return new WebMvcConfigurer(){ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()); } }; } }
|
PlatformUserManager 保存用户信息,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.isky.visual.interceptor;
import com.isky.visual.exception.GlobalException; import com.isky.visual.result.CodeMsg; import com.isky.visual.user.entity.User; import org.springframework.core.NamedThreadLocal; import java.util.Map;
public class PlatformUserManager { private static final ThreadLocal<User> userMap = new NamedThreadLocal("user resources"); public static void setUser(User user){ userMap.set(user); } public static User getUser(){ User user = userMap.get(); if(user == null ){ throw new GlobalException(CodeMsg.SESSION_ERROR); } return user; } }
|
基于以上代码,我们已经知道了如何实现HandlerInterceptor并重写其preHandle对请求做自定义校验及拦截放行处理,那么它是怎么做到呢,重写的三个方法执行顺序又是怎么样的呢?带着问题我们来看下源码探究下:
doDispatch 源码分析
首先要分析拦截器的原理及执行流程之前,我们得知道Springmvc 整个调度流程中有个很重要的调度控制器叫DispatcherServlet
,那么我们重点来看下它的调度方法doDispatch
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try { try { ModelAndView mv = null; Object dispatchException = null;
try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; }
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; }
this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); }
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); }
} finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); }
} }
|
注:afterCompletion 并不是发生异常才会执行,this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
这个中也有调用,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { this.logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException)exception).getModelAndView(); } else { Object handler = mappedHandler != null ? mappedHandler.getHandler() : null; mv = this.processHandlerException(request, response, handler, exception); errorView = mv != null; } }
if (mv != null && !mv.wasCleared()) { this.render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else if (this.logger.isTraceEnabled()) { this.logger.trace("No view rendering, null ModelAndView returned."); } if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, (Exception)null); } } }
|
类比filter
好,了解了拦截器的基本执行逻辑及源码后,我们来类比下filter
顾名思义,filter 是过滤器是过滤资源、请求、参数、地址等的,而Interceptor 是拦截器是拦截请求方法的;它们二者还是有很大区别的,宏观上将filter 的过滤范围Interceptor比拦截器大,还记得我们讲过SpringSecurity其实就是一组基于filte的实现吗?另外filter在整个action的生命周期中,是伴随Spring容器初始化时被调用一次的,而拦截器是可以被多次调用的!
filter 实现示例:
spring中我们需要使用@WebFilter注解来声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.springstudy.config;
import lombok.SneakyThrows; import org.springframework.stereotype.Component;
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException;
@Component @WebFilter(urlPatterns = {"/*"},filterName = "FilterConfig") public class FilterConfig implements Filter { @Override public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8");
filterChain.doFilter(request,response); }
@Override public void destroy() {
} }
|
或者如下自定义一个FilterRegistrationBean 类的bean:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Configuration public class WebConfig { @Bean public FilterRegistrationBean timeFilter() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); TimerFilter timerFilter = new TimerFilter(); registrationBean.setFilter(timerFilter); List<String> urls = new ArrayList<>(); urls.add("/*"); registrationBean.setUrlPatterns(urls); return registrationBean; } }
|
小结:filter 的过滤范围要大于interceptor,故而interceptor 显得更加轻量一点,对于基本的权限、日志、状态等的判断,我们一般选择使用interceptor更灵活些!
需要注意的filter 是基于Servlet容器的,而interceptor 是基于spring 容器的,在单体web项目中我们经常配置filter通配符来保护页面,图片,文件不被过滤处理!而在前后端分离的这种spring 项目,我们需要优先考虑使用interceptor!
更多请参考文章:spring boot 过滤器、拦截器的区别与使用