Spring全家桶系列--SpringBoot之AOP详解(下)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Spring全家桶系列--SpringBoot之AOP详解(下)

5.@AfterReturning


切入点返回结果之后执行,也就是都前置后置环绕都执行完了,这个就执行了


  1.    /**
  2.     * 执行完请求可以做的
  3.     * @param result
  4.     * @throws Throwable
  5.     */
  6.    @AfterReturning(returning ="result", pointcut ="cutOffPoint()")
  7.    publicvoid doAfterReturning(Object result)throwsThrowable{
  8.        logger.info("大家好,我是@AfterReturning,他们都秀完了,该我上场了");
  9.    }


执行结果


image.png


应用场景可以用来在订单支付完成之后就行二次的结果验证,重要参数的二次校验,防止在方法执行中的时候参数被修改等等


6.@AfterThrowing


这个是在切入执行报错的时候执行的


  1.    // 声明错误e时指定的抛错类型法必会抛出指定类型的异常
  2.    // 此处将e的类型声明为Throwable,对抛出的异常不加限制
  3.    @AfterThrowing(throwing ="e",pointcut ="cutOffPoint()")
  4.    publicvoid doAfterReturning(Throwable e){
  5.        logger.info("大家好,我是@AfterThrowing,他们犯的错误,我来背锅");
  6.        logger.info("错误信息"+e.getMessage());
  7.    }


在其他切入内容中随意整个错误出来,制造一个环境。


下面是@AfterThrowing的执行结果


image.png


7.AOP用在全局异常处理


定义切入点拦截ResultBean或者PageResultBean


  1.    @Pointcut(value ="execution(public com.example.beans.PageResultBean *(..)))")
  2.    publicvoid handlerPageResultBeanMethod(){
  3.    }


  4.    @Pointcut(value ="execution(public com.example.beans.ResultBean *(..)))")
  5.    publicvoid handlerResultBeanMethod(){
  6.    }



下面是AopController.java


  1. package com.example.aop;

  2. import com.example.beans.PageResultBean;
  3. import com.example.beans.ResultBean;
  4. import com.example.entity.UnloginException;
  5. import com.example.exception.CheckException;
  6. import org.aspectj.lang.ProceedingJoinPoint;
  7. import org.aspectj.lang.annotation.Around;
  8. import org.aspectj.lang.annotation.Aspect;
  9. import org.aspectj.lang.annotation.Pointcut;
  10. import org.slf4j.Logger;
  11. import org.slf4j.LoggerFactory;
  12. import org.springframework.stereotype.Component;

  13. /**
  14. * 使用@Aspect注解将此类定义为切面类
  15. * 根据晓风轻著的ControllerAOP所修改
  16. * 晓风轻大佬(很大的佬哥了):https://xwjie.github.io/
  17. */
  18. @Aspect
  19. @Component
  20. publicclassAopController{

  21.    privatestaticfinalLogger logger =LoggerFactory.getLogger(AopController.class);

  22.    ThreadLocal<ResultBean> resultBeanThreadLocal =newThreadLocal<>();
  23.    ThreadLocal<PageResultBean<?>> pageResultBeanThreadLocal =newThreadLocal<>();
  24.    ThreadLocal<Long> start =newThreadLocal<>();

  25.    /**
  26.     * 定义一个切点
  27.     */
  28.    @Pointcut(value ="execution(public com.example.beans.PageResultBean *(..)))")
  29.    publicvoid handlerPageResultBeanMethod(){
  30.    }


  31.    @Pointcut(value ="execution(public com.example.beans.ResultBean *(..)))")
  32.    publicvoid handlerResultBeanMethod(){
  33.    }

  34.    @Around("handlerPageResultBeanMethod()")
  35.    publicObject handlerPageResultBeanMethod(ProceedingJoinPoint pjp){
  36.        start.set(System.currentTimeMillis());
  37.        try{
  38.            pageResultBeanThreadLocal.set((PageResultBean<?>)pjp.proceed());
  39.            logger.info(pjp.getSignature()+" 方法执行耗时:"+(System.currentTimeMillis()- start.get()));
  40.        }catch(Throwable e){
  41.            ResultBean<?> resultBean = handlerException(pjp , e);
  42.            pageResultBeanThreadLocal.set(newPageResultBean<>().setMsg(resultBean.getMsg()).setCode(resultBean.getCode()));
  43.        }
  44.        return pageResultBeanThreadLocal.get();
  45.    }

  46.    @Around("handlerResultBeanMethod()")
  47.    publicObject handlerResultBeanMethod(ProceedingJoinPoint pjp){
  48.        start.set(System.currentTimeMillis());
  49.        try{
  50.            resultBeanThreadLocal.set((ResultBean<?>)pjp.proceed());
  51.            logger.info(pjp.getSignature()+" 方法执行耗时:"+(System.currentTimeMillis()- start.get()));
  52.        }catch(Throwable e){
  53.            resultBeanThreadLocal.set(handlerException(pjp , e));
  54.        }
  55.        return resultBeanThreadLocal.get();
  56.    }
  57.    /**
  58.     * 封装异常信息,注意区分已知异常(自己抛出的)和未知异常
  59.     */
  60.    privateResultBean<?> handlerException(ProceedingJoinPoint pjp,Throwable e){

  61.        ResultBean<?> result =newPageResultBean();
  62.        logger.error(pjp.getSignature()+" error ", e);

  63.        // 已知异常
  64.        if(e instanceofCheckException){
  65.            result.setMsg(e.getLocalizedMessage());
  66.            result.setCode(ResultBean.FAIL);
  67.        }elseif(e instanceofUnloginException){
  68.            result.setMsg("Unlogin");
  69.            result.setCode(ResultBean.NO_LOGIN);
  70.        }else{
  71.            result.setMsg(e.toString());
  72.            result.setCode(ResultBean.FAIL);
  73.        }
  74.        return result;
  75.    }
  76. }


