Spring之AOP(超详解)

Spring之AOP(超详解)

AOP

什么是AOP

AOP(Aspect-Oriented Programming)即面向切面编程,是一种编程范式,它通过将横切关注点(Cross-cutting Concerns)从业务逻辑中分离出来,实现代码的模块化和松耦合,在spring中用于将那些与业务无关但对多个事务产生影响的公共行为或逻辑,抽取公共代码块复用,降低代码耦合程度。比如用于日志记录、事务管理、公共字段的填充等。

为什么要使用AOP

在传统OOP(面向对象编程)中,代码的核心逻辑(如业务逻辑)与横切关注点(如日志、事务、权限)会高度耦合。例如,每个业务方法都需要重复编写日志记录的代码,导致:

代码冗余:相同功能的代码分散各处。

维护困难:修改日志格式需改动所有相关方法。

核心逻辑不清晰:业务代码被非核心代码“污染”。

AOP的核心目标:将横切关注点与核心逻辑解耦,通过声明式的方式统一管理,提升代码复用性和可维护性。

AOP的核心概念

横切关注点:指贯穿于多个模块的功能,如日志记录、事务管理、权限验证、缓存、异常处理等。这些功能与核心业务逻辑无关,但会散布在代码的多个地方,导致代码冗余和维护困难。切面(Aspect):封装横切关注点的模块,将这些分散的功能集中管理。连接点(Join Point):程序执行过程中的特定点,如方法调用、异常抛出等。切点(Pointcut):一组连接点的集合,定义了切面在何处(When)、对哪些方法(Where)生效(如execution(* com.example.service.*.*(..))匹配某包下所有方法)。通知(Advice):切面在特定连接点执行的代码,分为5种类型:

@Before:方法执行前。

@AfterReturning:方法正常返回后。

@AfterThrowing:方法抛出异常后。

@After(Finally):方法结束后(无论成功或异常)。

@Around:包裹方法执行(最强大,可控制是否执行原方法)。

织入(Weaving):将切面与目标对象连接并创建代理对象的过程,可发生在编译时、类加载时或运行时。

AOP原理

AOP(面向切面编程)的核心原理是动态代理和字节码增强,通过在运行时或编译时将切面逻辑(如日志、事务)插入到目标方法中,实现横切关注点的分离。

在Spring中默认是使用动态代理的方式实现AOP。

Spring中AOP的使用

2.1 添加依赖

在Maven项目中添加Spring AOP依赖:

org.springframework.boot

spring-boot-starter-aop

2.2 定义切面(Aspect)

创建一个类并用@Aspect注解标记:

@Aspect

@Component

public class LoggingAspect {

// 定义通知和切点

}

2.3 定义通知(Advice)

Spring支持五种通知类型:

@Before:目标方法执行前执行。

@After:目标方法执行后执行(无论是否异常)。

@AfterReturning:目标方法正常返回后执行。

@AfterThrowing:目标方法抛出异常后执行。

@Around:包裹目标方法,可控制是否执行方法。

示例:记录方法执行时间

@Around("execution(* com.example.service.*.*(..))")

public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {

long startTime = System.currentTimeMillis();

Object result = joinPoint.proceed(); // 执行目标方法

long duration = System.currentTimeMillis() - startTime;

System.out.println(joinPoint.getSignature() + " executed in " + duration + "ms");

return result;

}

2.4 定义切点(Pointcut)

使用@Pointcut注解定义可重用的切点表达式:

@Pointcut("execution(* com.example.service.*.*(..))")

public void serviceMethods() {}

@Before("serviceMethods()")

public void beforeServiceMethod() {

System.out.println("Before service method execution");

}

2.5 启用AOP

在配置类中添加@EnableAspectJAutoProxy:

@Configuration

@EnableAspectJAutoProxy

public class AppConfig {}

3. 切点表达式语法

execution() 是 Spring AOP 中最常用的切入点表达式,用来匹配方法的执行。它可以精确地定义目标方法的签名,包括方法的访问修饰符、返回类型、类名、方法名和参数,常用语法:

3.1、execution() 表达式详解

语法结构

execution([修饰符] 返回类型 包.类.方法(参数))

修饰符:可选,如 public、protected、private,默认匹配所有访问权限。

返回类型:必须明确指定,可以是具体类型或通配符 *。

包.类.方法:类的全路径和方法名,支持通配符。

通配符规则

通配符

说明

*

匹配任意字符(包名、类名、方法名、参数类型)。

..

匹配任意数量的子包(包路径中)或任意数量的参数(参数列表中)。

+

匹配指定类型及其子类型(仅用于参数类型)。

常见用法示例

表达式示例

匹配目标

execution(* com.example.service.*.*(..))

