Spring原始碼分析——spring容器總覽

蘇凌峰發表於2021-09-12

1.spring簡介

2.spring容器流程梳理

3.總結

1.spring簡介
spring我相信大家都不陌生,只要你是一位JAVA開發人員,肯定會使用它,但是我們日常的使用中,很多時候只需要寫業務邏輯,使用別人已經封裝好的技術,對其原理卻一無所知。在此係列的部落格中,筆者希望儘可能地總結spring原始碼,讓我們不再只會使用它,讓讀者懂其原理。
所謂:有道無術,術尚可求,有術無道,止於術
框架的使用可能就是‘術’,而框架的底層實現原理,就是‘道’。有了‘道’之後,‘術’是可以根據自己的需要造出來的。就像知道了數學公式是如何推導的,自然不用記住公式,還能對公式進行魔改,甚至發明公式。但是如果只有‘術’,就像只知道了公式的形狀,並不知道其來源,稍微變一下我們就認不出來了。
無論是學什麼東西,筆者認為,積累‘道’是非常重要的,且不可急功近利,否則就是欲速則不達。

2.spring容器流程梳理

我們在使用spring的時候,使用最多的就是AOP和IOC。

image.png

IOC:控制反轉

理念:讓別人為我們服務!

假設我現在需要一個物件,一般情況下我們都是需要自己new出來,但是有了IOC之後,我們將一些物件的控制權給了spring容器,當我們想要的時候,spring就會自己把物件送來!

image.png

看到這裡我們可能比較迷糊,還是不知道ioc的具體使用,我們來舉個例子好了:

假設我們有一個UserServiceImpl,是IUserService的實現類,但是我們每次使用的時候,都需要:

IUserService userService = new UserServiceImpl();

因為很多地方都要用到這個物件,所以我們要寫很多很多個new UserServiceImpl();

如果此時,IUserService更改了需求,需要把所有IUserService的實現類UserserviceImpl全部改成UserserviceImpl2(),那程式碼更改量是非常恐怖的。但是如果有了IOC,我們就可以和實現類解耦了。主要更改IOC容器裡面的實現類,當我們用到這個介面的實現類的時候,讓它給我們送來就行了(只需要修改一個地方)

AOP:面向切面程式設計

我們都知道,在我們編碼的時候,事務控制,許可權控制等程式碼邏輯,會像一把刀一樣,橫在我們業務邏輯中間,我們將這部分程式碼稱之為,橫切業務邏輯程式碼

那麼將這部分程式碼統一獨立出去,將橫切邏輯也業務邏輯分離,並進行統一管理,這就是AOP。

有了AOP和IOC的概念之後,接下來我們來梳理一下,spring的總體執行流程:

一開始,我們學習spring框架,都是從配置檔案開始的,我們在spring配置檔案中寫一個bean,然後我們再從容器中讀出來。

xml配置檔案:

    <bean id="a" class="slf.A">
    </bean>
//將xml載入到容器中
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//從容器中獲取bean
A a = (A) classPathXmlApplicationContext.getBean(A.class);

透過上述程式碼,我們大致可以分析出,我們使用spring的時候,大概是這樣一個執行邏輯:

image.png

我們大概可以總結出這個邏輯,但是spring裡面卻遠遠不止這麼多操作,我們先來對上面這張圖進行一個擴充,加入一個概念BeanDefinition!

image.png

BeanDefinition:
它是配置bean元資訊的一個介面,包含:

1)擁有屬性儲存的功能,比如類資訊,比如載入模式(懶載入等等)
2)擁有資源獲取的能力,也就是讀取配置資源的能力
3)對Bean物件的描述能力

因為有的bean不是馬上被例項化的(lazy-load),然後也可能有的bean包含了其它bean的資訊,也可能有的bean的屬性值需要讀取別的檔案(${jdbc.username}),所以我們需要beanDefinition。

那麼,bean資訊除了從xml中來,還可以從註解,json,yml,json等地方來,然後我們還可以對此圖進行一個擴充。

image.png

其實IOC容器裡面,並不僅僅只有反射,這麼簡單,因此我們對IOC容器也進行一個擴充。

image.png

我們先引入一個概念,beanFactoryPostProcessor:

BeanFactoryPostProcessor介面,可以對BeanDefinition後設資料進行操作。比如配置檔案中的${jdbc.username},我們可以使用BeanFactoryPostProcessor將它的值從檔案中讀取並賦值。

可見BeanFactoryPostProcessor可以修改BeanDefinition裡面的屬性值。

然後就是初始化,

在spring中,容器將物件的初始化和例項化分成了兩步操作:

分別是初始化和例項化:

如圖所示:

image.png

初始化,顧名思義,就是我們平時所說的,在堆記憶體開闢一塊空間

例項化比較複雜,分成了很多步,流程圖如所示,我們將它併入上面那個大圖,再進行講解:

image.png

填充屬性:
具體可以參考populateBean方法,大概就是裝配一些物件,比如物件A裡面有物件B,就將B從容器中取出來並裝進入,這個方法後續會詳細進行講解。

執行aware介面方法:
Aware介面的功能就是,一個類只要實現這個類介面的相關子類,則可以拿到相應的東西。

那麼具體可以拿到什麼呢?我們可以看一下這個類有哪些實現介面:
image.png
比如說 ApplicationContextAware,我們實現了這個介面,就可以從獲取ApplicationContext 這個物件:

public class AwareDemo implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

BeanPostProcessor:Before:
接下來執行beanPostProcessor前置方法,實現了這個介面的bean,我們要執行它的前置方法

public class BeanPostProcessorDemo implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return null;
    }
}

init-method:

如果bean的檔案配置中有init標籤,我們就要執行它對應的方法:

    <bean id="a" class="slf.A" init-method="init">
        <property name="b" ref="b"/>
    </bean>

BeanPostProcessor:After:
後置方法,可以參考上面。

3.總結

今天我們總結了Bean容器的大體的執行流程,也只是粗略地概括了一下,大致可以用一張圖來進行概括:

image.png

另外值得一提的是,beanFactoryPostProcessor和beanPostProcessor是有區別的,區別如圖所示:

image.png

相關文章