springboot的过滤器、拦截器、AOP

1、作用优先级

谨记: 过滤器 > 拦截器 > AOP

2、三者定义

2.1 过滤器(拦截器拦截的是URL)

Spring中自定义过滤器(Filter),当请求到达web容器时,会探测当前请求地址是否配置有过滤器,有则调用该过滤器的 doFilter 方法(可能会有多个过滤器),然后调用filterChain.doFilter(request,response)方法,才调用真实的业务逻辑,至此过滤器任务完成。

2.2 拦截器(拦截器拦截的是URL)

拦截器(HandlerInterceptor)有三个方法,相对于过滤器更加细致,有被拦截逻辑执行前、后等。Spring中拦截器有三个方法:preHandle,postHandle,afterCompletion。当请求匹配到对应的拦截器时,先执行 preHandle方法,返回值为boolean类型,true代表继续执行,false代表请求终止。若为true时,执行完preHandle方法,则执行真正的业务请求方法。postHandle在处理完业务请求后,返回时生效。而afterCompletion是整个请求完成才执行。

2.2 AOP(面向切面)

面向切面拦截的是类的元数据(包、类、方法名、参数等)

相对于过滤器、拦截器更加细致,而且非常灵活,拦截器只能针对URL做拦截,而AOP针对具体的代码,能够实现更加复杂的业务逻辑

3、三者使用场景

从过滤器–》拦截器–》切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能。

4、小试牛刀

ps:springboot版本 1.5.14.RELEASE ,由于属于springmvc内容,版本影响应该不大。注释内容都可以试试,我自己留个印象,我就不删了

4.1 过滤器 filter

两种实现方式:一、 注解@WebFilter 二、 配置文件
自定义filter,可以用@WebFilter进行注册(启动类记得添加注解 @ServletComponentScan 进行扫描),亲测本版本使用@order注解未能对过滤器进行排序,其他版本请自行测试。

/**
 * 自定义 过滤器   启动类添加 @ServletComponentScan//扫描自定义过滤器
 * 过滤器可以改变用户请求的资源  例如修改用户请求路径
 * 过滤器不能直接处理用户请求,返回数据
 */
@Slf4j
//@WebFilter(urlPatterns = "/*", filterName = "myFilter")  注解方式不支持排序
public class myFilter implements Filter {
     
    private FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
     
        this.filterConfig=filterConfig;
        //可以这里设置初始化参数 也可以在下面第二种方式:配置文件中配置
       //filterConfig.getServletContext().setInitParameter("encoding","utf-8");
        //
        System.out.println("初始化参数,自定义过滤器加载----------------------------------------------------");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
     
        log.info("进入自定义过滤器1--------------------");
        //System.out.println(this.filterConfig.getInitParameter("test0"));
        HttpServletResponse httpServletResponse=  (HttpServletResponse)response;
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        //System.out.println(this.filterConfig.getInitParameter("test0"));
        //统一设置编码 utf-8(encoding 自定义初始化参数)
        //if(StringUtils.isNotBlank(this.filterConfig.getInitParameter("encoding"))){
     
            //httpServletRequest.setCharacterEncoding(this.filterConfig.getInitParameter("encoding"));
       // }

//        //避免重定向路径 死循环
//        if(httpServletRequest.getRequestURI().contains("/excel/index")){
     
//            filterChain.doFilter(request,response);
//            return;
//        }
        //重定向(浏览器路径发生改变,重定向到xxx路径)
        //httpServletResponse.sendRedirect("/excel/index");
        //转发(浏览器路径不会发生改变)
        //request.getRequestDispatcher("/excel/index").forward(request,response);
        filterChain.doFilter(request,response);
       // httpServletRequest.setCharacterEncoding("utf-8");
        log.info("离开自定义过滤器1--------------------");

    }

    @Override
    public void destroy() {
     
        System.out.println("自定义过滤器删除----------------------------------------------------");}
}


方式二、采用配置文件,进行注册,可以实现多过滤器执行排序,order越小 优先级越高

/**
 * 自定义 请求过滤器配置   order越小优先级约高  同理 order越大优先级约低
 */
@Configuration
public class MyFilterConfig {
     

	//过滤器一
    @Bean
    public FilterRegistrationBean myFilter(){
     
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new myFilter());
        //自定义一些参数
        registrationBean.addInitParameter("test0","true");
        registrationBean.addInitParameter("test1", "test");
         registrationBean.addInitParameter("encoding","utf-8");
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(100); // 设置排序
        //设置监听级别      FORWARD(转发),INCLUDE,REQUEST(请求),ASYNC(异步),ERROR
        registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
        return registrationBean;
    }

	//过滤器二
    @Bean
    public FilterRegistrationBean mySecondFilter(){
     
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new mySecondFilter());
        registrationBean.addInitParameter("test2","true");
        registrationBean.addInitParameter("test3", "test");
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(10); // 设置排序
        registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
        return registrationBean;
    }
}

