一、概述
AOP:
⾯向切⾯编程,让程序员关注于类中方法的某个切点上
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
默认情况下,Spring 会根据被代理对象是否实现接⼝来选择使⽤ JDK 还是 CGLIB。当被代理对象没有实现 任何接⼝时,Spring 会选择 CGLIB。当被代理对象实现了接⼝,Spring 会选择 JDK 官⽅的代理技术,不过 我们可以通过配置的⽅式,让 Spring 强制使⽤ CGLIB。
- 通知(Advice):通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题
- 前置通知(@Before):在目标方法被调用之前调用通知功能
- 后置通知(@After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
- 返回通知(@AfterReturning):在目标方法成功执行之后调用通知
- 异常通知(@AfterThrowing):在目标方法抛出异常后调用通知
- 环绕通知(@Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
- 连接点(Joinpoint):要增强代码的地方(方法开始、结束、异常时等时机点)。只是候选点,最终是否增强代码得看后续逻辑
- 切点(Pointcut):已经把增强代码加入到业务主线之后的连接点
- 引入:引入允许我们向现有的类添加新方法或属性
- 织入(Weaving):把增强应用到目标对象老创建新的代理对象的过程。Spring 采用动态代理织入,而 Aspect 采用编译期织入和类装载期织入
- 切面(Aspect):切面 = 通知 + 切点。通知和切点共同定义了切面的全部内容
1 2 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. 通过注解定义
1 2 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. 使用参数化通知
1 2 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) { ... } }
|
三、创建切面
1 2 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:
1 2 3 4 5 6 7 8 9
| @Configuration @EnableAspectJAutoProxy @ComponentScan public class ConcertConfig { @Bean public Audience audience() { return new Audience(); } }
|
XML:
1 2 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 中声名切面
具体方法:
1 2 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 配置:
1 2 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
具体方法:
1 2 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 配置:
1 2 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
1 2 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 中的方法了。
目标类:
1 2 3 4 5 6 7
| @Component public class Women implements Person { @Override public void likePerson() { System.out.println("我是女生,我喜欢帅哥"); } }
|
被引入的类:
1 2 3 4 5 6 7
| @Component public class FemaleAnimal implements Animal { @Override public void eat() { System.out.println("我是雌性,我比雄性更喜欢吃零食"); } }
|
切面:
1 2 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 代理