Interceptor使用及探究(附实例)

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 过滤器、拦截器的区别与使用