过滤器Filter依赖于Servlet容器,过滤范围大。

4.2 拦截器 Interceptor

实现拦截器有两个接口,继承 HandlerInterceptor 或者继承 WebRequestInterceptor都可以。本章节只实现HandlerInterceptor。原因是 HandlerInterceptor更加强大。

**
 *自定义 拦截器   也可以集成 WebRequestInterceptor作为拦截器
 */
public class myHandlerInterceptor implements HandlerInterceptor
{
     
    /**
     *  * 返回值 false  请求终止
     *  * 返回值 true   请求继续执行
     *   Object o 表示被拦截的请求目标对象 即controller对象
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
     
        System.out.println("执行了拦截器  --------------preHandle方法");


        //return false;
        return true;
    }

    /**
     * 请求执行结束,返回时候,执行该方法
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
     
        System.out.println("执行了拦截器  --------------postHandle");
        //可以通过 modelAndView 修改视图路径

    }

    /**
     * 请求返回后,最后执行的方法
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param e
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
     
        System.out.println("执行了拦截器  --------------afterCompletion");
    }
}

注册自定义 拦截器myHandlerInterceptor 到容器 ,注意WebMvcConfigurer 有许多实现接口,但是为了美观,删除了,用到时候,引入一下就可以了,其他接口方法的用途请自行百度学习。

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
     


    /**
     * 拦截器注册
     * @param interceptorRegistry
     */
    @Override
    public void addInterceptors(InterceptorRegistry interceptorRegistry) {
     
        //多拦截器,就继续添加interceptorRegistry.addInterceptor  添加的顺序,就是拦截器的顺序

        interceptorRegistry.addInterceptor(new myHandlerInterceptor()).addPathPatterns("/test/*").excludePathPatterns("/test/test2");
        interceptorRegistry.addInterceptor(new myHandlerInterceptor2()).addPathPatterns("/test/*").excludePathPatterns("/test/test2");


    }


}

4.3 面向切面 AOP

AOP中 @Before @After @AfterThrowing@AfterReturning的执行顺序
基于 自定义注解作为切点 实现对日志的记录。

/**
 * 操作注解
 * @author 曾  自定义注解 用于操作日志
 */
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented
public @interface OperLog {
     

     String operModul() default ""; // 操作模块

     String operType() default "";  // 操作类型

     String operDesc() default "";  // 操作说明

}

AOP常用的通知注解有 @before @AfterReturning @ Around 本代码最终使用@ Around方法,其中@before @AfterReturning都已经实现,部分注释了

/**
 * 操作日志
 */
@Aspect  //切面注解
@Component
@Slf4j
public class OpenLoggerConfig {
     

    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     */
    @Pointcut("@annotation(com.cloud.user.config.OperLog)")
    public void operLogPointcut(){
     
    }

    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作  可以改成扫描的包路径
     */
    @Pointcut("execution(* com.cloud.user.myUser.*.controller..*.*(..))")
    public void operExceptionLogPointcut(){
     
    }

    /**
     * 执行切点方法之前,执行该方法
     * @param joinPoint
     */
    //@Before(value = "operLogPointcut()||operExceptionLogPointcut()")
    public void saveBeforeOperLog(JoinPoint joinPoint){
     
        log.info("我进来了,我即将要操作!!!------Before  Before  Before---------------");
    }


    /**
     * 环绕通知,无论是切点方法 开始还是结束,都执行该方法  @Around 需要提供返回值 (小坑)
     * @param joinPoint
     */
    @Around(value = "operLogPointcut()||operExceptionLogPointcut()")
    public Object saveAroundOperLog(ProceedingJoinPoint joinPoint) {
     
        Object proceed=null;
        log.info("我进来了,我即将要操作!!!--------Around  Around   Around-------------");
        long startTime = System.currentTimeMillis();
        System.err.println("startTime = " + startTime);
        try {
     
             proceed = joinPoint.proceed();
        }catch (Throwable e){
     
            //异常处理
            this.saveExceptionOperLog(joinPoint,e);
            long endTime = System.currentTimeMillis();
            System.err.println("endTime = " + endTime);
            long  alltime=endTime-startTime;
            log.info("我执行完了,我即将要离开!!!--------Around  Around   Around----------本次花费时间为:"+alltime+"毫秒");
            return proceed;
        }
        //正常操作处理
        this.saveOperLog(joinPoint,proceed);

        long endTime = System.currentTimeMillis();
        System.err.println("endTime = " + endTime);
        long  alltime=endTime-startTime;
        log.info("我执行完了,我即将要离开!!!--------Around  Around   Around----------本次花费时间为:"+alltime+"毫秒");
        return proceed;

    }

