06_Springboot|Spring AOP 介绍与简单日志切面实现


文章目录

      • 一、AOP
        • 1、AOP含义
        • 2、模式演变
        • 3、OOP的弊端?
        • 4、AOP的应用场景
      • 二、Aspect概念
        • 1、AOP中主要概念
        • 2、通知的类型(advice)
        • 3、@Aspect驱动
      • 三、代码实现
        • 1、定一个日志切面类
        • 2、定义切点(Pointcut)
        • 3、定义通知(Advice)
        • 4、实现效果
      • 附录

一、AOP
1、AOP含义
  • AOP即面向切面编程。通过预编译方式和运行动态代理实现在不修改源代码的情况下给程序动态统一添加的一种技术。
  • AOP的编程思想就是把很多类对象中的横切问题点,从业务逻辑中分离出来, 从而达到解耦的目的,增加代码的重用性,提高开发效率。
  • AOP可以理解为一种编程范式,可以理解为软件工程的范畴,开发人员是如何组织代码结构的,最早由AOP联盟主导,后被Spring引入框架中。
2、模式演变
最早每个类每个函数下都会有日志记录,基本每个类中都会有日志记录,产生大量重复代码。
后来,形成工具类的方式,每个类对象调用工具日志类,OOP(Object Oriented Programming,面向对象编程)的方式。
06_Springboot|Spring AOP 介绍与简单日志切面实现
文章图片

演变为:
06_Springboot|Spring AOP 介绍与简单日志切面实现
文章图片

3、OOP的弊端?
  • 形成依赖关系。会在Class A、Class B中调用工具类中的代码,形成依赖关系
  • 每个类中都要调用如Log方法
  • 如果产生修改,每个地方都要进行修改
4、AOP的应用场景 AOP可以进行动态植入,所以引入AOP,常用的AOP的场景如下:
【切面类一般不是核心业务逻辑】
  1. 日志记录:日志的打印
  2. 权限验证:操作权限,登录、角色等
  3. 事务处理:
  4. 效率检查:那个方法处理的最慢,大并发的情况下那个方法调用慢
  5. 异常处理:对异常信息的统一处理,或者使用监控实时处理
  6. 缓存处理:如GuavaCache或者是RedisCache等等
  7. 数据持久化:调用方法,先存储一份
  8. 内容分发:调用方法请求的时候,记录调用逻辑、方法、内容、次数等等
二、Aspect概念
Spring也参考了AspectJ的部分思想,AspectJ是java中的一种编程扩展框架,其内部使用的是 BCEL框架来完成其功能。 Android 使用 AOP 埋点需要引入 AspectJX(埋点上报等逻辑适合使用AOP的方式)
AspectJ是静态织入/静态代理(编译时),而AOP是动态织入/动态代理(运行时)。
1、AOP中主要概念
  • aspect:切面,切面由切点和通知组成,即包括横切逻辑的定义也包括链接点的定义
  • pointcut:切点,每个类拥有多个连接点,可以理解为连接点的集合
  • Joinpoint:连接点,程序执行的某个特定位置,如某个方法调用前后等(每个函数调用写日志的时候,调用方法就是连接点,多个方法的写日志,集合成为切点)
  • weaving:织入,将增强添加到目标类的具体连接点过程(过程描述)
  • advice:通知(增强),是织入到目标类的具体连接点的过程
  • target:目标对象,通知织入的目标类
  • aop Proxy:代理对象,即增强后产生的对象
【06_Springboot|Spring AOP 介绍与简单日志切面实现】Spring AOP 底层实现,是通过JDK动态代理或者CGLib代理在运行时期在对象初始化阶段织入代码的。
  • JDK动态代理结语接口实现。
  • CGLib是基于类的继承实现。
2、通知的类型(advice)
  • Before Advice:前置通知,在目标方法调用之前执行。无论方法是否遇见异常都执行。
  • After Returning Advice:后置通知,在方法返回后切入,抛出异常则不会切入。
  • After Throwing Advice:异常通知,在方法抛出异常时切入。
  • After Finally Advice:最终通知,在目标方法后执行,抛出异常则不会执行。
  • Around Advice:环绕通知,最强大的通知类型,可以控制目标方法的执行(通过调用ProceedingJoinPoint.proceed()),可以在目标执行全过程中进行执行。
3、@Aspect驱动 1、定义一个切面类Aspect
及在声明的类,增加@Component、@Aspect两个注解,SpringBoot中引入要加 spring-boot-aop依赖包。
2、定义切点Pointcut
定义其诶单,并定义切点在那些地方执行,采用@Pointcut注解完成,如
@Pointcut(value = "https://www.it610.com/article/execution( * com.spring.aop.controller.*.*(..))")

  • 包名、类名、方法名、参数名称
  • 规则:修饰符(可以不写,但是不能用*)+ 返回类型 + 那些包下面的类 + 那些方法 + 那些参数
  • “*”代表不限,“…”表示参数不限
    3、定义Advice通知
    利用通知的5中注解@Before、@After、@AfterReturning、@AfterThrowing、@Around来完成在某些切点的增强动作,如@Before(“myPointcut()”),myPointcut作为第二步骤的切点。
