Spring Ioc 源碼分析之Bean的加載和構(gòu)造

我們都知道瓮床,Spring Ioc和Aop是Spring的核心的功能,因此花一點(diǎn)時間去研究還是很有意義的,如果僅僅是知其所以然纤垂,也就體會不到大師設(shè)計Spring的精華矾策,還記得那句話,Spring為JavaEE開發(fā)帶來了春天峭沦。

IOC就是Inversion of control 也就是控制反轉(zhuǎn)的意思贾虽,另一種稱呼叫做依賴注入,這個可能更直觀一點(diǎn)吼鱼,拿個例子來說吧:

@Component
public class UserService {
    @Autowired
    private UserMapper mapper;
}

比如在UserService可能要調(diào)用一個Mapper蓬豁,這個Mapper去做DAO的操作,在這里我們直接通過@Autowired注解去注入這個Mapper菇肃,這個就叫做依賴注入地粪,你想要什么就注入什么,不過前提它是一個Bean琐谤。至于是怎么注入的蟆技,那是Spring容器做的事情,也是我們今天去探索的斗忌。

在進(jìn)行分析之前质礼,我先聲明一下,下面的這些代碼并不是從spring 源碼中直接拿過來织阳,而是通過一步步簡化眶蕉,抽取spring源碼的精華,如果直接貼源碼唧躲,我覺得可能很多人都會被嚇跑造挽,而且還不一定能夠?qū)W到真正的東西。

Spring要去管理Bean首先要把Bean放到容器里弄痹,那么Spring是如何獲得Bean的呢饭入?

  • 首先,Spring有一個數(shù)據(jù)結(jié)構(gòu)界酒,BeanDefinition圣拄,這里存放的是Bean的內(nèi)容和元數(shù)據(jù)嘴秸,保存在BeanFactory當(dāng)中毁欣,包裝Bean的實體:
public class BeanDefinition {
    //真正的Bean實例
    private Object bean;
    //Bean的類型信息
    private Class beanClass;
    //Bean類型信息的名字
    private String beanClassName;
    //用于bean的屬性注入 因為Bean可能有很多熟屬性
    //所以這里用列表來進(jìn)行管理
    private PropertyValues propertyValues = new PropertyValues();
}
  • PerpertyValues存放Bean的所有屬性
public class PropertyValues {
    private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
}
  • PropertyValue存放的是每個屬性,可以看到兩個字段岳掐,name和valu凭疮。name存放的就是屬性名稱,value是object類型串述,可以是任何類型
public class PropertyValue {
    private final String name;
    private final Object value;
}

定義好這些數(shù)據(jù)結(jié)構(gòu)了执解,把Bean裝進(jìn)容器的過程,其實就是其BeanDefinition構(gòu)造的過程,那么怎么把一些類裝入的Spring容器呢衰腌?

  • Spring有個接口就是獲取某個資源的輸入流新蟆,獲取這個輸入流后就可以進(jìn)一步處理了:
public interface Resource {
    InputStream getInputStream() throws IOException;
}
  • UrlResource 是對Resource功能的進(jìn)一步擴(kuò)展,通過拿到一個URL獲取輸入流右蕊。
public class UrlResource implements Resource {
    private final URL url;
    public UrlResource(URL url) {
        this.url = url;
    }
    @Override
    public InputStream getInputStream() throws IOException{
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        return urlConnection.getInputStream();
    }
  • ResourceLoader是資源加載的主要方法琼稻,通過location定位Resource,
    然后通過上面的UrlResource獲取輸入流:
public class ResourceLoader {
    public Resource getResource(String location){
        URL resource = this.getClass().getClassLoader().getResource(location);
        return new UrlResource(resource);
    }
}
  • 大家可能會對上面的這段代碼產(chǎn)生疑問:
     URL resource = this.getClass().getClassLoader().getResource(location);

為什么通過得到一個類的類類型饶囚,然后得到對應(yīng)的類加載器帕翻,然后調(diào)用類加載器的Reource怎么就得到了URL這種類型呢?
我們來看一下類加載器的這個方法:

 public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

從這個方法中我們可以看出萝风,類加載器去加載資源的時候嘀掸,先會去讓父類加載器去加載,如果父類加載器沒有的話规惰,會讓根加載器去加載睬塌,如果這兩個都沒有加載成功,那就自己嘗試去加載歇万,這個一方面為了java程序的安全性衫仑,不可能你用戶自己隨便寫一個加載器,就用你用戶的堕花。

  • 接下來我們看一下重要角色文狱,這個是加載BeanDefinition用的。
public interface BeanDefinitionReader {
    void loadBeanDefinitions(String location) throws Exception;
}
  • 這個接口是用來從配置中讀取BeanDefinition:
    其中registry key是bean的id缘挽,value存放資源中所有的BeanDefinition瞄崇,
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {

    private Map<String,BeanDefinition> registry;

    private ResourceLoader resourceLoader;

    protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
        this.registry = new HashMap<String, BeanDefinition>();
        this.resourceLoader = resourceLoader;
    }

