Spring AOP 分享 (初级篇+进阶篇+高级篇+扩展篇+思考篇)

本文深入探讨了Spring AOP的概念、应用场景、实现方式和最佳实践。介绍了AOP作为面向切面编程的原理,如何降低代码耦合度,以及在事务管理、日志记录等方面的应用。详细讲解了Spring AOP的动态代理机制,包括JDK和CGLIB的使用,以及AOP代理的创建时机和防止重复创建的策略。同时,还分析了AOP在Spring Boot项目中的实践建议和潜在问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

初级篇

AOP是什么?

Aspect-oriented Programming (AOP) 即面向切面编程。
简单来说,AOP 是一种编程范式,允许我们模块化地定义横跨多个对象的行为。AOP 可以帮助我们将应用程序的关注点分离,使得代码更加清晰、易于维护和扩展。

大白话:在方法执行前后运行指定代码,比如日志记录、事务开启/提交/回滚等。

为什么要AOP?

AOP可以帮助我们解决在代码中耦合度高的问题,让我们的代码更加模块化和易于维护。
具体来说,AOP可以通过在运行时动态地将通用功能(例如日志记录、性能分析、事务管理)应用于多个模块,而无需修改它们的代码。这样可以避免代码重复和嵌套,提高代码的复用性和可维护性。
另外,在复杂的业务场景中,多个模块可能需要共享某些共同的功能,而AOP可以让这些功能从模块中抽离出来,以便更好地进行组织和重用。
总的来说,AOP可以让我们更好地实现代码的分离和聚合,从而获得更高效、更可靠的代码。

大白话:增强原方法的功能,解耦通用功能,透明化静默操作。
举例 事务切面切面:
增强原方法的功能:原本方法使用的是数据库连接默认的策略自动提交事务的,有了切面能够保证方法内同一事务了;
解耦通用功能:但是很多方法都需要做事务的控制,有了切面不需要我们每一个方法都加几行相同的代码;
透明化静默操作:方法本身需要知道我怎么开的事务?需要知道我什么时候多打印了日志吗?

  • 伪代码:没有使用AOP前,每个方法都要CV一遍打印方法执行日志
@Override
public UserPO findByUsername(@AutoTrim String username) {
    log.info("execute findByUsername by username={}", username);
    Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("username"), username));
    Assert.isTrue(opt.isPresent(), "没有找到用户");
    UserPO userPO = opt.get();
    log.info("execute findByUsername by username={}; return {} ", username, userPO);
    return userPO;
}

@Override
public UserPO findByEmail(@AutoTrim String email) {
    log.info("execute findByEmail by email={}", email);
    Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("email"), email));
    Assert.isTrue(opt.isPresent(), "没有找到用户");
    UserPO userPO = opt.get();
    log.info("execute findByEmail by email={}; return {} ", email, userPO);
    return userPO;
}
  • 伪代码:使用AOP后,无需关心方法执行日志的打印
@Override
public UserPO findByUsername(@AutoTrim String username) {
    Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("username"), username));
    Assert.isTrue(opt.isPresent(), "没有找到用户");
    return opt.get();
}

@Override
public UserPO findByEmail(@AutoTrim String email) {
    Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("email"), email));
    Assert.isTrue(opt.isPresent(), "没有找到用户");
    return opt.get();
}

此处代码演示 【part1】
演示内容:

  • 展示未使用AOP前,使用UserServiceImpl注入Bean,测试接口查看日志打印;
  • 展示使用AOP后,使用UserServiceAopImpl注入Bean,测试接口查看日志打印;
  • 展示切面打印非项目内的类方法执行日志,切换PointcutlogPointcut2()
  • 展示灵活使用配置,控制切面开启日志,开启@ConditionalOnProperty,修改yml文件;例如:测试环境要开启日志,生产环境要关闭日志,通过配置灵活控制。

PS:我们在此暂不考虑基于XmlApplicationContext系列;

怎么实现AOP?

AOP的实现依赖于多态和动态代理。
为了更好的理解,我们可以先举例一个静态代理的类来分析。
此处代码演示 【part2】
演示内容:

  • 展示打印日志的静态代理,使用UserServiceStaticAopImpl注入Bean,测试接口查看日志打印;
  • 展示多种通知类型的静态代理,使用UserServiceStaticAopAnyImpl注入Bean,测试接口查看日志打印;

PS:我们在此暂不考虑基于XmlApplicationContext系列;

AOP的代码结构与核心概念

静态代理的AOP结构(简单易理解版):

Aspect

切面:指横跨多个类的一个关注点,它与不同类中相似的方法相对应。如保存数据时添加日志,可以新建一个切面配置日志记录逻辑。

大白话:一个模块化的切面程序,也可以理解为是一个实现切面功能的类;
用@Aspect定义的Bean Class,或者Spring xml配置里的aop:aspect标签:

<aop:config>
  <aop:aspect id="myAspect" ref="aBean">
    ...
  </aop:aspect>
</aop:config>

Join Point

连接点:指在应用程序执行过程中的某个特定位置,如方法调用或异常处理等。

可以理解为要切面的对象类型,比如要加切面的目标是构造器,或者一个方法,或者是一个属性的赋值;
AspectJ中可以有很多种,详见AspectJ Join Points
SpringAOP中只有一种,就是方法执行(Method execution);

Advice

通知:指在切面的某个连接点上执行的代码。通知有许多类型,包括“前置通知”、“后置通知”、“返回通知”、“异常通知”、“环绕通知”,其中“环绕通知”能够完全控制目标方法的执行。

Pointcut

切入点:指一个或多个连接点,通常定义在一个正则表达式中,描述哪些方法会被拦截。

参考:
Join Points and Pointcuts
Spring 之AOP AspectJ切入点语法详解
例:
within(com.supalle.springaop.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值