com.example.service 包下所有类的 所有方法(不限参数和返回类型)。

execution(public * com.example.*.User.*(..))

com.example 包下所有子包中 User 类的所有 public 方法。

execution(* com.example.dao.*.*(String, *))

com.example.dao 包下所有类的 方法名任意,且第一个参数为 String,第二个参数任意。

execution(* save*(..))

所有类中 方法名以 save 开头 的方法。

execution(* com.example..*.*(..))

com.example 包及其所有子包下所有类的所有方法。

execution(* *(int, ..))

所有方法中 第一个参数为 int 类型,后续参数任意(0个或多个)。

3.2、@annotation(注解类)

基于注解的切点匹配方式,决定了匹配带有指定注解的方法。

语法结构

@annotation(注解类的全限定名)

匹配 带有指定注解的方法。

注解可以是自定义注解或 Spring 内置注解(如 @Transactional)。

自定义注解:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Loggable {}

切面配置:

@Aspect

@Component

public class LoggingAspect {

// 拦截所有被 @Loggable 注解标记的方法

@Around("@annotation(com.example.Loggable)")

public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {

System.out.println("Method executed: " + joinPoint.getSignature());

return joinPoint.proceed();

}

}

目标方法:

@Service

public class UserService {

@Loggable

public void saveUser() {

// 业务逻辑

}

}

3.3、 其他常用切点表达式

除了 execution() 和 @annotation(),Spring AOP 还支持以下表达式:

表达式

说明

within(包.类)

匹配指定包或类下的所有方法(粗粒度)。

this(接口)

匹配代理对象实现了指定接口的 Bean。

target(接口)

匹配目标对象实现了指定接口的 Bean。

args(参数类型)

匹配方法参数为指定类型的方法。

@within(注解类)

匹配类上带有指定注解的所有方法。

@target(注解类)

匹配目标对象类上带有指定注解的所有方法。

@args(注解类)

匹配方法参数类型上带有指定注解的方法。

4. 组合切点表达式

使用逻辑运算符 &&(与)、||(或)、!(非)组合多个表达式。

示例

// 匹配 service 包下所有类的方法,且方法被 @Transactional 注解标记

@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(org.springframework.transaction.annotation.Transactional)")

public void transactionalServiceMethods() {}

4. 注意事项

代理机制:Spring AOP默认使用JDK动态代理(接口代理),若无接口则使用CGLIB。

自调用问题:同一类内部方法调用不会触发AOP通知。

性能:AOP会引入额外开销,但通常可忽略。

限制:只能拦截Spring管理的Bean,且不支持静态方法。

那既然动态代理存在基于JDK和CGLIB两种方式,那什么时候使用CGLIB什么使用JDK?他们两者有什么区别呢?

动态代理之CGLIB与JDK

1. 实现原理

JDK动态代理

CGLIB代理

基于接口的代理,要求目标类必须实现至少一个接口。

基于继承的代理,通过生成目标类的子类来实现代理。

使用java.lang.reflect.Proxy类动态生成代理对象。

使用ASM字节码框架直接修改目标类的字节码生成代理类。

2. 性能对比

JDK动态代理

CGLIB代理

早期版本(JDK 8之前)反射调用较慢,但JDK 8+通过优化反射性能提升显著。

生成的代理类直接调用目标方法(无需反射),早期版本性能更高。

在简单场景下性能与CGLIB接近。

生成代理类的过程较慢(需操作字节码),但代理对象方法调用更快。

3. 使用限制

JDK动态代理

CGLIB代理

目标类必须实现接口。

目标类和方法不能是final(否则无法生成子类)。

无法代理无接口的类。

无法代理private、static或final方法。

4. Spring中的默认行为

Spring AOP默认策略:

如果目标类实现了接口 → 使用JDK动态代理。

如果目标类无接口 → 使用CGLIB代理。

强制使用CGLIB:

@Configuration

@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用CGLIB

public class AppConfig {}

5. 对比总结

特性

JDK动态代理

CGLIB代理

实现方式

接口代理

继承代理

依赖

需要接口

无接口要求,但类不能为final

方法调用

反射调用

直接调用(通过子类重写)

性能

JDK 8+优化后接近CGLIB

生成代理类较慢,但调用更快

兼容性

仅支持接口方法

支持代理类的所有非final方法

相关推荐

ae怎么导出图片?
365速发平台app下载

ae怎么导出图片?

📅 08-01 👁️ 1811
[活动]天下贰(刺客联盟)
365速发平台app下载

[活动]天下贰(刺客联盟)

📅 08-28 👁️ 7434
《返校》全剧情流程图文攻略(完结)
世界杯365软件

《返校》全剧情流程图文攻略(完结)

📅 08-29 👁️ 7915