    /**
         * 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
         *
         * @param joinPoint 切入点
         * @param keys      返回结果
         */
    //@AfterReturning(pointcut = "operLogPointcut()",returning = "keys")
    public void saveOperLog(JoinPoint joinPoint, Object keys){
     
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 从获取RequestAttributes中获取HttpServletRequest的信息
                 HttpServletRequest request = (HttpServletRequest) requestAttributes
                         .resolveReference(RequestAttributes.REFERENCE_REQUEST);

                 try {
     
                         // 从切面织入点处通过反射机制获取织入点处的方法
                         MethodSignature signature = (MethodSignature) joinPoint.getSignature();
                         // 获取切入点所在的方法
                         Method method = signature.getMethod();
                         // 获取操作
                         OperLog opLog = method.getAnnotation(OperLog.class);
                        String operModul = "";
                        String operType = "";
                        String operDesc = "";
                         if (opLog != null) {
     
                                  operModul = opLog.operModul();
                                  operType = opLog.operType();
                                  operDesc = opLog.operDesc();

                             }
                         // 获取请求的类名
                         String className = joinPoint.getTarget().getClass().getName();
                         // 获取请求的方法名
                         String methodName = method.getName();
                         methodName = className + "." + methodName;
                         // 请求的参数
                         Map<String, String> returnMap = converMap(request.getParameterMap());

                         log.info("请求的路径为:"+request.getRequestURL()+"--------------");
                         log.info("请求的类方法为:"+methodName+"具体描述:"+operModul+operType+operDesc+"--------------");
                         log.info("请求的参数为:"+returnMap.toString()+"--------------");
                         if(keys==null){
     
                             log.info("请求方法返回结果为:"+null+"--------------");
                             return;
                         }
                         log.info("请求方法返回结果为:"+keys.toString()+"--------------");

                     } catch (Exception e) {
     
                         e.printStackTrace();
                     }
    }

    /**
     * 异常处理日志保存
     * @param joinPoint
     * @param error
     */
    //@AfterThrowing(pointcut = "operLogPointcut()",throwing = "error")
    public void saveExceptionOperLog(JoinPoint joinPoint, Throwable  error){
     
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);

        try {
     
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取操作
            OperLog opLog = method.getAnnotation(OperLog.class);
            String operModul = "";
            String operType = "";
            String operDesc = "";
            if (opLog != null) {
     
                operModul = opLog.operModul();
                operType = opLog.operType();
                operDesc = opLog.operDesc();

            }
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName;
            // 请求的参数
            Map<String, String> returnMap = converMap(request.getParameterMap());

            log.info("请求的路径为:"+request.getRequestURL()+"--------------");
            log.info("请求的类方法为:"+methodName+"具体描述:"+operModul+operType+operDesc+"--------------");
            log.info("请求的参数为:"+returnMap.toString()+"--------------");

            log.info("异常名称:"+error.getClass().getName()+"--------------");
            log.info("异常信息:"+stackTraceToString(error.getClass().getName(), error.getMessage(), error.getStackTrace()));

        } catch (Exception e) {
     
            e.printStackTrace();
        }
    }



     /**
       * 转换request 请求参数
       *
       * @param paramMap request获取的参数数组
     */
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
     
         Map<String, String> rtnMap = new HashMap<String, String>();
         for (String key : paramMap.keySet()) {
     
                 rtnMap.put(key, paramMap.get(key)[0]);
             }
         return rtnMap;
    }

    /**
           * 转换异常信息为字符串
           *
           * @param exceptionName    异常名称
           * @param exceptionMessage 异常信息
           * @param elements         堆栈信息
     */
     public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
     
//             StringBuffer strbuff = new StringBuffer();
//             for (StackTraceElement stet : elements) {
     
//                     strbuff.append(stet + "\n");
//                 }
             //String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
             String message = exceptionName + ":" + exceptionMessage ;
             return message;
     }
}

随便构建一个测试方法,访问即可(如图)。
springboot的过滤器、拦截器、AOP_第1张图片

5、总结

1、过滤器依赖于Servlet容器 ,拦截器不依赖与servlet容器,是SpringMVC自带的。

2、拦截器是基于java的反射机制的,而过滤器是基于函数回调

3、优先级:过滤器 > 拦截器 > AOP ,同时 粒度(粗->细)过滤器- > 拦截器 -> AOP

4、过滤器可以改变用户请求的资源 例如修改用户请求路径 ,不能直接处理用户请求,返回数据。

你可能感兴趣的:(过滤器,spring,java,aop,filter)