1. 程式人生 > 其它 >Spring原理與原始碼分析系列(二)- Spring IoC容器啟動過程分析(上)

Spring原理與原始碼分析系列(二)- Spring IoC容器啟動過程分析(上)

前言

Spring的IoC容器是一個提供IoC支援的輕量級容器。 
Spring提供了兩種容器:BeanFactory和ApplicationContext。 
兩者的繼承關係圖如下:

 

 

  • BeanFactory:基本的IoC容器,預設採用延遲初始化策略(lazy-load),即只有當客戶端物件需要訪問容器中某個bean物件的時候,才會對該bean物件進行初始化以及依賴注入操作。 所以BeanFactory容器的特點是啟動初期速度快,所需資源有限,適合於資源有限功能要求不嚴格的場景。

  • ApplicationContext: ApplicationContext在BeanFactory基礎上構建,支援其他的高階特性,如國際化,事件釋出等。
相對於BeanFactory容器來說,ApplicationContext在啟動的時候即完成資源的初始化,所以啟動時間較長,適合於系統資源充足,需要更多功能的場景。

關於Spring容器啟動過程的分析,本章節分為兩篇文章進行敘述,第一篇主要介紹Spring中Bean的相關概念以及IoC容器型別;第二篇開始詳細介紹IoC容器的啟動過程。
本篇詳述Spring中Bean的相關概念以及IoC容器型別。

一、Spring Bean

在介紹IoC容器之前,我們需要知道什麼是Bean以及相關概念。

1、Bean定義

Java 中Bean的定義:

類中所有的屬性都必須封裝,即:使用private宣告;
封裝的屬性如果需要被外部所操作,則必須編寫對應的setter、getter方法;
一個JavaBean中至少存在一個無參構造方法。
如:

package com.wgs.spring.bean;
/**
 * @author GenshenWang.nomico
 * @date 2017/11/23.
 */
public class User {
    private String name;
    private int age;
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
}

 而Spring IoC容器就是管理bean的工廠。 
Spring中bean 是一個被例項化,組裝,並通過 Spring IoC 容器所管理的物件。這些 bean 是由用容器提供的配置元資料建立的。Spring可以採用XML配置檔案的方式來管理和配置Bean資訊,如下

<?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="user" class="com.wgs.spring.bean.User"></bean>
</beans>

 

<beans>是XML配置檔案中根節點,下面可包含多個<bean>子節點。
Spring的XML配置檔案中的配置與<bean>元素是一一對應的。

下面來看看bean定義的常見屬性:
(1)id屬性
通常,註冊到容器的物件都有一個唯一的id值,如id="user",這個id值使其表示的bean與其他bean區分開來,當然也可以用以下的name屬性值進行標識;
(2)name屬性
可以用name屬性來指定bean的別名。
如:

<bean id="user" name="beanname/user" class="com.wgs.spring.bean.User"></bean>

 

name可以使用逗號、空格或冒號等分割指定多個name,而id就不可以。 
(3)class屬性 
每個註冊到容器的bean都需要通過class屬性指定其型別。(部分情況例外 )

其餘常見屬性:

 

 

2、Bean的型別

  • XML
  • Annotation
  • Class
  • Properties、YML

3、Bean的注入方式

在XML配置中,常用的是構造方法注入與setter注入兩種方式。
(1)構造方法注入
構造方法注入是通過有參構造器方法來注入屬性值,在XML中通過<constructor-arg type="" value="">為其賦值。
如:

public class User {
    private String name;
    private int age;

