1. 程式人生 > >spring bean載入原理

spring bean載入原理

簡單的分析了一下spring bean的載入原理,屬於個人的理解,原始碼比這個要複雜的多:

spring的配置檔案applicationContext.xml的內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <!-- 使用者dao類,就是一個普通的pojo,裡面有個addUser方法,呼叫會輸出一行字 -->
    <bean id="userDao" class="com.qjl.study.spring.dao.impl.UserDaoImpl"></bean>
</beans
>

非web環境下我們通常這麼來載入IOC容器,獲取bean:

BeanFactory ac = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserDao userDao = ac.getBean("userDao");

所以,我簡單的實現了一下這個BeanFactory介面和ClassPathXmlApplicationContext類(不過一般上是用ApplicationContext這個介面來接收ClassPathXmlApplicationContext,不過就本例來說沒有太大的區別)。
BeanFactory介面的程式碼如下:

package com.qjl.study.spring.factory;

/**
 * 類名稱: bean工廠
 * 類描述: 例項化各種bean
 * 全限定性類名: com.qjl.study.spring.factory.BeanFactory
 * @author MrQJL
 * @date 2018年1月3日 下午9:46:30
 * @version V1.0
 */
public interface BeanFactory {

    /**
     * 通過bean的id獲取例項化的bean物件
     * 獲取bean的時候,該bean可能不存在,所以要丟擲異常
     * @param
bean的名稱 * @return 例項化的bean物件 */
Object getBean(String name) throws Exception; }

我在BeanFactory裡面就寫了一個getBean(String name)方法,用於輸入bean的id,返回對應的例項,因為輸入的bean的id可能不存在,所以要丟擲異常。
ClassPathXmlApplicationContext類實現了BeanFactory介面,程式碼如下:

package com.qjl.study.spring.factory.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;

import com.qjl.study.spring.factory.BeanFactory;

/**
 * 類名稱: 上下文物件
 * 類描述: 用於獲取編譯路徑下xml檔案所對應的bean的IOC容器
 * 全限定性類名: com.qjl.study.spring.factory.impl.ClassPathXmlApplicationContext
 * @author MrQJL
 * @date 2018年1月3日 下午10:04:50
 * @version V1.0
 */
public class ClassPathXmlApplicationContext implements BeanFactory {

    /**
     * 這就是那個IOC容器
     */
    private Map<String, Object> beans = new HashMap<String, Object>();

    public ClassPathXmlApplicationContext(String configLocation) throws Exception {
        // 使用jdom的SAXBuilder讀取xml檔案
        SAXBuilder sb = new SAXBuilder();
        // 載入xml文件進記憶體
        Document doc = sb.build(this.getClass().getClassLoader()
            .getResourceAsStream(configLocation));

        // 獲取根節點--也就是beans
        Element root = doc.getRootElement();
        // 獲取根節點的孩子節點--也就是bean
        @SuppressWarnings("unchecked")
        List<Object> childList = root.getChildren("bean");
        // 迴圈取出每一個bean節點以及他們的id和class屬性,利用反射建立一個物件
        for (int i = 0; i < childList.size(); i++) {
            Element child = (Element) childList.get(i);
            // 獲取id屬性
            String id = child.getAttributeValue("id");
            // 獲取class屬性
            String clazz = child.getAttributeValue("class");
            // 通過反射載入類,例項化bean物件
            Object obj = Class.forName(clazz).newInstance();
            // 將例項化的物件放入IOC容器(map)中
            beans.put(id, obj);
        }
    }

    @Override
    public Object getBean(String name) {
        return beans.get(name);
    }
}

下面就從這個ClassPathXmlApplicationContext的建構函式開始說起:

1.讀取spring的配置檔案

呼叫ClassPathXmlApplicationContext的有參構造方法,傳入配置檔案的名稱。
呼叫SAXBuilder的build方法讀取配置檔案

// 使用jdom的SAXBuilder讀取xml檔案,也可以使用dom4j,SAX讀取xml配置檔案
SAXBuilder sb = new SAXBuilder();
// 載入xml文件進記憶體,configLocation就是傳入的檔名,Document是jdom包裡面的類
Document doc = sb.build(this.getClass().getClassLoader().getResourceAsStream(configLocation));
// 獲取根節點,也就是beans
Element root = doc.getRootElement();
// 獲取根節點的孩子節點,也就是bean
List<Object> childList = root.getChildren("bean");

2.反射載入類,例項化物件,並將物件放入IOC容器

// 迴圈取出每一個bean節點以及他們的id和class屬性,利用反射建立一個物件
for (int i = 0; i < childList.size(); i++) {
    Element child = (Element) childList.get(i);
    // 獲取bean標籤的id屬性
    String id = child.getAttributeValue("id");
    // 獲取bean標籤的class屬性
    String clazz = child.getAttributeValue("class");
    // 通過反射載入類,例項化bean物件
    Object obj = Class.forName(clazz).newInstance();
    // 將例項化的物件放入IOC容器(map)中
    beans.put(id, obj);
}

3.通過getBean從IOC容器獲取物件

@Override
public Object getBean(String name) {
    return beans.get(name);
}

至此,建構函式內的業務邏輯執行完畢,配置檔案中配置的bean都載入並例項化完畢,呼叫getBean方法獲取對應的例項物件即可。

spring bean載入的大體流程就是這樣,理解了基本的原理後,再閱讀原始碼就會輕鬆一些了。