最近看了點Spring的源碼,於是來稍微扯一扯,希望能幫一部分培訓班出身的朋友撕開一道口子,透透氣。

廣義上的Spring指的是Spring整個項目,包含SpringBoot、SpringCloud、SpringFramework、SpringData等等,

本系列文章只討論狹義上的Spring,也就是SpringFrameWork。

主要內容:

  • 盲點
  • Spring說,萬物皆可定義
  • 默默付出的後置處理器
  • 利用後置處理器返回代理對象

盲點

如果你恰好非科班轉行且從未獨立看過源碼,那麼你很可能至今都不曾注意某兩個概念。

你以為我會說IOC和AOP?NO。

看到這裡,一部分讀者心裡一驚:臥槽,說的啥玩意,Spring不就IOC和AOP嗎?!這兩個都不說,你這篇文章為啥能寫這麼長?

不錯,我就是這麼長。其實我要講的是:

  • BeanDefinition
  • BeanPostProcessor

大部分人一聽到「請你談談對Spring的理解」,就會下意識地搬出IOC和AOP兩座大山,趕緊糊弄過去。大概是這樣的:

IOC

所謂的控制反轉。通俗地講,就是把原本需要程序員自己創建和維護的一大堆bean統統交由Spring管理。

也就是說,Spring將我們從盤根錯節的依賴關係中解放了。當前對象如果需要依賴另一個對象,只要打一個@Autowired註解,Spring就會自動幫你安裝上。

AOP

所謂的面向切面編程。通俗地講,它一般被用來解決一些系統交叉業務的織入,比如日誌啦、事務啥的。打個比方,UserService的method1可能要列印日誌,BrandService的method2可能也需要。亦即:一個交叉業務就是要切入系統的一個方面。具體用代碼展示就是:

AOP圖一:這個切面,可以是日誌,也可以是事務

交叉業務的編程問題即為面向切面編程。AOP的目標就是使交叉業務模塊化。做法是將切面代碼移動到原始方法的周圍:

AOP圖二

原先不用AOP時(圖一),交叉業務的代碼直接硬編碼在方法內部的前後,而AOP則是把交叉業務寫在方法調用前後。那麼,為什麼AOP不把代碼也寫在方法內部的前後呢?兩點原因:

  • 首先,這與AOP的底層實現方式有關:動態代理其實就是代理對象調用目標對象的同名方法,並在調用前後加增強代碼。
InvocationHandler介於代理對象和目標對象中間,作用有兩個:1.銜接調用鏈 2.存放增強代碼
  • 其次,這兩種最終運行效果是一樣的,所以沒什麼好糾結的。

而所謂的模塊化,我個人的理解是將切面代碼做成一個可管理的狀態。比如日誌列印,不再是直接硬編碼在方法中的零散語句,而是做成一個切面類,通過通知方法去執行切面代碼。

我相信大部分培訓班出來的朋友也就言盡於此,講完上面內容就準備打卡下班了。

怎麼說呢,IOC按上面的解釋,雖然很淺,但也馬馬虎虎吧。然而AOP,很多人對它的認識是非常片面的...

這樣吧,我問你一個問題,現在我自己寫了一個UserController,以及UserServiceImpl implements UserService,並且在UserController中注入Service層對象:

@Autowired
private UserService userService;

那麼,這個userService一定是我們寫的UserServiceImpl的實例嗎?

如果你聽不懂我要問什麼,說明你對Spring的AOP理解還是太少了。

實際上,Spring依賴注入的對象並不一定是我們自己寫的類的實例,也可能是userServiceImpl的代理對象。下面分別演示這兩種情況:

  • 注入userServiceImpl對象
注入的是UserServiceImpl類型
  • 注入userServiceImpl的代理對象(CGLib動態代理)
注入的是CGLib動態代理生成的userServiceImpl的代理對象

為什麼兩次注入的對象不同?

因為第二次我給UserServiceImpl加了@Transactional 註解。

此時Spring讀取到這個註解,便知道我們要使用事務。而我們編寫的UserService類中並沒有包含任何事務相關的代碼。如果給你,你會怎麼做?動態代理嘛!上面說了,InvocationHandler作用有兩個:1.銜接調用鏈, 2.存放增強代碼。用動態代理在InvocationHandler的invoke()中開啟關閉事務即可完成事務控制。

看到這裡,我彷彿聽到有一部分兄弟默默說了句:臥槽,原來是這樣...

