Spring源碼分析系列(一)IOC容器的設(shè)計(jì)與實(shí)現(xiàn)(2)高級(jí)容器的實(shí)現(xiàn)

上期文章:
Spring源碼分析系列(一)IOC容器的設(shè)計(jì)與實(shí)現(xiàn)(1)基礎(chǔ)容器的實(shí)現(xiàn)

上一章我們講了以xmlBeanFactory的方式實(shí)現(xiàn)的基礎(chǔ)IOC容器,這回我們接著講IOC容器的高級(jí)實(shí)現(xiàn)突梦。首先先看下ApplicationContext的接口設(shè)計(jì)路線,他在繼承了BeanFactory的基礎(chǔ)上還集成了國(guó)際化接口MessageSource捞烟、資源獲取ResourceLoader卖擅、事件機(jī)制ApplicationEventPublisher等,通過他們來實(shí)現(xiàn)基礎(chǔ)容器做不到的高級(jí)特性静稻。


applicationContext接口設(shè)計(jì)路線

下面我們來看三段代碼

//通過實(shí)際路徑
String xmlPath = "WebContent/WEB-INF/config/base/applicationContext.xml";
    ApplicationContext ac = new FileSystemXmlApplicationContext(xmlPath);
    user = ac.getBean(test.class);
//通過根目錄默認(rèn)
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    user = ac.getBean(LsjmUserServiceImpl.class);
//通過xmlwebApplication
ServletContext servletContext =request.getSession().getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);

這三種方式都可以獲取到ApplicationContext對(duì)象脚作,我們以FileSystemXmlApplicationContext舉例葫哗。


FileSystemXmlApplicationContext整體結(jié)構(gòu).png

首先是初始化,執(zhí)行構(gòu)造方法,然后按照繼承鏈進(jìn)行父類初始化劣针,最后refresh啟動(dòng)桨螺。
繼承鏈上的父類包括:
AbstractXmlApplicationContext
AbstractRefreshableConfigApplicationContext
AbstractRefreshableApplicationContext
AbstractApplicationContext

 public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[]{configLocation}, true, (ApplicationContext)null);
    }
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
        super(parent);
        this.setConfigLocations(configLocations);
        if (refresh) {
            this.refresh();
        }
    }

我們繼續(xù)跟進(jìn)refresh方法,在AbstractApplicationContext類中看到了他的實(shí)現(xiàn)酿秸,這里是容器的準(zhǔn)備階段,調(diào)用了ConfigurableListableBeanFactory接口魏烫,那誰是他的實(shí)現(xiàn)類呢辣苏?我們接著往下走。

public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);
        }
    }
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        this.refreshBeanFactory();
        return this.getBeanFactory();
    }

我們通過refreshBeanFactory繼續(xù)執(zhí)行哄褒,通過AbstractApplicationContext的繼承體系稀蟋,我們?cè)贏bstractRefreshableApplicationContext類中找到了他的實(shí)現(xiàn)方法∧派模看他的邏輯退客,先容器校驗(yàn)是否創(chuàng)建,存在就銷毀链嘀。然后我們還是通過構(gòu)建實(shí)體類DefaultListableBeanFactory來實(shí)現(xiàn)容器萌狂,這個(gè)我們后續(xù)會(huì)講解如何實(shí)現(xiàn)。

通過這段代碼怀泊,我們發(fā)現(xiàn):
無論是BeanFactory體系的基礎(chǔ)容器茫藏,還是ApplicationContext體系的高級(jí)容器,我們是要通過DefaultListableBeanFactory來實(shí)現(xiàn)霹琼。

  protected final void refreshBeanFactory() throws Exception {
        if (this.hasBeanFactory()) {
            this.destroyBeans();
            this.closeBeanFactory();
        }
            DefaultListableBeanFactory beanFactory = this.createBeanFactory();
            beanFactory.setSerializationId(this.getId());
            this.customizeBeanFactory(beanFactory);
            this.loadBeanDefinitions(beanFactory);
            synchronized(this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
    }
ConfigurableListableBeanFactory唯一實(shí)現(xiàn)類就是DefaultListableBeanFactory.png

根據(jù)基礎(chǔ)容器的構(gòu)建方式可知务傲,要實(shí)現(xiàn)容器首先要將xml中的結(jié)構(gòu),解析出來裝載到CurrentHashMap<BeanDefinition>對(duì)象中枣申,所以這里的思路是如何定位xml售葡。追蹤抽象方法this.loadBeanDefinitions(beanFactory),我們找到了他的實(shí)現(xiàn)類AbstractXmlApplicationContext忠藤,這里定義了兩種資源類型的加載方式我們按照String類型繼續(xù)跟代碼挟伙。

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = this.getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        String[] configLocations = this.getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }

進(jìn)入到AbstractBeanDefinitionReader類中,然后獲取資源加載器ResourceLoader根據(jù)ResourceLoader的類型不同對(duì)資源有不同的獲取方法當(dāng)為ResourcePatternResolver類型時(shí)熄驼,將ResourceLoader強(qiáng)轉(zhuǎn)類型后調(diào)用getResource()方法定位資源否則像寒,直接調(diào)用DefaultResourceLoader的getResource()方法定位資源。


    public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = this.getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
        } else {
            int count;
            if (resourceLoader instanceof ResourcePatternResolver) {
                    Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
                    count = this.loadBeanDefinitions(resources);
                    if (actualResources != null) {
                        Collections.addAll(actualResources, resources);
                    }
                    return count;
            } else {
                Resource resource = resourceLoader.getResource(location);
                count = this.loadBeanDefinitions((Resource)resource);
                if (actualResources != null) {
                    actualResources.add(resource);
                }
                }
                return count;
            }
        }
    }

