假如我們在寫(xiě)一個(gè)基于Spring的普通應用程序,不管我們用了多么精妙的設計模式,進(jìn)行了如何巧妙的設計,我們必須在某個(gè)地方執行這樣的代碼:
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
new String[] {"applicationContext.xml", "applicationContext-part2.xml"});
appContext.getBean("…");
也許這樣的代碼算不上丑陋,但是它無(wú)疑破壞了程序的純潔性和透明性。我們的應用程序開(kāi)始顯式地依賴(lài)SpringFramework,我們必須清楚地知道Spring的配置文件有哪幾個(gè),每個(gè)配置文件的加入或修改源代碼,我們必須在某些代碼模塊里調用丑陋的getBean方法來(lái)創(chuàng )造對象。
但是所有的這些丑陋的事情似乎在我們的Web應用程序里消失啦,所有的代碼都是那么干凈,只有簡(jiǎn)單的get與set及接口之間的調用,我們不需要知道ApplicationContext,我們甚至不需要知道Spring。但是我們所有的對象卻又是通過(guò)Spring的ApplicationContext來(lái)創(chuàng )造的!
看上去似乎很神奇,但是假如我們稍微思考一下,就會(huì )發(fā)現這是一件合情合理又如此簡(jiǎn)單的事情,呵呵,只有第一個(gè)想到這個(gè)方法的人才是偉大的。讓我們仔細想一下普通應用程序和Web應用程序的最大區別在哪里?
其實(shí)真正的區別只有一個(gè),普通應用程序是一個(gè)主動(dòng)執行的程序,而Web應用程序卻是被動(dòng)的組件。這意味著(zhù)Web應用程序無(wú)法自己主動(dòng)去生成自己的線(xiàn)程去執行某項任務(wù),而必須借用Web容器中的一個(gè)線(xiàn)程。想象一下一個(gè)簡(jiǎn)單的任務(wù):我們想每隔一段時(shí)間執行一個(gè)任務(wù),比如說(shuō)在Console里打印出一行文字。在我們的Web應用程序里應該怎么完成?在我不知道Servlet Listener或Spring里提供的Schedule之前(其實(shí)Spring就是利用Servlet Listner初始化Application Context時(shí)啟動(dòng)schedule的),這么簡(jiǎn)單的任務(wù)在一個(gè)Web應用程序里竟然是不可想象。還記得我當時(shí)采用的是最傻的做法:寫(xiě)了一個(gè)單獨的應用程序,在這應用程序的main函數里啟動(dòng)了timetask。
但是如果換一種角度來(lái)看,整個(gè)Web應用程序生活在容器里也給我們帶來(lái)了額外的好處,當我們讓出了對應用程序的控制權之后,我們可以讓容器幫我們完成很多本來(lái)很難處理的事情。其實(shí)IOC容器的真正作用也在于此,當我們把我們的對象創(chuàng )建工作移交給IOC容器之后,我們發(fā)現整個(gè)程序變得如此清晰,如此透明,對象之間的關(guān)聯(lián)、哪些類(lèi)需要事務(wù)處理或AOP功能、哪些類(lèi)要遠程訪(fǎng)問(wèn),所有這些復雜的事情在我們的程序里都不見(jiàn)了,我們只看到了簡(jiǎn)單的get和set。
也許廢話(huà)太多,但我覺(jué)得經(jīng)過(guò)這樣分析,其實(shí)ApplicationContext之謎已經(jīng)不再是謎了。真正的關(guān)鍵在于當我們的Web應用程序是被動(dòng)的組件時(shí),它除了可以錯用容器的線(xiàn)程之外還可以錯用其它一些東西。我們可以讓容器來(lái)幫我們創(chuàng )建ApplicationContext,然后把它放在某個(gè)地方,然后在需要使用時(shí)讓容器從這個(gè)地方把ApplicationContext讀出來(lái),并執行相應的Controller就可以了。
這個(gè)"某個(gè)地方"就是ServletContext,而這個(gè)創(chuàng )建ApplicationContext的地方就是Servlet Listner,而取到ApplicationContext的地方是我們的DispatcherServlet。
仔細想一下,其實(shí)Web服務(wù)器并沒(méi)有什么了不起的地方,它只是一個(gè)Java程序,它只是會(huì )在啟動(dòng)的時(shí)候去ClassLoad某些指定文件夾下的lib或classes,它會(huì )讀某個(gè)在WEB-INF下面一個(gè)叫做web.xml的配置文件,再做一些初始化工作。Servlet Listener就是這個(gè)初始化工作的重要一步,服務(wù)器會(huì )讀出web.xml里配置好的所有listner,然后調用每個(gè)Listner的contextInitialized方法(它還會(huì )去調每個(gè)Servlet的init方法,不過(guò)把初始化方法寫(xiě)在Listner里才是天經(jīng)地義的)。哈哈,這也正是Spring MVC創(chuàng )建ApplicationContext的最好時(shí)機,當我們在web.xml里配置好ContextLoaderListener的時(shí)候,Spring就完成了ApplicationContext的創(chuàng )建過(guò)程,如果有人想研究源代碼的話(huà)可以去看一下,不過(guò)這個(gè)創(chuàng )建過(guò)程并不象想象中的那么有趣,只是通過(guò)Class.forName和BeanUtils.instantiateClass創(chuàng )建出一個(gè)WebApplicationContext,然后再讀了一下IOC容器的配置文件。
接下來(lái)的一個(gè)問(wèn)題是我們要把創(chuàng )建的ApplicationContext放在哪里?答案是ServletContext,其實(shí)沒(méi)必須對ServletContext進(jìn)行深究,它只是可以一個(gè)可以全局存放Web應用程序的場(chǎng)所,我們只要想象成一個(gè)全局的HashMap就可以了,我們可以要把它put進(jìn)去,就可以在Servlet或其它地方把它get出來(lái)。
Web服務(wù)器還要干的一件事件當然是在某個(gè)request到來(lái)時(shí),它會(huì )啟動(dòng)一個(gè)單獨的線(xiàn)程(這也是為何Webwork可以把Context放到ThreadLocal里的原因),根據web.xml里的配置和request的URI匹配去執行相應的Servlet。由于Servlet可以很輕松地讀到ServletContext,當然也可以很輕松地讀到ApplicationContext啦。接下來(lái)的事情就比想象中要簡(jiǎn)單啦,經(jīng)過(guò)一些準備工作之后ApplicationContext中的URLMapping里配置好的某個(gè)Controller,執行一下再rend某個(gè)view就可以了。其實(shí)struts或webwork2的執行過(guò)程也是如此,所以MVC framwork分析透了其實(shí)真沒(méi)什么了不起,遠比O/R Mapping或其它的framework簡(jiǎn)單。雖然MVC的執行過(guò)程如此簡(jiǎn)單,但是我們還需要了解一些細節上的事件,所以讓我們下次來(lái)討論一下Spring MVC framework的執行過(guò)程吧。
聯(lián)系客服