用上面的环绕通知可以对所有返回ResultBean或者PageResultBean的方法进行切入,这样子就不用在业务层去捕捉错误了,只需要去打印自己的info日志。

看下面一段代码


  1.    @Transactional
  2.    @Override
  3.    publicint insertSelective(Area record){
  4.        record.setAddress("test");
  5.        record.setPostalcode(88888);
  6.        record.setType(3);
  7.        int i=0;
  8.        try{
  9.            i = areaMapper.insertSelective(record);
  10.        }catch(Exception e){
  11.            logger.error("AreaServiceImpl insertSelective error:"+e.getMessage());
  12.        }
  13.        return i;
  14.    }


假如上面的插入操作失败出错了? 你认为会回滚吗?


答案是:不会。


为什么?


因为你把错误捕捉了,事物没检测到异常就不会回滚。


那么怎么才能回滚呢?


在catch里加throw new RuntimeException().


可是那么多业务方法每个设计修改的操作都加,代码繁琐,怎么进行处理呢?


在这里用到上面的AOP切入处理,错误不用管,直接抛,抛到控制层进行处理,这样的话,接口调用的时候,出错了,接口不会什么都不返回,而是会返回给你错误代码,以及错误信息,便于开发人员查错。


8.以上用的是log4j2的日志处理


先移除springboot自带的log日志处理


在build.gradle中增加

  1. configurations {
  2.    providedRuntime
  3.    // 去除SpringBoot自带的日志
  4.    all*.exclude group:'org.springframework.boot',module:'spring-boot-starter-logging'
  5. }
  6. ext {
  7.    springBootVersion ='2.0.1.RELEASE'
  8. }
  9. dependencies {
  10.    compile "org.springframework.boot:spring-boot-starter-log4j2:${springBootVersion}"
  11. }


然后在application.yml中增加


  1. #显示mysql执行日志
  2. logging:
  3.  level:
  4.    com:
  5.      example:
  6.        dao: debug
  7.  config: classpath:log4j2-spring.xml


