2023-06-18
原文作者:代码有毒 mrcode 原文地址:https://mrcode.blog.csdn.net/article/details/81502532

使用切片拦截rest服务

本节内容

  • 过滤器(Filter)
  • 拦截器(interceptor)
  • 切片(Aspect)

假设一个需求:打印出所有请求的耗时时间

Filter

  • 实现一个javax.servlet.Filter
  • @Component 让这个实现类被spring容器接管

就可以让过滤器生效了

    package com.example.demo.web.filter;
    
    import org.springframework.stereotype.Component;
    
    import javax.servlet.*;
    import java.io.IOException;
    import java.time.Duration;
    import java.time.Instant;
    
    /**
     * ${desc}
     * @author zhuqiang
     * @version 1.0.1 2018/8/2 14:42
     * @date 2018/8/2 14:42
     * @since 1.0
     */
    @Component  // 生效需要让spring容器接管
    public class TimeFilter implements Filter {
        // 初始化
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("TimeFilter init");
        }
    
        // 执行
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            Instant start = Instant.now();
            chain.doFilter(request, response);
            System.out.println("耗时:" + Duration.between(start, Instant.now()).toMillis());
        }
    
        // 销毁
        @Override
        public void destroy() {
            System.out.println("TimeFilter destroy");
        }
    }

编码注册过滤器

传统的过滤器可以使用 web.xml 等方式注册,spring boot 里面可以通过配置类添加

这种方式可以把一些第三方的过滤器添加进来

    package com.example.demo.web.config;
    
    import com.example.demo.web.filter.TimeFilter;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.servlet.Filter;
    
    @Configuration  // 这里的注解别用错了
    public class WebConfig {
        @Bean
        public FilterRegistrationBean timeFilter() {
            FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
            registrationBean.setFilter(new TimeFilter());
            // 可以自定义拦截路径
            registrationBean.addUrlPatterns("/*");
            return registrationBean;
        }
    }

过滤器有一些限制,比如获取不到具体是哪一个方法处理的;

过滤器是j2ee的规范,在拦截器之前,还没有进入我们的具体控制器方法的时候被调用

拦截器(interceptor)

  • 实现HandlerInterceptor拦截器
  • 添加到spring kvc中

实现拦截器

    @Configuration
    public class TimeInterceptor implements HandlerInterceptor {
        // 进入方法前
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            request.setAttribute("startTime", Instant.now());
            HandlerMethod method = (HandlerMethod) handler;
            System.out.println("preHandle " + method.getBean().getClass().getName());
            System.out.println("preHandle " + method.getMethodParameters());
            System.out.println("preHandle");
            return true;
        }
    
        // 进入方法后
        // 如果方法异常,则不会进入该节点
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            Instant startTime = (Instant) request.getAttribute("startTime");
            System.out.println("postHandle 耗时" + Duration.between(startTime, Instant.now()).toMillis());
        }
    
        // 请求后:无论如何都会走该节点
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
            System.out.println("afterCompletion");
            // 注意这里的异常,如果异常被全局异常处理器ControllerExceptionHandler消费掉了的话,这里的异常信息的null
            System.out.println("afterCompletion ex" + ex);
        }
    }

添加到spring mvc中: 这个不同于过滤器的添加逻辑,需要手动进行配置

    @Configuration
    // org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter 5.0+已过时
    // 使用了jdk8 的接口默认方法
    public class WebConfig implements WebMvcConfigurer {
        @Autowired
        private TimeInterceptor timeInterceptor;
    
        @Bean
        public FilterRegistrationBean timeFilter() {
            FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
            registrationBean.setFilter(new TimeFilter());
            // 可以自定义拦截路径
            registrationBean.addUrlPatterns("/*");
            return registrationBean;
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // 可以添加多个不同的拦截器
            registry.addInterceptor(timeInterceptor);
        }
    }

切片(Aspect)

提供了更为强大的拦截功能,不只是能拦截mvc的handler;还能拦截符合截点的所有方法或则类

  • 切入点(注解)

    • 在哪些方法上起作用
    • 在什么时候起作用
  • 增强(方法)

    • 起作用时执行的业务逻辑

Aspect 注解属于aop包
`compile(“org.springframework.boot:spring-boot-starter-aop”)

官网能查看表切片怎么使用 和表达式是什么意思
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop

    @Component
    @Aspect
    public class TimeAspect {
        // 环绕通知 还有其他类型的注解
        // 这里的表达式在官网可以学习怎么使用
        // https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop
        @Around("execution(* com.example.demo.web.controller.UserController.*(..))")
        public Object doAccessCheck(ProceedingJoinPoint point) throws Throwable {
            Instant start = Instant.now();
            Object proceed = point.proceed();  // 类似于调用过滤器链一样
            // 这里对于异常来说和之前的都类似,异常的话下面不会继续走了
            System.out.println("耗时:" + Duration.between(start, Instant.now()).toMillis());
            Object[] args = point.getArgs();
            for (Object arg : args) {
                System.out.println(arg);
            }
            return proceed;
        }
    }

总结

  • 过滤器(Filter)

    • 能拿到最原始的http请求响应对象
    • 拿不到路径具体对于的handler
  • 拦截器(interceptor)

    • 能拿到handler
    • spring家族成员
  • 切片(Aspect)

    • 拿不到http请求响应对象
    • 可以拦截的更多:比如拦截mybatis生成的dao接口方法执行

处理顺序大概是下面这样;controllerAdvice是全局异常处理器

202306181906533591.png

阅读全文