    public Map<String, BeanDefinition> getRegistry() {
        return registry;
    }

    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }
}
  • 最后我們來看一個通過讀取Xml文件的BeanDefinitionReader:
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    /**
     * 構(gòu)造函數(shù) 傳入我們之前分析過的ResourceLoader 這個通過
     * location 可以 加載到Resource
     */
    public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }

    /**
     * 這個方法其實是BeanDefinitionReader這個接口中的方法
     * 作用就是通過location來構(gòu)造BeanDefinition
     */
    @Override
    public void loadBeanDefinitions(String location) throws Exception {
        //把location傳給ResourceLoader拿到Resource,然后獲取輸入流
        InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
        //接下來進(jìn)行輸入流的處理
        doLoadBeanDefinitions(inputStream);
    }

    protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
        //因為xml是文檔對象懈糯,所以下面進(jìn)行一些處理文檔工具的構(gòu)造
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        //把輸入流解析成一個文檔,java可以處理的文檔
        Document doc = docBuilder.parse(inputStream);
        // 處理這個文檔對象 也就是解析bean
        registerBeanDefinitions(doc);
        inputStream.close();
    }

    public void registerBeanDefinitions(Document doc) {
        //得到文檔的根節(jié)點(diǎn),知道根節(jié)點(diǎn)后獲取子節(jié)點(diǎn)就是通過層級關(guān)系處理就行了
        Element root = doc.getDocumentElement();
        //解析根節(jié)點(diǎn) xml的根節(jié)點(diǎn)
        parseBeanDefinitions(root);
    }

    protected void parseBeanDefinitions(Element root) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            //element有屬性的包裝
            if (node instanceof Element) {
                Element ele = (Element) node;
                processBeanDefinition(ele);
            }
        }
    }

    protected void processBeanDefinition(Element ele) {
        /**
         *  <bean id="object***" class="com.***.***"/>
         */
        //獲取element的id
        String name = ele.getAttribute("id");
        //獲取element的class
        String className = ele.getAttribute("class");
        BeanDefinition beanDefinition = new BeanDefinition();
        //處理這個bean的屬性
        processProperty(ele, beanDefinition);
        //設(shè)置BeanDefinition的類名稱
        beanDefinition.setBeanClassName(className);
        //registry是一個map谤逼,存放所有的beanDefinition
        getRegistry().put(name, beanDefinition);
    }



    private void processProperty(Element ele, BeanDefinition beanDefinition) {
        /**
         *類似這種:
         <bean id="userServiceImpl" class="com.serviceImpl.UserServiceImpl">
         <property name="userDao" ref="userDaoImpl"> </property>
         </bean>
         */
        NodeList propertyNode = ele.getElementsByTagName("property");
        for (int i = 0; i < propertyNode.getLength(); i++) {
            Node node = propertyNode.item(i);
            if (node instanceof Element) {
                Element propertyEle = (Element) node;
                //獲得屬性的名稱
                String name = propertyEle.getAttribute("name");
                //獲取屬性的值
                String value = propertyEle.getAttribute("value");
                if (value != null && value.length() > 0) {
                    //設(shè)置這個bean對應(yīng)definition里的屬性值
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
                } else {
                    //value是Reference的話  就會進(jìn)入到這里處理
                    String ref = propertyEle.getAttribute("ref");
                    if (ref == null || ref.length() == 0) {
                        throw new IllegalArgumentException("Configuration problem: <property> element for property '"
                                + name + "' must specify a ref or value");
                    }
                    //構(gòu)造一個BeanReference 然后把這個引用方到屬性list里
                    BeanReference beanReference = new BeanReference(ref);
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
                }
            }
        }
    }
}

  • 上面用到了BeanReference(如下),其實這個和PropertyValue類似欲诺,用不同的類型是為了更好的區(qū)分:
public class BeanReference {
     private String name;
     private Object bean;
}

好了腮郊,到現(xiàn)在我們已經(jīng)分析完了Spring是如何找到Bean并加載進(jìn)入Spring容器的摹蘑,這里面最主要的數(shù)據(jù)結(jié)構(gòu)就是BeanDefinition,ReourceLoader來完成資源的定位轧飞,讀入衅鹿,然后獲取輸入流,進(jìn)一步的處理过咬,這個過程中有對xml文檔的解析和對屬性的填充大渤。
 由于自己能力有限,有些地方表述的不準(zhǔn)確掸绞,還請大家諒解~~,

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泵三,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烫幕,老刑警劉巖俺抽,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異较曼,居然都是意外死亡凌埂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門诗芜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞳抓,“玉大人,你說我怎么就攤上這事伏恐『⒀疲” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵翠桦,是天一觀的道長横蜒。 經(jīng)常有香客問我,道長销凑,這世上最難降的妖魔是什么丛晌? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮斗幼,結(jié)果婚禮上澎蛛,老公的妹妹穿的比我還像新娘。我一直安慰自己蜕窿,他們只是感情好谋逻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桐经,像睡著了一般毁兆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阴挣,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天气堕,我揣著相機(jī)與錄音,去河邊找鬼畔咧。 笑死茎芭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盒卸。 我是一名探鬼主播骗爆,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蔽介!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤虹蓄,失蹤者是張志新(化名)和其女友劉穎犀呼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薇组,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡外臂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了律胀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宋光。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖炭菌,靈堂內(nèi)的尸體忽然破棺而出罪佳,到底是詐尸還是另有隱情,我是刑警寧澤黑低,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布赘艳,位于F島的核電站,受9級特大地震影響克握,放射性物質(zhì)發(fā)生泄漏蕾管。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一菩暗、第九天 我趴在偏房一處隱蔽的房頂上張望掰曾。 院中可真熱鬧,春花似錦停团、人聲如沸婴梧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塞蹭。三九已至,卻和暖如春讶坯,著一層夾襖步出監(jiān)牢的瞬間番电,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工辆琅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漱办,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓婉烟,卻偏偏與公主長得像娩井,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子似袁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容