• Springboot中SpringMvc拦截器配置与应用(实战)


    一、什么是拦截器,及其作用

    拦截器(Interceptor): 用于在某个方法被访问之前进行拦截,然后在方法执行之前或之后加入某些操作,其实就是AOP的一种实现策略。它通过动态拦截Action调用的对象,允许开发者定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。

    拦截器的使用场景越来越多,尤其是面向切片编程流行之后。那通常拦截器可以做什么呢?
    之前我们在Agent介绍中,提到过统计函数的调用耗时。这个思路其实和AOP的环绕增强如出一辙。

    那一般来说,场景如下:

    1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。

    2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;

    3、函数增强:比如对一个函数进行参数检查,或者结果过滤等。甚至可以对函数就行权限认证。

    4、性能监控:统计函数性能,有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);

    5、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。

    6、OpenSessionInView:如Hibernate,在进入处理器打开Session,在完成后关闭Session。

    …………本质也是AOP(面向切面编程),也就是说符合横切关注点的所有功能都可以放入拦截器实现。

    二、springmvc拦截器相关接口和实现类

    package org.springframework.web.servlet;  
    public interface HandlerInterceptor {  
        boolean preHandle(  
                HttpServletRequest request, HttpServletResponse response,   
                Object handler)   
                throws Exception;  
      
        void postHandle(  
                HttpServletRequest request, HttpServletResponse response,   
                Object handler, ModelAndView modelAndView)   
                throws Exception;  
      
        void afterCompletion(  
                HttpServletRequest request, HttpServletResponse response,   
                Object handler, Exception ex)  
                throws Exception;  
    }  
    public interface AsyncHandlerInterceptor extends HandlerInterceptor {
    
        void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception;
    
    }

    public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
    
        // 在目标方法执行前执行
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            return true;
        }
    
        // 在目标方法执行后执行,但在请求返回前,我们仍然可以对 ModelAndView进行修改
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 
                              throws Exception {}
    
        // 在请求已经返回之后执行
        @Override
        public void afterCompletion(
                HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                throws Exception {}
    
        // 用来处理异步请求, 当Controller中有异步请求方法的时候会触发该方法
        @Override
        public void afterConcurrentHandlingStarted(
                HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {}
    }

    三、如何配置拦截器

    1、springboot项目中如何配置拦截器

    实现自定义拦截器只需要3步: 
    1、创建我们自己的拦截器类并实现 HandlerInterceptor 接口。 
    2、创建一个Java类继承WebMvcConfigurerAdapter,并重写 addInterceptors 方法。 
    3、实例化我们自定义的拦截器,然后将对像手动添加到拦截器链中(在addInterceptors方法中添加)。

    package com.springboot.study.interceptors;
     
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
     
    public class MyInterceptor1 implements HandlerInterceptor{
     
        @Override
        public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
                throws Exception {
            System.out.println("=====>(1)在整个请求之后调用,即在dispatcherServlet渲染了对应的视图之后(主要是进行资源清理工作)");
        }
     
        @Override
        public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
                throws Exception {
            System.out.println("=====>(1)在请求处理之后调用,即在controller方法执行之后调用");
        }
     
        @Override
        public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
            System.out.println("=====>(1)在请求处理之前调用,即在Controller方法调用之前!");
            return true;//只有返回true才会往下执行,返回FALSE的话就会取消当前请求
        }
     
    }
    package com.springboot.study.interceptors;
     
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
     
    public class MyInterceptor2 implements HandlerInterceptor{
     
        @Override
        public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
                throws Exception {
            System.out.println("=====>(2)在整个请求之后调用,即在dispatcherServlet渲染了对应的视图之后(主要是进行资源清理工作)");
        }
     
        @Override
        public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
                throws Exception {
            System.out.println("=====>(2)在请求处理之后调用,即在controller方法执行之后调用");
        }
     
        @Override
        public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
            System.out.println("=====>(2)在请求处理之前调用,即在Controller方法调用之前!");
            return true;//只有返回true才会往下执行,返回FALSE的话就会取消当前请求
        }
     
    }
    package com.springboot.study.controller;
     
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
     
    @RestController
    public class MyController {
        
        @RequestMapping("/index")
        public String index(){
            return "hello!";
        }
        
    }
    package com.springboot.study.config;
     
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
     
    import com.springboot.study.interceptors.MyInterceptor1;
    import com.springboot.study.interceptors.MyInterceptor2;
     
    @Configuration
    public class MyWebAppConfigurer extends WebMvcConfigurerAdapter{
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
            registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**");
            super.addInterceptors(registry);
        }
    }
    运行结果:
    =====>(1)在请求处理之前调用,即在Controller方法调用之前! =====>(2)在请求处理之前调用,即在Controller方法调用之前! =====>(2)在请求处理之后调用,即在controller方法执行之后调用 =====>(1)在请求处理之后调用,即在controller方法执行之后调用 =====>(2)在整个请求之后调用,即在dispatcherServlet渲染了对应的视图之后(主要是进行资源清理工作) =====>(1)在整个请求之后调用,即在dispatcherServlet渲染了对应的视图之后(主要是进行资源清理工作)

    四、实例

    自定义权限注解

    定义一个@interface

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Access {
     
        String[] value() default {};
     
        String[] authorities() default {};
     
        String[] roles() default {};
     
    }

    @Target注解是标注这个类它可以标注的位置:
    常用的元素类型(ElementType):

    public enum ElementType {
        /** Class, interface (including annotation type), or enum declaration */
        // TYPE类型可以声明在类上或枚举上或者是注解上
        TYPE,
        /** Field declaration (includes enum constants) */
        // FIELD声明在字段上
        FIELD,
        /** Method declaration */
        // 声明在方法上
        METHOD,
        /** Formal parameter declaration */
        // 声明在形参列表中
        PARAMETER,
        /** Constructor declaration */
        // 声明在构造方法上
        CONSTRUCTOR,
        /** Local variable declaration */
        // 声明在局部变量上
        LOCAL_VARIABLE,
        /** Annotation type declaration */
        ANNOTATION_TYPE,
        /** Package declaration */
        PACKAGE,
        /**
         * Type parameter declaration
         *
         * @since 1.8
         */
        TYPE_PARAMETER,
        /**
         * Use of a type
         *
         * @since 1.8
         */
        TYPE_USE
    }

    @Retention注解表示的是本注解(标注这个注解的注解保留时期)

    public enum RetentionPolicy {
        /**
         * Annotations are to be discarded by the compiler.
         */
        // 源代码时期
        SOURCE,
        /**
         * Annotations are to be recorded in the class file by the compiler
         * but need not be retained by the VM at run time.  This is the default
         * behavior.
         */
        // 字节码时期, 编译之后
        CLASS,
        /**
         * Annotations are to be recorded in the class file by the compiler and
         * retained by the VM at run time, so they may be read reflectively.
         *
         * @see java.lang.reflect.AnnotatedElement
         */
         // 运行时期, 也就是一直保留, 通常也都用这个
        RUNTIME
    }

    @Documented是否生成文档的标注, 也就是生成接口文档是, 是否生成注解文档

    注解说完了, 下面需要到对应的controller的方法中取添加注解, 配置该方法允许的权限

    在方法上配置权限

    @RestController
    public class HelloController {
     
        @RequestMapping(value = "/admin", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET)
        // 配置注解权限, 允许身份为admin, 或者说允许权限为admin的人访问
        @Access(authorities = {"admin"})
        public String hello() {
            return "Hello, admin";
        }
    }

    编写权限拦截逻辑

    // 自定义一个权限拦截器, 继承HandlerInterceptorAdapter类
    public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
     
        // 在调用方法之前执行拦截
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 将handler强转为HandlerMethod, 前面已经证实这个handler就是HandlerMethod
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 从方法处理器中获取出要调用的方法
            Method method = handlerMethod.getMethod();
            // 获取出方法上的Access注解
            Access access = method.getAnnotation(Access.class);
            if (access == null) {
            // 如果注解为null, 说明不需要拦截, 直接放过
                return true;
            }
            if (access.authorities().length > 0) {
                // 如果权限配置不为空, 则取出配置值
                String[] authorities = access.authorities();
                Set<String> authSet = new HashSet<>();
                for (String authority : authorities) {
                // 将权限加入一个set集合中
                    authSet.add(authority);
                }
                // 这里我为了方便是直接参数传入权限, 在实际操作中应该是从参数中获取用户Id
                // 到数据库权限表中查询用户拥有的权限集合, 与set集合中的权限进行对比完成权限校验
                String role = request.getParameter("role");
                if (StringUtils.isNotBlank(role)) {
                    if (authSet.contains(role)) {
                    // 校验通过返回true, 否则拦截请求
                        return true;
                    }
                }
            }
            // 拦截之后应该返回公共结果, 这里没做处理
            return false;
        }
     
    }

    拦截器详解源码地址:https://www.cnblogs.com/fangjian0423/p/springMVC-interceptor.html

  • 相关阅读:
    设计模式之美学习-接口隔离原则(七)
    设计模式之美学习-里式替换原则(六)
    设计模式之美学习-开闭原则(五)
    设计模式之美学习-设计原则之单一职责(四)
    设计模式之美学习-如何进行面向对象设计(三)
    ffmpeg 从内存中读取数据(或将数据输出到内存)
    CImage 对话框初始化时候显示透明 PNG
    RTMPdump(libRTMP) 源代码分析 9: 接收消息(Message)(接收视音频数据)
    RTMPdump(libRTMP) 源代码分析 8: 发送消息(Message)
    RTMPdump(libRTMP) 源代码分析 7: 建立一个流媒体连接 (NetStream部分 2)
  • 原文地址:https://www.cnblogs.com/ysq2018China/p/10250897.html
Copyright © 2020-2023  润新知