最簡 Spring AOP 原始碼分析!
阿新 • • 發佈:2020-12-10
![](https://img2020.cnblogs.com/other/633265/202012/633265-20201210143003352-2147163579.jpg)
# 前言
最近在研究 Spring 原始碼,Spring 最核心的功能就是 `IOC 容器`和 `AOP`。本文定位是以最簡的方式,分析 Spring AOP 原始碼。
# 基本概念
![](https://img2020.cnblogs.com/other/633265/202012/633265-20201210143003709-597108252.png)
上面的思維導圖能夠概括了 Spring AOP,其最重要的是 Spring AOP `只能作用於 Bean`,而 AspectJ 能夠在編譯期、類載入期對位元組碼進行更改。
# 猜測實現原理
Spring AOP 的實現原理是`動態代理`,但是具體又是怎麼實現的呢?
在 Spring 容器中,我們使用的每個 bean 都是 BeanDefinition 的例項,容器會在合適的時機根據 BeanDefinition 的基本資訊例項化 bean 物件。
所以比較簡單的做法是,Spring 會自動生成代理物件的代理類。我們在獲取 bean 時,Spring 容器返回代理類物件,而不是實際的 bean。
# 除錯程式碼
本文使用的程式碼,安裝了 `lombok`,並基於 `Spring Boot`,是一個完全基於註解的最簡除錯程式碼。
註解配置類 AopConfig:
```java
@Slf4j
@Component
@Aspect
public class AopConfig {
@Pointcut("within(com.life.demo..*)")
public void pointCut() {
}
@Before("com.life.demo.AopConfig.pointCut()")
public void log() {
log.info("this is point cut...");
}
}
```
Spring 啟動類 AppApplication:
```java
@SpringBootApplication
@EnableAspectJAutoProxy
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}
```
Controller HelloWorldController:
```java
package com.life.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
public class HelloWorldController {
@GetMapping("/hello")
public String greeting() {
return "hello!";
}
}
```
執行 Web 應用,在瀏覽器輸入網址 http://localhost:11111/hello,會看到 log:
```
INFO 96257 --- [io-11111-exec-1] com.life.demo.AopConfig : this is point cut...
```
驗證出成功配置了代理。
# 使用說明
1. @EnableAspectJAutoProxy 開啟 AOP。
2. 使用 @Aspect 註解的 bean 都會被 Spring 當做用來實現 AOP 的配置類。
3. 配置 Advice,不做詳細介紹,具體參考 [Spring AOP 官方文件](https://docs.spring.io/spring/docs/2.0.x/reference/aop.html)。
3. @Pointcut,用來匹配 Spring 容器中的所有 bean 的方法的。
```java
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature
```
@Pointcut 中使用了 execution 來正則匹配方法簽名,這也是最常用的,除了 execution,我們再看看其他的幾個比較常用的匹配方式:
- within:指定所在類或所在包下面的方法(Spring AOP 獨有)
`如 @Pointcut("within(com.javadoop.springaoplearning.service..*)")`
- @annotation:方法上具有特定的註解,如 @Subscribe 用於訂閱特定的事件。
`如 @Pointcut("execution(* .(..)) && @annotation(com.javadoop.annotation.Subscribe)")`
- bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 獨有)
`如 @Pointcut("bean(*Service)")`
Tips:上面匹配中,通常 "." 代表一個包名,".." 代表包及其子包,方法引數任意匹配使用兩個點 ".."。
# 原始碼深入分析
## @EnableAspectJAutoProxy 開啟 AOP
@EnableAspectJAutoProxy 註解定義:
```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
```
```java
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
```
在 AppApplication 啟動類上要加入 `@EnableAspectJAutoProxy` 註解開啟 AOP,檢視該註解原始碼,其 proxyTargetClass() 是在 AspectJAutoProxyRegistrar 類中呼叫,而 AspectJAutoProxyRegistrar 是一個 ImportBeanDefinitionRegistrar。再往上追根溯源,可以看到是在介面 ConfigurableApplicationContext 中 void refresh() 呼叫。
## IOC 容器管理 AOP 例項
在建立 bean 時,會呼叫 AbstractAutowireCapableBeanFactory#doCreateBean(...)。
```java
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// 初始化 bean
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 1. 建立例項
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
...
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 2. 裝載屬性
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
// 3. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
...
}
```
著重看第3步 initializeBean(...) 方法:
```java
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction