Spring官網閱讀系列(一):Spring容器及實例化

Spring官網閱讀系列(一):Spring容器及實例化
從今天開始,我們一起過一遍Spring的官網,一邊讀,一邊結合在路神課堂上學習的知識,講一講自己的理解。不管是之前關於動態代理的文章,還是讀Spring的官網,都是爲了之後對Spring的源碼做更全面細緻的學習,所以在這個過程中,不會涉及過多底層的代碼,更多是通過例子證明我們在官網得出的結論,希望自己可以堅持下來,給自己加個油!!!

本文主要涉及到官網中的1.2,1.3節。

[TOC]

Spring容器
容器是什麼?
我們先看官網中的一句話:

The org.springframework.context.ApplicationContext interface represents the Spring IoC container and is responsible for instantiating, configuring, and assembling the beans.

翻譯下來大概就是:

Spring IOC容器就是一個org.springframework.context.ApplicationContext的實例化對象
容器負責了實例化,配置以及裝配一個bean
那麼我們可以說:

從代碼層次來看:Spring容器就是一個實現了ApplicationContext接口的對象,

從功能上來看: Spring 容器是 Spring 框架的核心,是用來管理對象的。容器將創建對象,把它們連接在一起,配置它們,並管理他們的整個生命週期從創建到銷燬。

容器如何工作?
我們直接看官網上的一張圖片,如下:

Spring官網閱讀系列(一):Spring容器及實例化
Spring容器通過我們提交的pojo類以及配置元數據產生一個充分配置的可以使用的系統

這裏說的配置元數據,實際上我們就是我們提供的XML配置文件,或者通過註解方式提供的一些配置信息

Spring Bean
如何實例化一個Bean?
從官網上來看,主要有以下三種方法

Spring官網閱讀系列(一):Spring容器及實例化
構造方法
通過靜態工廠方法
通過實例工廠方法
這三種例子,官網都有具體的演示,這裏就不再貼了,我們通過自己查閱部分源碼,來驗證我們在官網得到的結論,然後通過debug等方式進行驗證。

我們再從代碼的角度進行一波分析,這裏我們直接定位到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance這個方法中,具體定位步驟不再演示了,大家可以通過形如下面這段代碼:

ClassPathXmlApplicationContext cc =
// 這裏我們通過xml配置實例化一個容器
new ClassPathXmlApplicationContext("classpath:application.xml");
MyServiceImpl luBan = (MyServiceImpl) cc.getBean("myServiceImpl");
直接main方法運行,然後在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance這個方法的入口打一個斷點,如圖:

Spring官網閱讀系列(一):Spring容器及實例化
Spring官網閱讀系列(一):Spring容器及實例化
接下來我們對這個方法進行分析,代碼如下:

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 1.獲取這個bean的class屬性,確保beanDefinition中beanClass屬性已經完成解析
// 我們通過xml從<bean>標籤中解析出來的class屬性在剛剛開始的時候必定是個字符串
Class<?> beanClass = resolveBeanClass(mbd, beanName);

    // 省略異常判斷代碼.....

    // 2.通過beanDefinition中的supplier實例化這個bean
    Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
    if (instanceSupplier != null) {
        return obtainFromSupplier(instanceSupplier, beanName);
    }

    // 3.通過FactoryMethod實例化這個bean
    if (mbd.getFactoryMethodName() != null) {
        return instantiateUsingFactoryMethod(beanName, mbd, args);
    }

    // 4.下面這段代碼都是在通過構造函數實例化這個Bean,分兩種情況,一種是通過默認的無參構造,一種                   是通過推斷出來的構造函數
    boolean resolved = false;
    boolean autowireNecessary = false;
    if (args == null) {
        synchronized (mbd.constructorArgumentLock) {
            if (mbd.resolvedConstructorOrFactoryMethod != null) {
                resolved = true;
                autowireNecessary = mbd.constructorArgumentsResolved;
            }
        }
    }

    if (resolved) {
        if (autowireNecessary) {
            return autowireConstructor(beanName, mbd, null, null);
        }
        else {
            return instantiateBean(beanName, mbd);
        }
    }

    // Candidate constructors for autowiring?
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
            mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
        return autowireConstructor(beanName, mbd, ctors, args);
    }

    // Preferred constructors for default construction?
    ctors = mbd.getPreferredConstructors();
    if (ctors != null) {
        return autowireConstructor(beanName, mbd, ctors, null);
    }

    // No special handling: simply use no-arg constructor.
    return instantiateBean(beanName, mbd);
}

我們主要關注進行實例化的幾個方法:

通過BeanDefinition中的instanceSupplier直接獲取一個實例化的對象。這個instanceSupplier屬性我本身不是特別理解,在xml中的標籤以及註解的方式都沒有找到方式配置這個屬性。後來在org.springframework.context.support.GenericApplicationContext這個類中找到了以下兩個方法
Spring官網閱讀系列(一):Spring容器及實例化
經過斷點測試,發現這種情況下,在實例化對象時會進入上面的supplier方法。下面是測試代碼:

public static void main(String[] args) {
// AnnotationConfigApplicationContext是GenericApplicationContext的一個子類
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.registerBean("service", Service.class,Service::new);
ac.refresh();
System.out.println(ac.getBean("service"));
}
可以發現進入了這個方法進行實例化

Spring官網閱讀系列(一):Spring容器及實例化
這個方法一般不常用,平常我們也使用不到,就不做過多探究,筆者認爲,這應該是Spring提供的一種方便外部擴展的手段,讓開發者能夠更加靈活的實例化一個bean。

