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

喜歡我就點擊藍字關注我吧java

從今天開始,咱們一塊兒過一遍Spring的官網,爲Spring源碼的學習打好基礎。在這個過程當中,不會涉及過多底層的代碼,更可能是經過例子證實咱們在官網得出的結論,但願本身能夠堅持下來,給本身加個油!!!程序員

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

Spring容器
容器是什麼?

咱們先看官網中的一句話:spring

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

翻譯下來大概就是:app

  1. Spring IOC容器就是一個org.springframework.context.ApplicationContext的實例化對象框架

  2. 容器負責了實例化,配置以及裝配一個bean編輯器

那麼咱們能夠說:函數

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

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

容器如何工做?

咱們直接看官網上的一張圖片,以下:

在這裏插入圖片描述

Spring容器經過咱們提交的pojo類以及配置元數據產生一個充分配置的可使用的系統

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

Spring Bean
如何實例化一個Bean?

從官網上來看,主要有如下三種方法


  1. 構造方法

  2. 經過靜態工廠方法

  3. 經過實例工廠方法

這三種例子,官網都有具體的演示,這裏就再也不貼了,咱們經過本身查閱部分源碼,來驗證咱們在官網獲得的結論,而後經過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這個方法的入口打一個斷點,如圖:

在這裏插入圖片描述

接下來咱們對這個方法進行分析,代碼以下:

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, nullnull);

            }

            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);

    }

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

  1. 經過BeanDefinition中的instanceSupplier直接獲取一個實例化的對象。這個instanceSupplier屬性我自己不是特別理解,在xml中的

    標籤以及註解的方式都沒有找到方式配置這個屬性。後來在 org.springframework.context.support.GenericApplicationContext這個類中找到了如下兩個方法
在這裏插入圖片描述

通過斷點測試,發現這種狀況下,在實例化對象時會進入上面的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提供的一種方便外部擴展的手段,讓開發者可以更加靈活的實例化一個bean。

  1. 接下來咱們經過不一樣的建立bean的手段,來分別驗證對象的實例化方法

  • 經過@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:


能夠發現,代碼執行到最後一行,同時咱們看代碼上面的註釋能夠知道,當沒有進行特殊的處理的時候,默認會使用無參構造函數進行對象的實例化

  • 經過普通XML的方式(同@compent註解,這裏就不贅訴了)

  • 經過@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));

    }

}

一樣,斷點也進入最後一行

  • 經過@Bean的方式

測試代碼:

@Configuration

@ComponentScan("com.dmz.official")

public class Config {

    @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"));

    }

}

斷點結果:


能夠發現,經過@Bean方法建立對象時,Spring底層是經過factoryMethod的方法進行實例化對象的。Spring會在咱們須要實例化的這個對象對應的BeanDefinition中記錄factoryBeanName是什麼(在上面的例子中factoryBeanName就是config),同時會記錄這個factoryBean中建立對象的factoryMethodName是什麼,最後經過factoryBeanName獲取一個Bean而後反射調用factoryMethod實例化一個對象。

這裏咱們須要注意幾個概念:

  1. 這裏所說的經過靜態工廠方式經過factoryBeanName獲取一個Bean,注意,這個Bean,不是一個FactoryBean。也就是說不是一個實現了org.springframework.beans.factory.FactoryBean接口的Bean。至於什麼是FactoryBean咱們在後面的文章會認真分析

  2. 提到了一個概念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">


<!--    <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>

斷點以下:


能夠發現,這種狀況也進入了instantiateUsingFactoryMethod方法中。經過靜態工廠方法這種方式特殊之處在於,包含這個靜態方法的類,不須要實例化,不須要被Spring管理。Spring的調用邏輯大概是:

  1. 經過<bean>標籤中的class屬性獲得一個Class對象

  2. 經過Class對象獲取到對應的方法名稱的Method對象

  3. 最後反射調用Method.invoke(null,args)

由於是靜態方法,方法在執行時,不須要一個對象。

  • 經過實例工廠方法的方式

測試代碼(配置文件不變):

public static void main(String[] args) {

    ClassPathXmlApplicationContext cc =

        new ClassPathXmlApplicationContext("application.xml");

    System.out.println(cc.getBean("clientService"));

}

斷點以下:


仍是執行的這個方法。這個方法的執行過程我斷點跟蹤了之後,發現跟@Bean方式執行的流程是同樣的。這裏也再也不贅述了。

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

  1. Spring是如何推斷構造函數的?咱們在上面驗證的都是無參的構造函數,而且只提供了一個構造函數

  2. Spring是如何推斷方法的?不論是靜態工廠方法,仍是實例工廠方法的方式,咱們都只在類中提供了一個跟配置匹配的方法名,假設咱們對方法進行了重載呢?

要說清楚這兩個問題須要比較深刻的研究代碼,同時進行測試。咱們在官網學習過程當中,暫時不去強求這類問題。這裏提出來是爲了在源碼學習過程當中,咱們能夠帶必定目的性去閱讀。

實例化總結:
  1. 對象實例化,只是獲得一個對象,還不是一個徹底的Spring中的Bean,咱們實例化後的這個對象尚未完成依賴注入,沒有走完一系列的聲明週期,這裏須要你們注意

  2. Spring官網上指明瞭,在Spring中實例化一個對象有三種方式:

  • 構造函數

  • 實例工廠方法

  • 靜態工廠方法

  1. 我本身總結以下結論:

    Spring經過解析咱們的配置元數據,以及咱們提供的類對象獲得一個Beanfinition對象。經過這個對象能夠實例化出一個java bean對象。主要流程如圖:


這篇文章到這裏就結束了,主要學習了Spring官網中的1.2,1.3兩小節。下篇文章,咱們開始學習1.4中的知識。主要涉及到依賴注入的一些內容,也是咱們Spring中很是重要的一塊內容哦!下篇文章再見!




本文分享自微信公衆號 - 程序員DMZ(programerDmz)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索