進(jìn)入到DefaultResourceLoader類瓜贾,getResource里會(huì)判斷傳入的路徑類型诺祸,是根目錄標(biāo)識(shí)的定位,還是Url標(biāo)識(shí)的定位祭芦,還是是實(shí)際路徑標(biāo)識(shí)的定位筷笨。FileSystemXmlApplicationContext屬于實(shí)際路徑所以我們繼續(xù)執(zhí)行g(shù)etResourceByPath方法。

public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        Iterator var2 = this.protocolResolvers.iterator();
        Resource resource;
        do {
            if (!var2.hasNext()) {
                if (location.startsWith("/")) {
                    return this.getResourceByPath(location);
                }
                if (location.startsWith("classpath:")) {
                    return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
                }
                try {
                    URL url = new URL(location);
                    return (Resource)(ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
                } catch (MalformedURLException var5) {
                    return this.getResourceByPath(location);
                }
            }
            ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
            resource = protocolResolver.resolve(location, this);
        } while(resource == null);
        return resource;
    }

getResourceByPath的實(shí)現(xiàn)類就在我們最開始的入口FileSystemXmlApplicationContext類中,這個(gè)方法會(huì)返回一個(gè)FileSystemResource對(duì)象胃夏,通過這個(gè)對(duì)象Spring就可以進(jìn)行I/O操作完成BeanDefinition定位轴或。

 protected Resource getResourceByPath(String path) {
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemResource(path);
    }

經(jīng)過上述操作我們完成了配置資源的定位工作,接下就是BeanDefinition的載入和解析仰禀。
讓我們回到AbstractBeanDefinitionReader類中的loadBeanDefinitions(String , Set<Resource>)方法上,getResource使我們獲得了資源的坐標(biāo)照雁,然后我們執(zhí)行this.loadBeanDefinitions,這里開始正式開始BeanDefinition的載入和解析答恶。

 Resource resource = resourceLoader.getResource(location);
 count = this.loadBeanDefinitions((Resource)resource);

繼續(xù)追蹤代碼在BeanDefinitionReader接口中我們按照xml解析的方式選擇XmlBeanDefinitionReader作為實(shí)現(xiàn)類饺蚊,這里將resource對(duì)象轉(zhuǎn)換為流并通過doLoadBeanDefinitions方法進(jìn)行讀取。具體的讀取過程都在doLoadDocument中悬嗓,最后將讀取的結(jié)果放入Document對(duì)象中并開始注冊(cè)污呼。

public int loadBeanDefinitions(EncodedResource encodedResource) {
InputStream inputStream = encodedResource.getResource().getInputStream();
InputSource inputSource = new InputSource(inputStream);
    if (encodedResource.getEncoding() != null) {
      inputSource.setEncoding(encodedResource.getEncoding());
    }
    var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}

  protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) {
          // 具體的讀取過程都在doLoadDocument中
            Document doc = this.doLoadDocument(inputSource, resource);
            int count = this.registerBeanDefinitions(doc, resource);
            return count;
    }

我們繼續(xù)跟進(jìn)代碼registerBeanDefinitions,我們發(fā)現(xiàn)registerBeanDefinitions真正的實(shí)現(xiàn)類又回到了我們?cè)谧?cè)基礎(chǔ)容器時(shí)用到的DefaultBeanDefinitionDocumentReader類包竹。后續(xù)的注冊(cè)過程就不在敘述燕酷,參考基礎(chǔ)容器的實(shí)現(xiàn),高級(jí)容器與基礎(chǔ)容器的的核心本質(zhì)上都是一套東西周瞎。
最后關(guān)于AppliicationContext的高級(jí)特性部分苗缩,我們往回找,找到AbstractApplicationContext看refresh方法堰氓。在準(zhǔn)備好基礎(chǔ)的IOC容器后挤渐,我們開始逐步注冊(cè)那些高級(jí)特性。

   public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            // 啟動(dòng)注冊(cè)ioc容器
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

                
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                // 初始化消息
                this.initMessageSource();
                // 初始化時(shí)間機(jī)制
                this.initApplicationEventMulticaster();
                this.onRefresh();
                //注冊(cè)監(jiān)聽器
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載双絮,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者浴麻。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市囤攀,隨后出現(xiàn)的幾起案子软免,更是在濱河造成了極大的恐慌,老刑警劉巖焚挠,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膏萧,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蝌衔,警方通過查閱死者的電腦和手機(jī)榛泛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來噩斟,“玉大人曹锨,你說我怎么就攤上這事√暝剩” “怎么了沛简?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵齐鲤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我椒楣,道長(zhǎng)给郊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任捧灰,我火速辦了婚禮淆九,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘毛俏。我一直安慰自己吩屹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布拧抖。 她就那樣靜靜地躺著,像睡著了一般免绿。 火紅的嫁衣襯著肌膚如雪唧席。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天嘲驾,我揣著相機(jī)與錄音淌哟,去河邊找鬼。 笑死辽故,一個(gè)胖子當(dāng)著我的面吹牛徒仓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播誊垢,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼掉弛,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了喂走?” 一聲冷哼從身側(cè)響起殃饿,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芋肠,沒想到半個(gè)月后乎芳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帖池,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年奈惑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睡汹。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肴甸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出帮孔,到底是詐尸還是另有隱情雷滋,我是刑警寧澤不撑,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站晤斩,受9級(jí)特大地震影響焕檬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜澳泵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一实愚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兔辅,春花似錦腊敲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至介时,卻和暖如春没宾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沸柔。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工循衰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人褐澎。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓会钝,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親工三。 傳聞我的和親對(duì)象是個(gè)殘疾皇子迁酸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355