一、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 动态代理
代理模式 https://www.cnblogs.com/handsometaoa/p/16107991.html
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 验证
启动程序后,调用连接点方法,便会执行方法。