AOP

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它通过将横切关注点(cross-cutting concern)从业务逻辑中分离出来,从而提高代码的可维护性和可重用性。横切关注点是指那些在多个模块或类中都需要关注的通用功能,例如日志记录、性能监控、安全检查、事务管理等。

AOP的核心概念

  • 连接点(Join Point):
    程序执行过程中的某个特定点,例如方法调用、异常抛出等。
  • 切点(Pointcut):
    定义了一组连接点,用于指定在哪些连接点上应用切面。
  • 通知(Advice):
    在切点处执行的代码块,用于实现横切关注点的逻辑。通知可以分为以下几种类型:
    • 前置通知(Before Advice):在连接点之前执行。
    • 后置通知(After Advice):在连接点之后执行。
    • 返回通知(After Returning Advice):在连接点正常返回后执行。
    • 异常通知(After Throwing Advice):在连接点抛出异常时执行。
    • 环绕通知(Around Advice):在连接点之前和之后都执行。
  • 切面(Aspect):
    将切点和通知组合在一起的模块,用于定义横切关注点的逻辑。
  • 织入(Weaving):
    将切面应用到目标对象的过程,可以发生在编译时、类加载时或运行时。

AOP的优势

  • 代码复用:
    将横切关注点集中在一个地方实现,避免重复代码。
  • 代码可维护性:
    修改横切关注点时,只需修改切面,无需修改多个模块。
  • 代码简洁性:
    业务逻辑代码更加专注于核心功能,不被横切关注点干扰。

SpringAOP

基于代理模式实现,适用于Spring框架中的应用。
使用SpringAOP,需要在pom.xml文件中导入依赖:

1
2
3
4
<dependency>
<groupld.org/springframework.boot〈/groupld>
<artifactld>spring-boot-starter-aop</artifactld>
</dependency>

编写AOP程序

在aop方法实现类前加上注解@Component和@Aspect
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用 @Aspect 注解声明这是一个切面
// 使用 @Component 注解将切面注册为 Spring 容器中的一个 Bean
@Aspect
@Component
public class LoggingAspect {
// 定义一个前置通知,在方法执行之前执行
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("Logging before method execution");
}

// 定义一个后置通知,在方法执行之后执行
@After("execution(* com.example.service.*.*(..))")
public void logAfter() {
System.out.println("Logging after method execution");
}
}

通知类型

  • @Around: 环绕通知。该注解标注的通知方法在目标方法执行前、后都被执行
  • @Before:前置通知。该注解标注的通知方法在目标方法执行之前被执行
  • @After:后置通知。该注解标注的通知方法在目标方法执行后被执行,且无论是否有异常都会执行
  • @AfterReturning:返回后通知。该注解标注的通知方法在目标方法执行后被执行,但是目标方法有异常时不会执行
  • @AfterThrowing:异常后通知。该注解标注的通知方法在发生异常后执行

通知顺序

  • 当多个切面类的切入点都匹配到了目标方法时,多个通知方法的执行顺序
    • 不同切面类中,默认按照切面类的类名字母顺序排序:
      • 目标方法执行前的通知方法:字母排名靠前的先执行
      • 目标方法执行后的通知方法:字母排名靠后的先执行
    • 在切面类上加注解@Order(数字)可以控制顺序
      • 目标方法执行前的通知方法:数字小的先执行
      • 目标方法执行后的通知方法:数字大的先执行

切入点表达式

  • 描述切入点方法的一种表达式,主要用来决定项目中哪些方法需要加入通知
    • 形式:
      • execution(……):根据方法的返回值、包名、类名、方法名、方法参数等来匹配
        • 语法:execution(【访问修饰符】 返回值 【包名.类名】.方法名(方法参数)【 throws 异常】)
        • 注:带【】的可以省略
        • 异常是方法上声明抛出的异常,不是实际抛出的异常
        • 通配符:
          • *:单个独立的任意符号,可以匹配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名字中的一部分
          • .. :多个连续的任意符号,可以通配任意层级的包、或任意类型、任意个数的参数
        • 可以用||、&&、!来组合不同的切入点表达式
      • @annotation(……):用于匹配标识有特定注解的方法
        • 用法:自定义一个注解,在定义时加上@Retention()指定注解生效时间,和@Target()指定注解作用范围;在要通知的方法前加上自定义的注解,然后在@PointCut()中使用注解@annotation(自定义的注解的全类名)

连接点

  • Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等
  • @Around通知,获取连接点信息只能用ProceedingJoinPoint
  • 其他四种通知类型,获取连接点信息只能用JoinPoint(ProceedingJoinPoint的父类型)

完整的SpringAOP实例

1、目标类

1
2
3
4
5
public class UserService {
public void addUser() {
System.out.println("Adding a new user");
}
}

2、切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Aspect //声明该类是一个切面类
@Component //将切面类注册为 Spring 容器中的一个 Bean
public class LoggingAspect {
// 定义切点,匹配 com.example.service 包中所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {

}

//定义通知
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("Logging before method execution");
}
}

3、配置类

1
2
3
4
5
6
@Configuration
@EnableAspectJAutoProxy //有了这个注解才能支持@Aspect等相关的一系列AOP注解的功能
@ComponentScan(basePackages = "com.example")
public class AppConfig {

}

4、测试类

1
2
3
4
5
6
7
public class TestAOP {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.addUser();
}
}