Spring IoC容器的初始化
文章目錄
關於Spirng IoC容器的初始化過程在《Spirng技術內幕:深入解析Spring架構與設計原理》一書中有明確的指出,IoC容器的初始化過程可以分為三步:
- Resource定位(Bean的定義檔案定位)
- 將Resource定位好的資源載入到BeanDefinition
- 將BeanDefiniton註冊到容器中
Resource定位
Resource是Sping中用於封裝I/O操作的介面。正如前面所見,在建立spring容器時,通常要訪問XML配置檔案,除此之外還可以通過訪問檔案型別、二進位制流等方式訪問資源,還有當需要網路上的資源時可以通過訪問URL,Spring把這些檔案統稱為Resource。
常用的resource資源型別如下:
FileSystemResource
:以檔案的絕對路徑方式進行訪問資源,效果類似於Java中的File;
ClassPathResource
:以類路徑的方式訪問資源,效果類似於this.getClass().getResource("/").getPath();
ServletContextResource
UrlResource
:訪問網路資源的實現類。例如file: http: ftp:等字首的資源物件;ByteArrayResource
: 訪問位元組陣列資源的實現類。
Spring提供了ResourceLoader介面用於實現不同的Resource載入策略,該介面的例項物件中可以獲取一個resource物件,也就是說將不同Resource例項的建立交給ResourceLoader的實現類來處理。
ResourceLoader介面中只定義了兩個方法:
Resource getResource(String location); //通過提供的資源location引數獲取Resource例項
ClassLoader getClassLoader(); // 獲取ClassLoader,通過ClassLoader可將資源載入JVM
注:ApplicationContext的所有實現類都實現了RecourceLoader介面,因此可以直接呼叫getResource方法獲取Resoure物件。不同的ApplicatonContext實現類使用getResource方法取得的資源型別不同,例如:FileSystemXmlApplicationContext.getResource獲取的就是FileSystemResource例項;ClassPathXmlApplicationContext.gerResource獲取的就是ClassPathResource例項;XmlWebApplicationContext.getResource獲取的就是ServletContextResource例項,另外像不需要通過xml直接使用註解@Configuation方式載入資源的AnnotationConfigApplicationContext等等。
在資源定位過程完成以後,就為資原始檔中的bean的載入創造了I/O操作的條件。
通過返回的resource物件,進行BeanDefinition的載入
在Spring中配置檔案主要格式是XML,對於用來讀取XML型資原始檔來進行初始化的IoC 容器而言,該類容器會使用到AbstractXmlApplicationContext類,該類定義了一個名為loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的方法用於獲取BeanDefinition:
// 該方法屬於AbstractXmlApplicationContect類
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
// 用於獲取BeanDefinition
this.loadBeanDefinitions(beanDefinitionReader);
}
此方法在具體執行過程中首先會new一個與容器對應的BeanDefinitionReader型例項物件,然後將生成的BeanDefintionReader例項作為引數傳入loadBeanDefintions(XmlBeanDefinitionReader),繼續往下執行載入BeanDefintion的過程。例如AbstractXmlApplicationContext有兩個實現類:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext,這些容器在呼叫此方法時會建立一個XmlBeanDefinitionReader類物件專門用來載入所有的BeanDefinition。
下面以XmlBeanDefinitionReader物件載入BeanDefinition為例,使用原始碼說明載入BeanDefinition的過程:
// 該方法屬於AbstractXmlApplicationContect類
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();//獲取所有定位到的resource資源位置(使用者定義)
if (configResources != null) {
reader.loadBeanDefinitions(configResources);//載入resources
}
String[] configLocations = getConfigLocations();//獲取所有本地配置檔案的位置(容器自身)
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);//載入resources
}
}
通過上面程式碼將使用者定義的資源以及容器本身需要的資源全部載入到reader中,reader.loadBeanDefinitions方法的原始碼如下:
// 該方法屬於AbstractBeanDefinitionReader類, 父介面BeanDefinitionReader
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
// 將所有資源全部載入,交給AbstractBeanDefinitionReader的實現子類處理這些resource
counter += loadBeanDefinitions(resource);
}
return counter;
}
BeanDefinitionReader介面定義了 int loadBeanDefinitions(Resource resource)方法:
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
XmlBeanDefinitionReader 類實現了BeanDefinitionReader介面中的loadBeanDefinitions(Resource)方法。XmlBeanDefinitionReader類中幾主要對載入的所有resource開始進行處理,大致過程是,先將resource包裝為EncodeResource型別,然後處理,為生成BeanDefinition物件做準備,其主要幾個方法的原始碼如下:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 包裝resource為EncodeResource型別
return loadBeanDefinitions(new EncodedResource(resource));
}
// 載入包裝後的EncodeResource資源
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
try {
// 通過resource物件得到XML檔案內容輸入流,併為IO的InputSource做準備
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// Create a new input source with a byte stream.
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 開始準備 load bean definitions from the specified XML file
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 獲取指定資源的驗證模式
int validationMode = getValidationModeForResource(resource);
// 從資源物件中載入DocumentL物件,大致過程為:將resource資原始檔的內容讀入到document中
// DocumentLoader在容器讀取XML檔案過程中有著舉足輕重的作用!
// XmlBeanDefinitionReader例項化時會建立一個DefaultDocumentLoader型的私有屬性,繼而呼叫loadDocument方法
// inputSource--要載入的文件的輸入源
Document doc = this.documentLoader.loadDocument(
inputSource, this.entityResolver, this.errorHandler, validationMode, this.namespaceAware);
// 將document檔案的bean封裝成BeanDefinition,並註冊到容器
return registerBeanDefinitions(doc, resource);
}
catch ...(略)
}
上面程式碼分析到了registerBeanDefinitions(doc, resource)這一步,也就是準備將Document中的Bean按照Spring bean語義進行解析並轉化為BeanDefinition型別,這個方法的具體過程如下:
/**
* 屬於XmlBeanDefinitionReader類
* Register the bean definitions contained in the given DOM document.
* @param doc the DOM document
* @param resource
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException
*/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 獲取到DefaultBeanDefinitionDocumentReader例項
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 獲取容器中bean的數量
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
通過 XmlBeanDefinitionReader 類中的私有屬性 documentReaderClass 可以獲得一個 DefaultBeanDefinitionDocumentReader 例項物件:
private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
DefaultBeanDefinitionDocumentReader實現了BeanDefinitionDocumentReader介面,它的registerBeanDefinitions方法定義如下:
// DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
// 獲取doc的root節點,通過該節點能夠訪問所有的子節點
Element root = doc.getDocumentElement();
// 處理beanDefinition的過程委託給BeanDefinitionParserDelegate例項物件來完成
BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
// Default implementation is empty.
// Subclasses can override this method to convert custom elements into standard Spring bean definitions
preProcessXml(root);
// 核心方法,代理
parseBeanDefinitions(root, delegate);
postProcessXml(root);
}
上面出現的BeanDefinitionParserDelegate類非常非常重要(需要了解代理技術,如JDK動態代理、cglib動態代理等)。Spirng BeanDefinition的解析就是在這個代理類下完成的,此類包含了各種對符合Spring Bean語義規則的處理,比如<bean></bean>、<import></import>、<alias><alias/>等的檢測。
parseBeanDefinitions(root, delegate)方法如下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
// 遍歷所有節點,做對應解析工作
// 如遍歷到<import>標籤節點就呼叫importBeanDefinitionResource(ele)方法對應處理
// 遍歷到<bean>標籤就呼叫processBeanDefinition(ele,delegate)方法對應處理
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
//對應使用者自定義節點處理方法
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 解析<import>標籤
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 解析<alias>標籤
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 解析<bean>標籤,最常用,過程最複雜
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 解析<beans>標籤
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
這裡針對常用的<bean>標籤中的方法做簡單介紹,其他標籤的載入方式類似:
/**
* Process the given bean element, parsing the bean definition
* and registering it with the registry.
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 該物件持有beanDefinition的name和alias,可以使用該物件完成beanDefinition向容器的註冊
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 註冊最終被修飾的bean例項,下文註冊beanDefinition到容器會講解該方法
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
parseBeanDefinitionElement(Element ele)方法會呼叫parseBeanDefinitionElement(ele, null)方法,並將值返回BeanDefinitionHolder類物件,這個方法將會對給定的<bean> 籤進行解析,如果在解析<bean>標籤的過程中出現錯誤則返回null。
需要強調一下的是parseBeanDefinitionElement(ele, null)方法中產生了一個抽象型別的BeanDefinition例項,這也是我們首次看到直接定義BeanDefinition的地方,這個方法裡面會將<bean>標籤中的內容解析到BeanDefinition中,之後再對BeanDefinition進行包裝,將它與beanName,Alias等封裝到BeanDefinitionHolder 物件中,該部分原始碼如下:
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
...(略)
String beanName = id;
...(略)
// 從上面按過程走來,首次看到直接定義BeanDefinition !!!
// 該方法會對<bean>節點以及其所有子節點如<property>、<List>、<Set>等做出解析,具體過程本文不做分析(太多太長)
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
...(略)
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
將BeanDefiniton註冊到容器中
最終Bean配置會被解析成BeanDefinition並與beanName,Alias一同封裝到BeanDefinitionHolder類中, 之後beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()),註冊到DefaultListableBeanFactory.beanDefinitionMap中。之後客戶端如果要獲取Bean物件,Spring容器會根據註冊的BeanDefinition資訊進行例項化。
BeanDefinitionReaderUtils類:
public static void registerBeanDefinition(
BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory) throws BeansException {
// Register bean definition under primary name.
String beanName = bdHolder.getBeanName();
// 註冊beanDefinition!!!
beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition());
// 如果有別名的話也註冊進去,Register aliases for bean name, if any.
String[] aliases = bdHolder.getAliases();
if (aliases != null) {
for (int i = 0; i < aliases.length; i++) {
beanFactory.registerAlias(beanName, aliases[i]);
}
}
}
DefaultListableBeanFactory實現了上面呼叫BeanDefinitionRegistry介面的 registerBeanDefinition( beanName, bdHolder.getBeanDefinition())方法,這一部分的主要邏輯是向DefaultListableBeanFactory物件的beanDefinitionMap中存放beanDefinition,當初始化容器進行bean初始化時,在bean的生命週期分析裡必然會在這個beanDefinitionMap中獲取beanDefition例項,有機會成文分析一下bean的生命週期,到時可以分析一下如何使用這個beanDefinitionMap。
registerBeanDefinition( beanName, bdHolder.getBeanDefinition() )方法具體方法如下:
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "Bean definition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// beanDefinitionMap是個ConcurrentHashMap型別資料,用於存放beanDefinition,它的key值是beanName
Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': there's already [" + oldBeanDefinition + "] bound");
}
else {
if (logger.isInfoEnabled()) {
logger.info("Overriding bean definition for bean '" + beanName +
"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
}
else {
this.beanDefinitionNames.add(beanName);
}
// 將獲取到的BeanDefinition放入Map中,容器操作使用bean時通過這個HashMap找到具體的BeanDefinition
this.beanDefinitionMap.put(beanName, beanDefinition);
// Remove corresponding bean from singleton cache, if any.
// Shouldn't usually be necessary, rather just meant for overriding
// a context's default beans (e.g. the default StaticMessageSource
// in a StaticApplicationContext).
removeSingleton(beanName);
}
小結
定位:RecourceLoader介面的實現類通過getResource(資源引數)獲取Resource物件。
載入:loadBeanDefinitions(DefaultListableBeanFactory beanFactory) ,建立一個與容器對應的BeanDefinitionReader例項物件,然後將生成的BeanDefintionReader例項作為引數傳入loadBeanDefintions(XmlBeanDefinitionReader)。
1.BeanDefinitionReader介面的實現類載入Resource到reader中:先將resource包裝為EncodeResource型別,再將resource資原始檔的內容讀入到document中。
2.BeanDefinitionParserDelegate類包含了各種對符合Spring Bean語義規則的處理,將document中的<bean>標籤中的內容解析到BeanDefinition中,之後再對BeanDefinition進行包裝,將它與beanName,Alias等封裝到BeanDefinitionHolder 物件中。
註冊:DefaultListableBeanFactory實現了BeanDefinitionRegistry介面,完成BeanDefinition向容器的註冊。通過registerBeanDefinition( beanName, bdHolder.getBeanDefinition())方法,向DefaultListableBeanFactory物件的beanDefinitionMap中存放BeanDefinition。