三、代码实现
以日志打印切面实现为例:包含测试用的Controller、切面类MyAdvice
1、定一个日志切面类 增加@Aspect、@Component表示这是一个切面类,组件;通知增加logger打印。
@Aspect @Component public class MyAdvice {private Logger logger = LoggerFactory.getLogger(MyAdvice.class); ... }

2、定义切点(Pointcut) 先定义一个切点,切点在执行过程中调用。
/** * 切点名称:myPointCut * 执行环境:执行过程中即:execution(表示 目标地址下所有类的所有方法,不限定参数) */ @Pointcut(value = "https://www.it610.com/article/execution( * com.spring.aop.controller.*.*(..))") public void myPointCut() { }

补充:切点类型
补充:切点的主要的种类:
方法执行:execution(MethodSignature)
方法调用:call(MethodSignature)
构造器执行:execution(ConstructorSignature)
构造器调用:call(ConstructorSignature)
类初始化:staticinitialization(TypeSignature)
属性读操作:get(FieldSignature)
属性写操作:set(FieldSignature)
例外处理执行:handler(TypeSignature)
对象初始化:initialization(ConstructorSignature)
对象预先初始化:preinitialization(ConstructorSignature)
Advice执行:adviceexecution()
基于控制流的pointcuts
主要包括两种类型的控制流:
cflow(Pointcut ),捕获所有的连接点在指定的方法执行中,包括执行方法本身。
cflowbelow(Pointcut ),捕获所有的连接点在指定的方法执行中,除了执行方法本身。
3、定义通知(Advice) 通知在调用前和调用后打印过程信息。
/** * 定制通知 * * @param proceedingJoinPoint 只能用在环绕通知中,其他4类只用JoinPoint *ProceedingJoinPoint暴露了运行时状态 * @return * @throws Throwable * @Around 表示环绕通知,从myPointCut切点进去,增强点 */ @Around("myPointCut()") public Object myLogger(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {String className = proceedingJoinPoint.getTarget().getClass().toString(); String methodName = proceedingJoinPoint.getSignature().getName(); Object[] param = proceedingJoinPoint.getArgs(); ObjectMapper mapper = new ObjectMapper(); logger.info("调用前:" + className + methodName + "传递的参数" + mapper.writeValueAsString(param)); // 环绕通知最重要:定义整个目标方法都要执行 Object object = proceedingJoinPoint.proceed(); logger.info("调用后:" + className + methodName + "返回值" + mapper.writeValueAsString(object)); return object; }

4、实现效果 在切面执行环境execution中的目标包中创建对应的Controller,并做简单的GET请求。
@RestController public class HelloController {@GetMapping("/hello") public String hello(@RequestParam String name) {return "hello " + name; }@GetMapping("/hello1") public String hello1(@RequestParam String name, @RequestParam String age) {return "hello " + name + " age = " + age; } }

1、单参数打印,即请求/hello
06_Springboot|Spring AOP 介绍与简单日志切面实现
文章图片
06_Springboot|Spring AOP 介绍与简单日志切面实现
文章图片

2、多参数打印,即请求/hello1
06_Springboot|Spring AOP 介绍与简单日志切面实现
文章图片

06_Springboot|Spring AOP 介绍与简单日志切面实现
文章图片

实现针对不同参数个数的执行过程中的日志打印,实现一个简单的日志切面。
附录
简单日志切面类文件
结构:
06_Springboot|Spring AOP 介绍与简单日志切面实现
文章图片

完整切面代码:
@Aspect @Component public class MyAdvice {private Logger logger = LoggerFactory.getLogger(MyAdvice.class); /** * 切点名称:myPointCut * 执行环境:执行过程中即:execution(表示 目标地址下所有类的所有方法,不限定参数) */ @Pointcut(value = "https://www.it610.com/article/execution( * com.spring.aop.controller.*.*(..))") public void myPointCut() { }/** * 定制通知 * * @param proceedingJoinPoint 只能用在环绕通知中,其他4类只用JoinPoint *ProceedingJoinPoint暴露了运行时状态 * @return * @throws Throwable * @Around 表示环绕通知,从myPointCut切点进去,增强点 */ @Around("myPointCut()") public Object myLogger(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {String className = proceedingJoinPoint.getTarget().getClass().toString(); String methodName = proceedingJoinPoint.getSignature().getName(); Object[] param = proceedingJoinPoint.getArgs(); ObjectMapper mapper = new ObjectMapper(); logger.info("调用前:" + className + methodName + "传递的参数" + mapper.writeValueAsString(param)); // 环绕通知最重要:定义整个目标方法都要执行 Object object = proceedingJoinPoint.proceed(); logger.info("调用后:" + className + methodName + "返回值" + mapper.writeValueAsString(object)); return object; }}

参考:https://www.bilibili.com/video/BV1yK411M7hb?p=2

    推荐阅读