但是,上面對IOC和AOP的理解,也僅僅是應用級別,是一個面。僅僅瞭解到這個程度,對Spring的瞭解還是非常扁平的,不夠立體。


Spring說,萬物皆可定義

上帝說,要有光。於是特斯拉搞出了交流電。

Java說,萬物皆對象。但是Spring另外搞了BeanDefinition...

什麼BeanDefinition呢?其實它是bean定義的一個頂級介面:

正如BeanDefinition介面的注釋所言:一個BeanDefinition是用來描述一個bean實例的

哎呀臥槽,啥玩意啊。描述一個bean實例?我咋想起了Class類呢。

其實,兩者並沒有矛盾。

BeanDefinition的實現類很多,這裡僅以AbstractBeanDefinition為例,它實現了BeanDefinition

Class只是描述了一個類有哪些欄位、方法,但是無法描述如何實例化這個bean!如果說,Class類描述了一塊豬肉,那麼BeanDefinition就是描述如何做紅燒肉:

  • 單例嗎?
  • 是否需要延遲載入?
  • 需要調用哪個初始化方法/銷毀方法?

在容器內部,這些bean定義被表示為BeanDefinition對象,包含以下元數據:

1.包限定的類名:通常,定義bean的實際實現類。2.Bean行為配置:它聲明Bean在容器中的行為(範圍、生命週期回調,等等)。3.Bean依賴:對其他Bean的引用。4.對當前Bean的一些設置:例如,池的大小限制或在管理連接池的bean中使用的連接數。——Spring官方文檔

大部分初學者以為Spring解析<bean/>或者@Bean後,就直接搞了一個bean存到一個大Map中,其實並不是。

  • Spring首先會掃描解析指定位置的所有的類得到Resources(可以理解為.Class文件)
  • 然後依照TypeFilter和@Conditional註解決定是否將這個類解析為BeanDefinition
  • 稍後再把一個個BeanDefinition取出實例化成Bean

就好比什麼呢?你從海里吊了一條魚,但是你還沒想好清蒸還是紅燒,那就乾脆先曬成魚乾吧。一條鹹魚,其實蘊藏著無限可能,因為它可能會翻身!


默默付出的後置處理器

接下來,我們討論一下鹹魚如何翻身。

最典型的例子就是AOP。上面AOP的例子中我說過了,如果不加@Transactional,那麼Controller層注入的就是普通的userServiceImpl,而加了註解以後返回的實際是代理對象。

為什麼Spring要返回代理對象?因為我們壓根就沒在UserServiceImpl中寫任何commit或者rollback等事務相關的代碼,但是此時此刻代理對象卻能完成事務操作。毫無疑問,這個代理對象已經被Spring加了佐料(事務增強代碼)。

那麼Spring是何時何地加佐料的呢?說來話長,我們先繞個彎子。

大部分人把Spring比作容器,其實潛意識裡是將Spring完全等同於一個Map了。其實,真正存單例對象的Map,只是Spring中很小很小的一部分,僅僅是BeanFactory子類的一個欄位,我更習慣稱它為「單例池」。

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

這裡的ApplicationContext和BeanFactory是介面,實際上都有各自的子類。比如註解驅動開發時,Spring中最關鍵的就是AnnotationConfigApplicationContext和DefaultListableBeanFactory。

所以,很多人把Spring理解成一個大Map,還是太膚淺了。就拿ApplicationContext來講,它也實現了BeanFactory介面,說明它其實也是一個容器。但是同為容器,與BeanFactory不同的是,ApplicationContext主要用來包含各種各樣的組件,而不是存bean:

ApplicationContext的部分組件示意圖(包括Bean工廠)

那麼,Spring是如何給鹹魚加佐料(事務代碼的織入)的呢?關鍵就在於後置處理器。

後置處理器其實可以分好多種,屬於Spring的擴展點之一:

引用:https://www.zhihu.com/question/48427693/answer/691483076

前三個BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor、BeanPostProcessor都算是後置處理器,這裡篇幅有限,暫且先只介紹一下BeanPostProcessor。

BeanFactoryPostProcessor是處理BeanFactory的,所以存在ApplicationContext中。而BeanPostProcessor是處理Bean的,所以存在BeanFactory中,請務必注意!

BeanFactoryPostProcessor是用來幹預BeanFactory創建的,而BeanPostProcessor是用來幹預Bean的實例化。不知道大家有沒有試過在普通Bean中注入ApplicationContext實例?你第一時間想到的是:

@Autowired
ApplicationContext annotationConfigApplicationContext;

除了利用Spring本身的IOC容器自動注入以外,你還有別的辦法嗎?

我們可以讓Bean實現ApplicationContextAware介面:

實現ApplicationContextAware介面,並實現setApplicationContext()方法,用成員變數去接收形參applicationContext

後期,Spring會調用setApplicationContext()方法傳入ApplicationContext實例。

Spring官方文檔:一般來說,您應該避免使用它,因為它將代碼耦合到Spring中,並且不遵循控制反轉樣式。

這是我認為Spring最牛逼的地方:代碼具有高度的可擴展性,甚至你自己都懵逼,為什麼實現了一個介面,這個方法就被莫名其妙調用,還傳進了一個對象...

這其實就是後置處理器的工作!

什麼意思呢?

也就是說,雖然表面上在我們只要讓Bean實現一個介面就能完成ApplicationContext組件的注入,看起來很簡單,但是背地裡Spring做了很多事情。Spring會在框架的某一處搞個for循環,遍歷當前容器中所有的BeanPostProcessor,其中就包括一個叫ApplicationContextAwareProcessor的後置處理器,它的作用是:處理實現了ApplicationContextAware介面的Bean。

上面這句話有點繞,大家停下來多想幾遍。

Spring Bean的生命週期,創建過程必然經過BeanPostProcessor

要擴展的類(Bean)是不確定的,但是處理擴展類的流程(循環BeanPostProcessor)是寫死的。因為一個程序,再怎麼高度可擴展,總有一個要定下來吧。也就是說,在這個Bean實例化的某一緊要處,必然要經過很多BeanPostProcessor。但是,BeanPostProcessor也不是誰都處理,有時也會做判斷。比如:

if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}

所以,此時此刻一個類實現ApplicationContextAware介面,有兩層含義:

  • 作為後置處理器的判斷依據,只有你實現了該介面我才處理你
  • 提供被後置處理器調用的方法

利用後置處理器返回代理對象

大致瞭解Spring Bean的創建流程後,接下來我們嘗試著用BeanPostProcessor返回當前Bean的代理對象。

pom.xml

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
</dependencies>

AppConfig

@Configuration //JavaConfig方式,即當前配置類相當於一個applicationConotext.xml文件
@ComponentScan //默認掃描當前配置類(AppConfig)所在包及其子包
public class AppConfig {

}

Calculator

public interface Calculator {
public void add(int a, int b);
}

CalCulatorImpl

@Component
public class CalculatorImpl implements Calculator {
public void add(int a, int b) {
System.out.println(a+b);
}
}

後置處理器MyAspectJAutoProxyCreator

使用步驟:

  1. 實現BeanPostProcessor
  2. @Component加入Spring容器

@Component
public class MyAspectJAutoProxyCreator implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
final Object obj = bean;
//如果當前經過BeanPostProcessors的Bean是Calculator類型,我們就返回它的代理對象
if (bean instanceof Calculator) {
Object proxyObj = Proxy.newProxyInstance(
this.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("開始計算....");
Object result = method.invoke(obj, args);
System.out.println("結束計算...");
return result;
}
}
);
return proxyObj;
}
//否則返回本身
return obj;
}
}

測試類

public class TestPostProcessor {
public static void main(String[] args) {

System.out.println("容器啟動成功!");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
//列印當前容器所有BeanDefinition
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}

System.out.println("============");

//取出Calculator類型的實例,調用add方法
Calculator calculator = (Calculator) applicationContext.getBean(Calculator.class);
calculator.add(1, 2);
}

先把MyAspectJAutoProxyCreator的@Component注釋掉,此時Spring中沒有我們自定義的後置處理器,那麼返回的就是CalculatorImpl:

把@Component加上,此時MyAspectJAutoProxyCreator加入到Spring的BeanPostProcessors中,會攔截到CalculatorImpl,並返回代理對象:

代理對象的add()方法被增強:前後列印日誌

本文是Spring源碼系列的第一篇,僅僅是介紹了兩個重要概念:BeanDefinition和BeanPostProcessor。更詳細的內容, 比如Bean的生命週期流程及九大類後置處理器的介紹,以後有機會再慢慢更新。通過本文,大家只要有朦朧的Spring Bean生命週期的概念,以及知道BeanDefinition和BeanPostProcessor即可。

2019-06-25 12:58:54


推薦閱讀:
相關文章