Interceptor的使用及探究
@
拦截器都在用,可为啥这么用?为啥不用filter呢?你得知道这些东西
基本概念
Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等;
快速上手
Interceptor 拦截器示例:
实现HandlerInterceptor 类,代码如下:
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;
}
// 校验不通过,即表示用户未登录或登录session 失效
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 是自定义的一个注解
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;
}
// 获取session
HttpSession session = request.getSession();
if(session== null){
return false;
}
// 根据sessionid 获取用户信息
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 是自定义的一个注解,代码如下:
package com.isky.visual.interceptor.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginValidate {
/**
* 不使用注解,默认需要校验用户登入,
* 如果不需要校验,请使用@LoginValidate(false) 标注类或方法
* @return
*/
boolean value() default true;
}
从代码注释,我们可以了解到如果我们需要放行某个请求或这个某个Controller 下的所有请求的话,只需要在对应的方法或类上加上对应的注解@LoginValidate(false)
即可,当然对于类等资源的放行也可以这样写:
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;
/**
* 自定义WebMvcConfigurer 配置类
*/
@Configuration
public class DefaultWebMvcConfigurer{
@Bean
public WebMvcConfigurer webMvcConfigurerAdapter(){
return new WebMvcConfigurer(){
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor());
// 添加放行路径 过滤、/user 下的所有请求 或者指定某一个或多个
//registry.addInterceptor(new LoginInterceptor()).addPathPatterns("*").excludePathPatterns("/user/*");
}
};
}
}
PlatformUserManager 保存用户信息,代码如下:
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
源码如下:
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;
}
}
// 调用我们自定义的拦截器中的preHandle方法 如果校验不通过 !false 就直接返回了
// 可以看到后面的postHandle 如果前面的preHandle校验失败,postHandle是不会执行的
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
// 调用我们自定义的拦截器中的postHandle方法
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);
// 细心的你如果点进triggerAfterCompletion中就会发现
// 如果发生异常将调用的我们自定义拦截器中的afterCompletion方法
} 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);
这个中也有调用,源码如下:
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.");
}
// 当前解析已经开始,即通过反射数据已经绑定到对应的视图上了,接下来执行afterCompletion方法
// 我们可以在afterCompletion记录异常日志,但是如果我们结合@ExceptionHandler自定义了异常处理
// 这里就会是一个null,即表示异常已经处理过了,spring这里不会再将异常传递出去(感兴趣可以自行试下);
// 实际上我们也很少在afterCompletion 这个节点再去处理异常,我们一般都在postHandle中处理,因为业务上的异常基本发生在数据查询及逻辑处理中
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注解来声明
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;
/**
* @author Administrator
*/
@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;
// 添加过滤处理逻辑
// String urlsString= request.getRequestURI();
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
filterChain.doFilter(request,response);
}
@Override
public void destroy() {
}
}
或者如下自定义一个FilterRegistrationBean 类的bean:
@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 过滤器、拦截器的区别与使用