log4j2-spring.xml


  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
  3. <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
  4. <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
  5. <configurationstatus="INFO"monitorInterval="30">
  6.    <!--先定义所有的appender-->
  7.    <appenders>
  8.        <!--这个输出控制台的配置-->
  9.        <consolename="Console"target="SYSTEM_OUT">
  10.            <!--输出日志的格式-->
  11.            <PatternLayoutpattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/>
  12.        </console>

  13.        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
  14.        <Filename="Test"fileName="logs/test.log"append="false">
  15.            <PatternLayoutpattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/>
  16.        </File>

  17.        <RollingFilename="RollingFileInfo"fileName="logs/log.log"filePattern="logs/info.log.%d{yyyy-MM-dd}">
  18.            <!-- 只接受level=INFO以上的日志 -->
  19.            <ThresholdFilterlevel="info"onMatch="ACCEPT"onMismatch="DENY"/>
  20.            <PatternLayoutpattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/>
  21.            <Policies>
  22.                <TimeBasedTriggeringPolicymodulate="true"interval="1"/>
  23.                <SizeBasedTriggeringPolicy/>
  24.            </Policies>
  25.        </RollingFile>

  26.        <RollingFilename="RollingFileError"fileName="logs/error.log"filePattern="logs/error.log.%d{yyyy-MM-dd}">
  27.            <!-- 只接受level=WARN以上的日志 -->
  28.            <Filters>
  29.                <ThresholdFilterlevel="warn"onMatch="ACCEPT"onMismatch="DENY"/>
  30.            </Filters>
  31.            <PatternLayoutpattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/>
  32.            <Policies>
  33.                <TimeBasedTriggeringPolicymodulate="true"interval="1"/>
  34.                <SizeBasedTriggeringPolicy/>
  35.            </Policies>
  36.        </RollingFile>

  37.    </appenders>

  38.    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
  39.    <loggers>
  40.        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
  41.        <loggername="org.springframework"level="INFO"></logger>
  42.        <loggername="org.mybatis"level="INFO"></logger>
  43.        <rootlevel="all">
  44.            <appender-refref="Console"/>
  45.            <appender-refref="Test"/>
  46.            <appender-refref="RollingFileInfo"/>
  47.            <appender-refref="RollingFileError"/>
  48.        </root>
  49.    </loggers>
  50. </configuration>


之后在你要打印日志的类中增加


  1. privatestaticfinalLogger logger =LoggerFactory.getLogger(你的类名.class);

  2.    publicstaticvoid main(String[] args){
  3.        logger.error("error级别日志");
  4.        logger.warn("warning级别日志");
  5.        logger.info("info级别日志");
  6.    }


有了日志后就很方便了,在你的方法接收对象时打印下,然后执行了逻辑之后打印下, 出错之后很明确了,就会很少去Debug的,养成多打日志的好习惯,多打印一点info级别的日志,用来在开发环境使用,在上线的时候把打印的最低级别设置为warning,这样你的info级别日志也不会影响到项目的重要Bug的打印


写这个博客的时候我也在同时跑着这个项目,有时候会出现一些错误,例如jar包版本,业务层引用无效,AOP设置不生效等等,也同时在排查解决,如果你遇到了同样的错误,可以去我的GitHub联系我,如小弟有时间或许也能帮到你,谢谢

Github地址:https://github.com/cuifuan

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
16天前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
1月前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
40 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
21天前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
30 1
|
23天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
34 1
|
17天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
31 0
|
1月前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
67 2
|
1月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
109 1
|
1月前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
28 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
|
1月前
|
Java API Spring
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中过滤器的基础知识和实战项目应用的教程。
27 0
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
|
1月前
|
Java Spring
springboot 学习十一:Spring Boot 优雅的集成 Lombok
这篇文章是关于如何在Spring Boot项目中集成Lombok,以简化JavaBean的编写,避免冗余代码,并提供了相关的配置步骤和常用注解的介绍。
105 0
下一篇
无影云桌面