Spring AOP 的实现原理
一、AOP的基本概念
将横切关注点(日志、事务、权限)从业务逻辑中分离出来,提高代码的可维护性。
下面将解释,AOP专属名词,切面、连接点、切点、通知、目标对象、代理对象:
- 切面:切面是封装横切关注点的模块,比如日志记录。 @Aspect 修饰类,如 LoggingAspect
- 连接点:连接点就是作用的实际方法
- 切点:@Pointcut("execution(* org.example.tao.service.impl.UserServiceImpl.getAllUsers(..))")
- 通知:@Before @AfterReturning @AfterThrowing @After @Around
- 目标对象:原始业务对象
- 代理对象:Spring 启动时动态生成的代理对象
二、Spring AOP
2.1 实现原理
Spring AOP 是基于动态代理实现的,具体有俩种代理方式:
- JDK 动态代理(需要有接口)
- CGLIB 动态代理
2.2 优劣势
优点:
- 解耦:将横切关注点与业务逻辑分离,提高代码的可维护性。
- 灵活:通过切点表达式可以灵活地定义拦截规则。
- 非侵入式:无需修改目标类代码,即可实现功能增强。
缺点:
- 性能开销:动态代理会引入一定的性能开销。
- 局限性:只能拦截Spring管理的Bean,且无法拦截非public方法。
2.3 工作流程
- 定义切面:
使用@Aspect注解定义切面类。
在切面类中定义通知方法,并使用@Before、@After等注解指定通知类型。 - 定义切点:
使用@Pointcut注解定义切点表达式,指定哪些方法会被拦截。 - 生成代理对象:
Spring容器在初始化时,根据切面和切点生成代理对象。
如果目标类实现了接口,使用JDK动态代理;否则使用CGLIB动态代理。 - 执行通知:
当目标方法被调用时,代理对象会根据切点表达式判断是否需要拦截。
如果需要拦截,则按照通知类型(如前置通知、后置通知)执行通知逻辑。
三、展示一下
3.1 引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
3.2 定义切面类
@Aspect // 封装切面关注点的模块,比如日志记录
@Component
public class LoggingAspect {
// 切点,通过表达式匹配连接点 // todo: 实际替换为自己想要切面的方法。
@Pointcut("execution(* org.example.tao.service.impl.UserServiceImpl.getAllUsers(..))")
public void loggingPointCut() {}
// 前置通知,作用于连接点
@Before("loggingPointCut()")
public void logBefore() {
System.out.println("方法执行前的日志记录");
}
@AfterReturning(pointcut = "loggingPointCut()", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("方法返回后的日志记录,返回值:" + result);
}
@AfterThrowing(pointcut = "loggingPointCut()", throwing = "exception")
public void logAfterThrowing(Exception exception) {
System.out.println("方法抛出异常后的日志记录,异常:" + exception.getMessage());
}
@After("loggingPointCut()")
public void logAfter() {
System.out.println("方法执行后的日志记录");
}
@Around("loggingPointCut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Signature signature = joinPoint.getSignature();
System.out.println("环绕日志开始,方法名:" + signature.getName());
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 执行目标方法
long elapsedTime = System.currentTimeMillis() - start;
System.out.println("环绕日志结束,耗时:" + elapsedTime + "ms");
return result;
}
}
3.3 验证
启动程序后,调用连接点方法,便会执行方法。
本文来自博客园,作者:帅气的涛啊,转载请注明原文链接:https://www.cnblogs.com/handsometaoa/p/18782188