初级篇
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,测试接口查看日志打印; - 展示切面打印非项目内的类方法执行日志,切换
Pointcut
为logPointcut2()
; - 展示灵活使用配置,控制切面开启日志,开启
@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.