AOP-面向切面编程

什么是AOP

AOP(Aspect Oriented Programming) 意为:面向切面编程,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。

AOP的编程思想就是把很多类对象中的横切问题点,从业务逻辑中分离出来,从而达到解耦的目的,增加代码的重用性,提高开发效率。

OOP的问题

假设现在有ABC三个类,要在这三个类中打印日志的话,通过OOP的思想,我们可以把这个打印日志的代码封装成一个公共类中的函数来给不同的类进行调用。但这样会产生以下问题:

  • 很多类对象和Logger仍然存在依赖关系
  • 甚至每个类中的所有方法都需要调用Logger
  • 如果修改了方法,需要修改N遍,不能一次修改完成改变

如果遇到类似场景,则需要使用AOP来进行解决

AOP的核心原理

Spring AOP就是基于动态代理实现的,通过JDK动态代理或CGLib代理在运行时期对象初始化阶段织入代码的。如果要代理的对象实现了某个接口,那么AOP会使用JDK Proxy去创建代理对象,而对于没有实现接口的对象,AOP会使用CGLib生成一个被代理对象的子类来作为代理。

AOP的应用场景

AOP常用的应用场景如下:

  • 日志记录
  • 权限验证
  • 事务处理
  • 效率检查
  • 异常处理
  • 缓存处理
  • 数据持久化
  • 内容分发

Aspect概念

Spring AOP中主要概念理解

  • aspect:切面,切面由切点和通知组成,即包括横切逻辑的定义也包括连接点的定义
  • pointcut:切点,每个类都拥有多个连接点,可以理解是连接点的集合
  • joinpoint:连接点,程序执行的某个特定位置,如某个方法调用前后等
  • weaving:织入,将增强添加到目标类的具体连接点的过程
  • advice:通知,是织入到目标类连接点上的一段代码,就是增强到的位置与内容
  • target:目标对象,通知织入的目标类
  • aop Proxy:代理对象,即增强后产生的对象

Spring AOP底层实现,使通过JDK动态代理或CGLib代理在运行时期在对象初始化阶段织入代码的

两者区别:

  • JDK动态代理是基于接口实现
  • CGLib是基于类的继承实现

通知的种类

  • Before advice:前置通知,即在目标方法调用之前执行。注意:即无论方法是否遇到异常都执行
  • After returning advice:后置通知,在目标方法执行后执行,前提是目标方法没有遇到异常,如果有异常则不执行通知
  • After throwing advice:异常通知,在目标方法抛出异常时执行,可以获取异常信息
  • After finally advice:最终通知,在目标方法执行后执行,无论是否是异常执行
  • Around advice:环绕通知,最强大的通知类型,可以控制目标方法的执行(通过调用ProceedingJoinPoint.proceed()),可以在目标执行全过程中进行执行

实现步骤

  1. 定义一个切面类Aspect

    即在声明的类,增加@Component @Aspect两个注解,Springboot中要引入spring-boot-starter-aop依赖包

  2. 定义切点Pointcut

    定义切点,并定义切点在哪些地方执行,采用@Pointcut注解完成,如

    @Pointcut(public * com.xxx.xxx.*.*(...))

    规则:修饰符(可以不写,但不能用*) + 返回类型 + 哪些包下的类 + 哪些方法 + 方法参数

    "*"代表不限

    ".."代表参数不限

  3. 定义Advice通知

    利用通知的5种类型注解@Before,@After,@AfterReturning,@AfterThrowing,@Around来完成在某些切点的增强动作,如@Before("myPointcut()"),myPointcut为第二步骤定义的切点

DEMO演示

通过新建项目来演示aop的实际使用流程

image-20221113145448681

依赖勾选Spring Web即可

要使用AOP,还需要在pom.xml中添加spring-boot-starter-aop依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.7.5</version>
</dependency>

准备就绪后,就可以开始进行AOP的实操,先创建一个控制器类,随便写上一个接口

package com.demo.springaop.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AOPController {

    @GetMapping("/hello")
    public String hello(@RequestParam("name") String name){
        return "hello," + name;
    }

}

然后就可以创建一个切面类来进行增强

package com.demo.springaop;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAdvice {
    private Logger logger = LoggerFactory.getLogger(MyAdvice.class);

    // 定义切面
    @Pointcut(value = "execution(* com.demo.springaop.controller.*.*(..))")
    public void myPointcut(){}

    // 环绕通知
    @Around("myPointcut()")
    public Object myLogger(ProceedingJoinPoint pjp) throws Throwable{
        String className = pjp.getTarget().getClass().toString(); // 获取类名
        String methodName = pjp.getSignature().getName(); // 获取方法名
        Object[] array = pjp.getArgs(); // 获取参数

        ObjectMapper mapper = new ObjectMapper();

        logger.info("调用前:" + className + methodName + "传递的参数为:" + mapper.writeValueAsString(array));

        Object obj = pjp.proceed(); // 调用整条函数进行执行

        logger.info("调用后:" + className + methodName + "传递的参数为:" + mapper.writeValueAsString(array));

        return obj; // 打印目标函数返回值
    }
}

尝试调用,可以正常输出日志

image-20221113155556184

最后修改:2022 年 11 月 13 日
如果觉得我的文章对你有用,能不能v我50参加疯狂星期四