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實現。