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

    余路那么长,还是得带着虔诚上路...
  • 相关阅读:
    致那些不甘寂寞的人
    实现一个前端路由,如何实现浏览器的前进与后退 ?
    github 授权登录教程与如何设计第三方授权登录的用户表
    十分钟弄懂:数据结构与算法之美
    前端架构师亲述:前端工程师成长之路的 N 问 及 回答
    JavaScript 数据结构与算法之美
    JavaScript 数据结构与算法之美
    JavaScript 数据结构与算法之美
    HTTPS中间人攻击实践(原理·实践)
    借助FreeHttp任意篡改http报文 (使用·实现)
  • 原文地址:https://www.cnblogs.com/itiaotiao/p/12886473.html
Copyright © 2020-2023  润新知