zoukankan      html  css  js  c++  java
  • spring拦截器Interceptor

    在Spring Boot中,拦截器可以分为两种类型:

    • 一是WebMVC,负责拦截请求,类似于过滤器,对用户的请求在Controller接收前进行处理,在Controller处理完成后加工结果等。使用时需实现HandlerInterceptor接口。
    • 一是AOP,拦截指定类型的方法,通过动态代理模式实现,可以在方法的调用前和调用后添加功能处理。使用时需要实现MethodInterceptor接口。

    拦截器(Interceptor)和过滤器(Filter)对比

    相同点:

    • 都可以对请求进行提前处理和响应内容加工。
    • 都支持多个拦截器/过滤器的链路传递。

    不同点:

    • 拦截器由Spring提供,过滤器由Servlet提供。

    HandlerInterceptor

    HandlerInterceptor接口属于顶级接口,里面一共有三个default方法(这是jdk8的新特性,用来在接口中编写带有方法体的方法。实现接口可以不重写default方法,默认调用的仍是接口中的default方法体)

    public interface HandlerInterceptor {
        default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            return true;
        }
    
        default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        }
    
        default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        }
    }
    boolean preHandle:会在controller处理前调用该方法,方法返回true则进入对应的controller。方法返回false则不会进入controller。可以用来编码,安全控制,权限校验等。
    void postHandle:在controller处理完成返回ModelAndView后执行。此时还没有进行视图渲染,还可以修改ModelAndView。
    void afterCompletion:整个请求已经处理完成了,可能返回了正常的请求结果,也可能返回一个异常。

    下面我们来做一个简单的demo。 

    先定义一个controller:

    @RestController
    public class UserController {
    
        @GetMapping("users/{id}")
        public String getUser(@PathVariable("id") String id) {
            System.out.println("controller[url=users/" + id + "]");
            return "testUser";
        }
    
        @GetMapping("users/login")
        public String test(User user, Model model) {
            System.out.println("controller[url=users/login]");
            model.addAttribute("id", user.getId());
            model.addAttribute("name", user.getName());
            model.addAttribute("password", user.getPassword());
            model.addAttribute("mail", user.getMail());
            return "index";
        }
    }

    里面提供了两个请求链接:/users/{id}和/users/login,这里为了简单方便,我们均将请求方式设置为get。

    定义一个拦截器UserInteceptor(这里先测试第一个方法preHandle):

    @Component
    public class UserInteceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("preHandle;");
            Map<String, String[]> map = request.getParameterMap();
            map.forEach((k, v) -> {
                System.out.println("[Key=" + k + ";Value=" + StringUtils.join(v) + "];");
            });
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("postHandle");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("afterCompletion");
        }
    }

    然后将拦截器添加进容器中,设定它的拦截路径为/users/**:

    @Configuration
    public class InteceptorConfig implements WebMvcConfigurer {
    
        @Autowired
        private UserInteceptor userInteceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(userInteceptor).addPathPatterns("/users/*");
        }
    }

    就此一个简单的项目已经构成,我们启动程序,然后访问/users/login?id=1&username=yxf&password=123&mail=5@qq.com

    可以看到访问结果:

    preHandle; //----------------------------调用了preHandle方法
    [Key=id;Value=1]; //---------------------打印request中的id
    [Key=name;Value=yxf]; //-----------------打印request中的name
    [Key=password;Value=123]; //-------------打印request中的password
    [Key=mail;Value=5@qq.com]; //------------打印request中的mail
    controller[url=users/login] //-----------在preHandle之后,这里进入UserController的test方法。
    postHandle //----------------------------controller处理完成后调用PostHandle方法。
    //---------------在这中间其实还有dispatchServlet调用视图解析器对View的解析等------------------ afterCompletion //-----------------------postHandle处理完成后调用afterCompletion。

     顺序是按照preHandle→Controller→postHandle→视图渲染器→afterCompletion的顺序执行。

    MethodInterceptor

    MethodInterceptor继承关系:

    在MethodInterceptor接口中,只提供了一个方法

    Object invoke(MethodInvocation invocation) throws Throwable;

    首先分析方法的传入参数MethodInvocation

    MethodInvocation对象继承关系如下

    相关方法说明

    Method getMethod(); //获取Java反射类Method
    
    Object[] getArguments(); // 获取方法的传入参数
    
    Object proceed() throws Throwable; // 继续执行方法
    
    Object getThis(); // 获取方法所在的对象
    
    AccessibleObject getStaticPart(); // 获取的也是Java反射类Method

    测试Demo

    为了方便一部分功能展示,我们这里定义一个注解用来后续操作

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DemoAnnotation {
    
        String value() default "test";
        
    }

    定义我们的方法过滤器,从参数对象获取到被拦截的方法的相关信息,这里为了简单直接将信息打印在控制台(实际情况因具体业务而异)。

    public class DemoInterceptor implements MethodInterceptor {
    
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            System.out.println("--------------------------------------------------");
    
            // 参数
            Object[] object = methodInvocation.getArguments();
            for (Object obj : object) {
                System.out.println(obj.getClass().getName());
                if (obj instanceof String) {
                    System.out.println("param = " + ((String) obj).toString());
                }
            }
    
            // 对象
            Object obj = methodInvocation.getThis();
            System.out.println(obj.getClass().getName());
    
            // 方法
            Method method = methodInvocation.getMethod();
    
            // 继续执行
            obj = methodInvocation.proceed();
            System.out.println(obj.getClass().getName());
    
            // 获取注解
            DemoAnnotation d = method.getAnnotation(DemoAnnotation.class);
            if (d == null) {
                System.out.println("当前类没有DemoAnnotation注解");
            } else {
                System.out.println(d.value());
            }
    
            System.out.println("--------------------------------------------------");
            return null;
        }
    }

    配置过滤器。(方法过滤器实际可以算是实现AOP的一种方式,我们需要定义一个切面,配置它的切点和增强)

        @Bean
        public DefaultPointcutAdvisor defaultPointcutAdvisor() {
            // 声明切点
    //        JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
    //        pointcut.setPatterns("com.example.*");
            AspectJExpressionPointcut pointcut =new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* com.example..*(*))"); // 拦截com.example包和子包下带一个参数的任何方法
    
            // 声明增强
            DemoInterceptor interceptor = new DemoInterceptor();
    
            // 配置切面
            DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
            advisor.setPointcut(pointcut);
            advisor.setAdvice(interceptor);
    
            return advisor;
        }

    定义调用类,这里为了简单,也没有写service层,直接在controller层调用了。

    @RestController
    public class DemoController {
    
        @GetMapping("/demo/{id}")
        public String demo(@PathVariable("id") String id) {
            return "test demo";
        }
    
        @GetMapping("/demo2/{id}")
        @DemoAnnotation
        public String demo2(@PathVariable("id") String id) {
            return "test demo";
        }
    
    }

    运行程序,分别访问上面的两个地址,打印如下

    /demo/1

    --------------------------------------------------
    java.lang.String
    param = 1
    com.example.demo.controller.DemoController
    java.lang.String
    当前类没有DemoAnnotation注解
    --------------------------------------------------

    /demo2/2

    --------------------------------------------------
    java.lang.String
    param = 2
    com.example.demo.controller.DemoController
    java.lang.String
    test
    --------------------------------------------------

    结束

  • 相关阅读:
    Linux进程管理及while循环(转)
    AT5661-[AGC040C]Neither AB nor BA【模型转换】
    CF573D-Bear and Cavalry【动态dp】
    关于专人整理和分析需求
    Codeforces 1005D Polycarp and Div 3
    [Luogu]P5513 [CEOI2013]Board
    IDEA Mybatis 中文数据添加到MySQL,显示乱码
    如何比较两个word文档的差异
    抗体计算设计
    抗体
  • 原文地址:https://www.cnblogs.com/yxth/p/10865396.html
Copyright © 2011-2022 走看看