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依赖:
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方法
友情链接:
©Copyright © 2022 2006年世界杯歌曲_冰岛世界杯排名 - guoyunzhan.com All Rights Reserved.