接下來我們通過不同的創建bean的手段,來分別驗證對象的實例化方法br/>通過@compent,@Service等註解的方式
測試代碼:

public class Main {
public static void main(String[] args) {
// 通過配置類掃描
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
System.out.println(ac.getBean(Service.class));
}
}

@Component
public class Service {

}
觀察debug:

Spring官網閱讀系列(一):Spring容器及實例化
可以發現,代碼執行到最後一行,同時我們看代碼上面的註釋可以知道,當沒有進行特殊的處理的時候,默認會使用無參構造函數進行對象的實例化

通過普通XML的方式(同@compent註解,這裏就不贅訴了)br/>通過@Configuration註解的方式
測試代碼:

public class Main {
public static void main(String[] args) {
// 通過配置類掃描
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// 這裏將測試對象換爲config即可,同時記得將條件斷點更改爲beanName.equlas("config")
System.out.println(ac.getBean(config.class));
}
}
Spring官網閱讀系列(一):Spring容器及實例化
同樣,斷點也進入最後一行

通過@Bean的方式
測試代碼:

@Configurationbr/>@ComponentScan("com.dmz.official")
public class Config {
br/>@Bean
public Service service(){
return new Service();
}
}

public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(Config.class);
System.out.println(ac.getBean("service"));
}
}
斷點結果:

Spring官網閱讀系列(一):Spring容器及實例化
可以發現,通過@Bean方法創建對象時,Spring底層是通過factoryMethod的方法進行實例化對象的。Spring會在我們需要實例化的這個對象對應的BeanDefinition中記錄factoryBeanName是什麼(在上面的例子中factoryBeanName就是config),同時會記錄這個factoryBean中創建對象的factoryMethodName是什麼,最後通過factoryBeanName獲取一個Bean然後反射調用factoryMethod實例化一個對象。

這裏我們需要注意幾個概念:

這裏所說的通過靜態工廠方式通過factoryBeanName獲取一個Bean,注意,這個Bean,不是一個FactoryBean。也就是說不是一個實現了org.springframework.beans.factory.FactoryBean接口的Bean。至於什麼是FactoryBean我們在後面的文章會認真分析
提到了一個概念BeanDefinition,它就是Spring對自己所管理的Bean的一個抽象。不懂可以暫且跳過,後面有文章會講到。
通過靜態工廠方法的方式
測試代碼:

public static void main(String[] args) {
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application.xml");
System.out.println(cc.getBean("service"));
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"&gt;
<!-- <bean id="myServiceImpl" class="com.dmz.official.service.Service"/>-->

<!-- the factory bean, which contains a method called get() -->
<bean id="myFactoryBean" class="com.dmz.official.service.MyFactoryBean">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- 測試實例工廠方法創建對象-->
<bean id="clientService"
      factory-bean="myFactoryBean"
      factory-method="get"/>

<!--測試靜態工廠方法創建對象-->
<bean id="service"
      class="com.dmz.official.service.MyFactoryBean"
      factory-method="staticGet"/>

</beans>
斷點如下:

Spring官網閱讀系列(一):Spring容器及實例化
可以發現,這種情況也進入了instantiateUsingFactoryMethod方法中。通過靜態工廠方法這種方式特殊之處在於,包含這個靜態方法的類,不需要實例化,不需要被Spring管理。Spring的調用邏輯大概是:

通過<bean>標籤中的class屬性得到一個Class對象
通過Class對象獲取到對應的方法名稱的Method對象
最後反射調用Method.invoke(null,args)
因爲是靜態方法,方法在執行時,不需要一個對象。

通過實例工廠方法的方式
測試代碼(配置文件不變):

public static void main(String[] args) {
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application.xml");
System.out.println(cc.getBean("clientService"));
}
斷點如下:

Spring官網閱讀系列(一):Spring容器及實例化
還是執行的這個方法。這個方法的執行過程我斷點跟蹤了以後,發現跟@Bean方式執行的流程是一樣的。這裏也不再贅述了。

到這裏,這段代碼我們算結合官網大致過了一遍。其實還遺留了以下幾個問題:

Spring是如何推斷構造函數的?我們在上面驗證的都是無參的構造函數,並且只提供了一個構造函數
Spring是如何推斷方法的?不管是靜態工廠方法,還是實例工廠方法的方式,我們都只在類中提供了一個跟配置匹配的方法名,假設我們對方法進行了重載呢?
要說清楚這兩個問題需要比較深入的研究代碼,同時進行測試。我們在官網學習過程中,暫時不去強求這類問題。這裏提出來是爲了在源碼學習過程中,我們可以帶一定目的性去閱讀。

實例化總結:
對象實例化,只是得到一個對象,還不是一個完全的Spring中的Bean,我們實例化後的這個對象還沒有完成依賴注入,沒有走完一系列的聲明週期,這裏需要大家注意
Spring官網上指明瞭,在Spring中實例化一個對象有三種方式:構造函數實例工廠方法靜態工廠方法
我自己總結如下結論:Spring通過解析我們的配置元數據,以及我們提供的類對象得到一個Beanfinition對象。通過這個對象可以實例化出一個java bean對象。主要流程如圖:
Spring官網閱讀系列(一):Spring容器及實例化
這篇文章到這裏就結束了,主要學習了Spring官網中的1.2,1.3兩小節。下篇文章,我們開始學習1.4中的知識。主要涉及到依賴注入的一些內容,也是我們Spring中非常重要的一塊內容哦!

看完記得點個關注+分享,我們下篇文章再見!

推薦閱讀
金三銀四季,阿里工作10多年Java大牛的“心得”,獻給迷茫中的你)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章