    public User(String name){
        this.name = name;
    }
    public User(int age){
        this.age = age;
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

 這樣在Xml中,就可以通過type屬性來指定要傳入的引數型別。 
比如: 
如果給name賦值,就只需要傳入一個type=”String”型別的值即可:

<bean id="user" class="com.wgs.spring.bean.User">
        <constructor-arg type="java.lang.String">
            <value>wgs</value>
        </constructor-arg>
</bean>

 如果給age賦值,就只需要傳入一個type=”int”型別的值即可:

<bean id="user" class="com.wgs.spring.bean.User">
        <constructor-arg type="int">
            <value>25</value>
        </constructor-arg>
</bean>

 如果給age,name同時賦值,就可以通過index來指定傳入引數的順序:

<bean id="user" class="com.wgs.spring.bean.User">
    <constructor-arg index="0" value="wgs"></constructor-arg>
    <constructor-arg index="1" value="25"></constructor-arg>
</bean>

 

這樣index=”0”的value值就賦值給構造器User(String name, int age)第1個引數值;
index=”1”的value值就賦值給構造器第2個引數值.

如果在構造器中有別的物件的依賴,可以通過ref屬性來賦值。

(2)setter方法注入
setter方法注入需要Bean類提供setter方法和無參構造器,在XML配置檔案中通過<property name = "" value="">為其賦值。

public class User {
    private String name;
    private int age;

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

 在XML中為setter方法提供了<property >元素。<property >元素有個name屬性,用來指定對應bean類中的屬性值。 
如下:

 <bean id="user" class="com.wgs.spring.bean.User">
       <property name="age" value="25"></property>
       <property name="name" value="wgs"></property>
</bean>

 

4、Bean的scope

scope常被叫做“作用域”,宣告容器中該物件的存活時間。超過這個scope,物件即將被銷燬。
bean常見的scope有:

  • singleton
  • prototype
  • request
  • session
  • global session
容器預設的scope是singleton。
常用的是如下兩種型別:
(1)singleton
宣告為singleton型別的bean在容器中有如下特性:

一個物件例項:singleton型別的bean在一個容器中只存在一個共享例項,所有對該型別bean的引用引用都會共享這單一例項。
物件例項存活時間:從容器啟動到它第一次被請求而例項化開始,將一直存活到容器退出。即與IoC容器的生命週期相同。
IoC容器中預設的scope即是singleton,在XML中也可以設定為false:

<bean id="user" name="beanname/user" class="com.wgs.spring.bean.User" 
scope="singleton"></bean>

 

(2) prototype

宣告為prototype型別的bean在容器中有如下特性:

  • 物件例項:容器在接到該型別物件的請求的時候,每次都會重新建立一個新的物件例項給請求方;
  • 生命週期:容器每次返回請求方一個新的物件例項後,就不再擁有該物件的引用;該物件的生死均由請求方負責。
對於那些請求方不能共享使用的物件型別,應該將其bean定義的scope設定為prototype。

<bean id="user" name="beanname/user" class="com.wgs.spring.bean.User" scope="prototype"></bean>

 

這樣每個請求方都可以得到自己對應的一個物件例項。

singleton與prototype的區別:
(1)singleton型別的物件在容器只會存在一個物件例項,被共享;
而prototype型別的物件會每次建立新的物件例項。
(2)singleton型別的物件的生命週期由容器管理;
而prototype型別的物件的生命週期由自己管理。

5、BeanDefinition介面

Spring IoC是管理Bean的容器,負責建立,裝配,銷燬Bean。
在IoC容器中,BeanDefinition抽象了Bean的定義,儲存了Bean的必要資訊,如在xml配置中,BeanDefinition就儲存了與<bean>相關的id,name,aliases等屬性,封裝了很多與Bean相關的基本資料(在XML配置中這些資料都是通過諸如<bean id="" name="">等標籤進行配置的),是容器實現依賴反轉功能的核心資料結構。

下面是BeanDefinition的原始碼:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
    String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
    int ROLE_APPLICATION = 0;
    int ROLE_SUPPORT = 1;
    int ROLE_INFRASTRUCTURE = 2;
    String getParentName();
    void setParentName(String parentName);
    String getBeanClassName();
    void setBeanClassName(String beanClassName);
    String getFactoryBeanName();
    void setFactoryBeanName(String factoryBeanName);
    String getFactoryMethodName();
    void setFactoryMethodName(String factoryMethodName);
    String getScope();
    void setScope(String scope);
    boolean isLazyInit();
    void setLazyInit(boolean lazyInit);
    String[] getDependsOn();
    void setDependsOn(String... dependsOn);
    boolean isAutowireCandidate();
    void setAutowireCandidate(boolean autowireCandidate);
    boolean isPrimary();
    void setPrimary(boolean primary);
    ConstructorArgumentValues getConstructorArgumentValues();
    MutablePropertyValues getPropertyValues();
    boolean isSingleton();
    boolean isPrototype();
    boolean isAbstract();
    int getRole();
    String getDescription();
    String getResourceDescription();
    BeanDefinition getOriginatingBeanDefinition();
}

 

可以看到BeanDefinition介面中定義了很多bean相關的屬性和方法,
如類名,scope,屬性,構造引數列表,是否單例,是否軟載入等。
這樣對bean的操作實際上就是直接對BeanDefinition進行操作。

BeanDefinition介面繼承了AttributeAccessor介面,說明它擁有處理屬性的能力;
BeanDefinition介面繼承了BeanMetadataElement 介面,說明它擁有bean元素的屬性。

BeanDefinition只是一個介面,常用的實現類有:

  • ChildBeanDefinition
  • RootBeanDefinition
  • GenericBeanDefinition
二 Spring IoC容器—BeanFactory容器

1 BeanFactory容器職責—物件註冊與依賴繫結

  BeanFactory就是生成Bean的工廠,作為Spring提供的基本的IoC容器,其主要職責是:

  • 業務物件的註冊
  • 物件間依賴關係的繫結
BeanFactory的原始碼:

package org.springframework.beans.factory;

import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;

public interface BeanFactory {

String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;

Object getBean(String name, Object... args) throws BeansException;  
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

boolean containsBean(String name);

boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;    
Class<?> getType(String name) throws NoSuchBeanDefinitionException;

String[] getAliases(String name);

}

 

可以看出,BeanFactory介面中定義了一系列管理Bean的方法,如:
  • getBean:在容器中根據Bean name取得某個Bean;
  • containsBean:判斷容器中是否存在某個Bean;
  • isSingleton:判斷Bean是否是Singleton型別的Bean;
  • isPrototype:判斷Bean是否是Prototype型別的Bean;
  • isTypeMatch:判斷Bean的Class型別與指定的Class型別是否匹配;
  • getType:查詢Bean的Class型別;
  • getAliases:查詢Bean的別名。

BeanFactory只是一個介面,而DefaultListableBeanFactory是其一個較為通用的實現類。
下圖是BeanFactory容器物件註冊與依賴繫結過程中涉及到的介面: 

 

 

如圖所示,DefaultListableBeanFactory除了間接實現BeanFactory介面,還實現了BeanDefinitionRegistry介面。
BeanDefinitionRegistry介面定義抽象了Bean的註冊邏輯,擔當Bean註冊管理的角色,而BeanDefinitionRegistry將Bean註冊到容器當中,是以BeanDefinition儲存的。在容器當中每一個受管理的Bean都有一個與之對應的BeanDefinition,BeanDefinition儲存物件的所有必要資訊。

總結:
  • BeanFactory:該介面只定義如何訪問容器內管理的Bean的方法;
  • BeanDefinitionRegistry:該介面充當Bean註冊管理的角色;
  • DefaultListableBeanFactory:上述兩介面的具體實現類,負責Bean註冊以及管理的管理。

2. 物件註冊與依賴繫結方式

上小節描述了BeanFactory的功能與職責主要是物件註冊與依賴繫結。
Spring提供了三種方式來實現物件註冊與依賴繫結過程。

(1)直接編碼方式

首先需要通過BeanFactory來建立一個IoC容器,
然後通過BeanDefinitionRegistry將bean註冊到容器當中(而DefaultListableBeanFactory是上述兩介面的具體實現類),
最後可通過構造注入或者setter注入方式完成物件間關係的依賴繫結。

程式碼實現如下:

package com.wgs.spring.registry;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/17
 */
public class BeanFactoryRegistryDemo {

    public static void main(String[] args) {
        DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
        BeanFactory container = bindBeanByCode(beanRegistry);

        //3 完成上述步驟後即可從容器獲取Bean例項
        CommentService service = (CommentService) container.getBean("commentService");
        System.out.println(service.getCount());
    }

    public static BeanFactory bindBeanByCode(BeanDefinitionRegistry beanRegistry){
        //先將Bean抽象成BeanDefinition
        AbstractBeanDefinition commentService = new RootBeanDefinition(CommentService.class);
        AbstractBeanDefinition commentDao = new RootBeanDefinition(CommentDao.class);

        //1 註冊:beanRegistry是BeanDefinitionRegistry實現類,可以實現Bean註冊功能
        beanRegistry.registerBeanDefinition("commentService", commentService);
        beanRegistry.registerBeanDefinition("commentDao", commentDao);

        //2 依賴繫結:將commnetDao繫結到commnetService當中
        //  (1)通過構造方法注入
       /* ConstructorArgumentValues argumentValues = new ConstructorArgumentValues();
        argumentValues.addIndexedArgumentValue(0, commentDao);
        commentService.setConstructorArgumentValues(argumentValues);*/
        // (2)通過setter方法注入
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        propertyValues.addPropertyValue(new PropertyValue("commentDao", commentDao));
        commentService.setPropertyValues(propertyValues);

        return (BeanFactory)beanRegistry;
    }
}

 

(2)外部配置檔案方式

Spring的IoC容器支援兩種配置檔案格式:

  • Properties檔案格式
  • XML檔案格式
在這個過程中有個很重要的介面:BeanDefinitionReader。
該介面負責讀取配置檔案內容並對映到BeanDefinition,然後將BeanDefinition交由BeanDefinitionRegistry 完成Bean的註冊與載入。

本文通過XML檔案格式來描述這個功能,程式碼實現如下:
xml配置檔案:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="commentDao" class="com.wgs.spring.registry.CommentDao"/>
    <bean id="commentService" class="com.wgs.spring.registry.CommentService">
        <property name="commentDao">
            <ref bean="commentDao"></ref>
        </property>
    </bean>

</beans>

 Spring中提供了BeanDefinitionReader的實現類XmlBeanDefinitionReader來讀取檔案內容,並載入到容器中:

package com.wgs.spring.registry;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.*;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/17
 */
public class BeanFactoryRegistryDemo2 {

    public static void main(String[] args) {
        DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
        BeanFactory container = bindBeanByXML(beanRegistry);
        CommentService commentService = (CommentService) container.getBean("commentService");
        System.out.println(commentService.getCount());
    }

    public static BeanFactory bindBeanByXML(BeanDefinitionRegistry beanRegistry){
        //讀取配置檔案內容,解析檔案格式,並對映到對應的BeanDefinition,完成註冊
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanRegistry);
        reader.loadBeanDefinitions("classpath:/spring.xml");
        return (BeanFactory) beanRegistry;
    }
}

 

3)註解方式

