一、概述
AOP:
⾯向切⾯编程,让程序员关注于类中方法的某个切点上
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
默认情况下,Spring 会根据被代理对象是否实现接⼝来选择使⽤ JDK 还是 CGLIB。当被代理对象没有实现 任何接⼝时,Spring 会选择 CGLIB。当被代理对象实现了接⼝,Spring 会选择 JDK 官⽅的代理技术,不过 我们可以通过配置的⽅式,让 Spring 强制使⽤ CGLIB。
- 通知(Advice):通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题
- 前置通知(@Before):在目标方法被调用之前调用通知功能
- 后置通知(@After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
- 返回通知(@AfterReturning):在目标方法成功执行之后调用通知
- 异常通知(@AfterThrowing):在目标方法抛出异常后调用通知
- 环绕通知(@Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
 
- 连接点(Joinpoint):要增强代码的地方(方法开始、结束、异常时等时机点)。只是候选点,最终是否增强代码得看后续逻辑
- 切点(Pointcut):已经把增强代码加入到业务主线之后的连接点
- 引入:引入允许我们向现有的类添加新方法或属性
- 织入(Weaving):把增强应用到目标对象老创建新的代理对象的过程。Spring 采用动态代理织入,而 Aspect 采用编译期织入和类装载期织入
- 切面(Aspect):切面 = 通知 + 切点。通知和切点共同定义了切面的全部内容 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | <dependency><groupId>org.springframework</groupId>
 <artifactId>spring-aop</artifactId>
 <version>5.1.12.RELEASE</version>
 </dependency>
 <dependency>
 <groupId>org.aspectj</groupId>
 <artifactId>aspectjweaver</artifactId>
 <version>1.8.13</version>
 </dependency>
 
 | 
二、切点
Spring 借助 AspectJ 的切点表达式语言来定义 Spring 切面
| AspectJ 指示器 | 描述 | 
| arg() | 限制连接点匹配参数为指定类型的执行方法 | 
| @args() | 限制连接点匹配参数由指定注解标注的执行方法 | 
| execution() | 用于匹配是连接点的执行方法 | 
| this() | 限制连接点匹配 AOP 代理的 bean 引用为指定类型的类 | 
| target | 限制连接点匹配目标对象为指定类型的类 | 
| @target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解 | 
| within() | 限制连接点匹配指定的类型 | 
| @within() | 限制连接点匹配指定注解锁标注的类型(当使用 Spring AOP 时,方法定义在由指定的注解所标注的类里) | 
| @annotation | 限定匹配带有指定注解的连接点 | 
![image.png]()
1. 限定 Bean 的 ID
| 1
 | execution(* concert.Performance.perform()) and bean('woodstock')
 | 
2. 通过注解定义
| 12
 3
 4
 5
 6
 7
 
 | @Pointcut("execution(** concert.Performance.perform(..))")public void performance() {}
 
 @Before("performance()")
 public void silenceCellPhones() {
 System.out.println("Silencing cell phones");
 }
 
 | 
3. 使用参数化通知
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | 
 @Pointcut("execution(* com.san.spring.aop.Player.play(int)) && args(trackNumber)")
 public void pointcut(int trackNumber) {}
 
 @Around("pointcut(trackNumber)")
 public void countTrack(ProceedingJoinPoint pjp, int trackNumber) {
 try {
 pjp.proceed();
 int currentCount = getTrackCurrentCount(trackNumber);
 map.put(trackNumber, ++currentCount);
 } catch (Throwable e) {
 ...
 }
 }
 
 | 
三、创建切面
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | @Aspect@Component
 public class Audience {
 @Before("execution(** cocert.Performance.perform(..))")
 public void silenceCellPhones() {
 System.out.println("Silencing cell phones");
 }
 
 @Before("execution(** concert.Performance.perform(..))")
 public void takeSeats() {
 System.out.println("Taking seats");
 }
 
 @AfterReturning("execution(** concert.Performance.perform(..))")
 public void applause() {
 System.out.println("CLAP CLAP CLAP!!!");
 }
 
 @AfterThrowing("execution(** concert.Performance.perform(..))")
 public void demandRefund() {
 System.out.println("Demanding a refund");
 }
 
 @Around("performance()")
 public void watchPerformance(ProceedingJoinPoint jp) {
 try {
 System.out.println("Silencing cell phones");
 System.out.println("Taking seats");
 
 jp.proceed();
 System.out.println("CLAP CLAP CLAP!!!");
 } catch (Throwable e) {
 System.out.println("Demanding a refund");
 }
 }
 }
 
 | 
1. 使用自动代理
JavaConfig:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | @Configuration@EnableAspectJAutoProxy
 @ComponentScan
 public class ConcertConfig {
 @Bean
 public Audience audience() {
 return new Audience();
 }
 }
 
 | 
XML:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/aop"
 xsi:schemaLocation="http://www.springframework.org/schema/aop
 http://www.springframework.org/schema/aop/spring-aop.xsd
 http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context.xsd">
 
 <context:component-scan base-package="concert" />
 <aop:aspectj-autoproxy />
 <bean class="concert.Audience" />
 </beans>
 
 | 
2. 在 XML 中声名切面
具体方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
 | public class Audience {    public void silenceCellPhones() {
 System.out.println("Silencing cell phones");
 }
 
 public void takeSeats() {
 System.out.println("Taking seats");
 }
 
 public void applause() {
 System.out.println("CLAP CLAP CLAP!!!");
 }
 
 public void demandRefund() {
 System.out.println("Demanding a refund");
 }
 
 public void watchPerformance(ProceedingJoinPoint jp) {
 try {
 System.out.println("Silencing cell phones");
 System.out.println("Taking seats");
 
 jp.proceed();
 System.out.println("CLAP CLAP CLAP!!!");
 } catch (Throwable e) {
 System.out.println("Demanding a refund");
 }
 }
 }
 
 | 
XML 配置:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | <aop:config><aop:aspect ref="audience">
 
 <aop:pointcut id="performance" expression="execution(** concert.Performance.perform(..))"/>
 <aop:before pointcut-ref="performance" method="silenceCellPhones"/>
 <aop:before pointcut-ref="performance" method="takeSeats"/>
 <aop:after-returning pointcut="execution(** concert.Performance.perform(..))" method="applause"/>
 <aop:after-throwing pointcut="execution(** concert.Performance.perform(..))" method="demandRefund"/>
 <aop:around pointcut-ref="performance" method="watchPerformance"/>
 </aop:aspect>
 </aop:config>
 
 | 
2.1. 为通知传递参数 —— XML
具体方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | public class TrackCounter {    private Map<Integer, Integer> trackCounts = new HashMap<>();
 public void countTrack(int trackNumber) {
 int currentCount = getPlayCount(trackNumber);
 trackCounts.put(trackNumber, currentCount + 1);
 }
 public int getPlayCount(int trackNumber) {
 return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
 }
 }
 
 | 
XML 配置:
| 12
 3
 4
 5
 6
 7
 8
 
 | <aop:config><aop:aspect ref="trackCounter">
 
 <aop:pointcut id="trackPlayed" expression="execution(* soundsystem.CompactDisc.playTrack(int))
 and args(trackNumber)"/>
 <aop:before pointcut-ref="trackPlayed" method="countTrack"/>
 </aop:aspect>
 </aop:config>
 
 | 
2.2. 通过切面引入新的功能 —— XML
- type-matching: 指定目标接口
-  implement-interface: 指定要增加的方法所在的接口
-  default-impl: 指定要增加的方法的实现,也可以由 delegate-ref=”beanID” 来代替从而导入 Spring 容器中的 Bean
| 12
 3
 4
 5
 6
 
 | <aop:aspect><aop:declare-parents
 types-matching="concert.Performance+"
 implement-interface="concert.Encoreable"
 default-impl="concert.DefaultEncoreable"/>
 </aop:aspect>
 
 | 
四、通过注解引入新功能
![]()
通过建立一个代理类同时代理 A 和 B,调用者调用该代理时,就可以同时 A 和 B 中的方法了。
目标类:
| 12
 3
 4
 5
 6
 7
 
 | @Componentpublic class Women implements Person {
 @Override
 public void likePerson() {
 System.out.println("我是女生,我喜欢帅哥");
 }
 }
 
 | 
被引入的类:
| 12
 3
 4
 5
 6
 7
 
 | @Componentpublic class FemaleAnimal implements Animal {
 @Override
 public void eat() {
 System.out.println("我是雌性,我比雄性更喜欢吃零食");
 }
 }
 
 | 
切面:
| 12
 3
 4
 5
 6
 7
 
 | @Aspect@Component
 public class AspectConfig {
 
 @DeclareParents(value = "com.ccomma.annotation.Person+", defaultImpl = FemaleAnimal.class)
 public Animal animal;
 }
 
 | 
五、源码
- 在 bean 的后初始化方法(后置处理器)applyBeanPostProcessorsAfterInitialization 中执行了 AbstractAutoProxyCreator#postProcessAfterInitialization 方法
- 内部执行了 AbstractAutoProxyCreator#wrapIfNecessary 方法
-  getAdvicesAndAdvisorsForBean:查找出和当前 bean 匹配的 advisor
- AbstractAutoProxyCreator#createProxy:把委托对象的 aop 增强和通用拦截进行合并,最终给代理对象,内部通过代理工厂 ProxyFactory#getProxy 创建代理
- 代理工厂内部最终调用 DefaultAopProxyFactory#createAopProxy 来依据情况创建 jdk 代理或 cglib 代理