註解方式需要使用@Autowired和@Component對物件進行標記。

@Component:配合<context:component-scan base-package=""/>使用,<context:component-scan package=""/>會掃描指定的包(package)下標註有 @Component的類,並將他們作為Bean新增到容器當中進行管理;
@Autowired:通知容器,將依賴物件注入到當前物件中。
程式碼實現如下:

Component
public class CommentDao {
...
}

Component
public class CommentService {
    @Autowired
    private CommentDao commentDao;

    public int getCount(){
        commentDao = new CommentDao();
        return commentDao.getCommentCount();
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="com.wgs.spring.registry"/>
</beans>

 

三、Spring IoC容器—ApplicationContext容器

ApplicationContext是高階IoC容器的實現,除了擁有BeanFactory支援的所有功能外,還進一步拓展了基本容器的功能,包括:

  • BeanFactoryPostProcessor
  • 特殊型別的bean的自動識別
  • 容器啟動後bean例項的自動初始化
  • 國際化
  • 容器內時間釋出
Spring為BeanFactory提供了XmlBeanFactory實現;相應地為ApplicationContext型別容器提供如下幾個常用實現:
(1)FileSystemXmlApplicationContext:從檔案系統載入bean定義以及相關資源的ApplicationContext實現;
(2)ClassPathXmlApplicationContext:從classpath下載入bean定義以及相關資源的ApplicationContext實現;
(3)XmlWebApplicationContext:從Web應用程式中載入bean定義以及相關資源的ApplicationContext實現。