|
其實(shí)每個(gè)MVC framework的執(zhí)行過(guò)程都是大同小異的,當(dāng)個(gè)request過(guò)來(lái)時(shí),它都通過(guò)一個(gè)Servlet來(lái)響應(yīng)request,再根據(jù)request的路徑名和配置將這個(gè)request dispatch給一個(gè)Controller執(zhí)行,最后將之返回配置文件里對(duì)應(yīng)的頁(yè)面。在Spring MVC里,這個(gè)Servlet的名字叫DispatchServlet。稍看一下它的源碼會(huì)發(fā)現(xiàn)這是一很簡(jiǎn)單的類。
下面是DispatchServlet的類圖:
簡(jiǎn)單吧,這是典型的Template Method模式。每個(gè)類都會(huì)完成一些自己的本職工作,把不屬于自己的工作延遲到子類來(lái)完成。這些類的子職責(zé)在下面會(huì)有分析。其實(shí)整個(gè)SpringFramework用的最多的模式就是Template Method(Strategy也挺多,呵呵),也許任何Framework用的最多的都是Template Method模式。Why?看看Expert One on One J2EE Design and Development吧,至少Template Method和Strategy模式的分析這本書(shū)甚至比Head first Design Pattern還好。 我們先來(lái)看DispatchServlet的初始化執(zhí)行過(guò)程分析吧: 我們知道每個(gè)Servlet在Web服務(wù)器啟動(dòng)時(shí)都會(huì)有一個(gè)初始化的機(jī)會(huì),這就是Servlet的init過(guò)程,這是配置Servlet的最好機(jī)會(huì)。我們可以在這個(gè)階段干些啥事情呢? 1、把初始化的那些init-param讀到Servlet的屬性里。我們知道Servlet的init-param是放在ServletConfig里的,我們可以用循環(huán)去取這些屬性。但是每次都這么干實(shí)在太累了,干嗎不把在Servlet里增加幾個(gè)property,再這些init-param直放到Servlet的property里呢?呵呵,以后那些初始參數(shù)都可以直接拿來(lái)用啦,真方便。DispatchServlet的一個(gè)祖先類叫做HttpServletBean就是專門(mén)干這個(gè)的。以后假如我們要寫(xiě)自己的Servlet也可以直接繼承HttpServletBean這個(gè)類,這樣讀ServletConfig的操作都省掉了,哈哈! 2、從ServletContext里取出ApplicationContext,并擴(kuò)展成自己的ApplicationContext. 在ApplicationContext之謎里我們已經(jīng)提到我們可以用Servlet Listner執(zhí)行的機(jī)會(huì)把ApplicationContext放到ServletContext中去。但是不是直接拿這個(gè)ApplicationContext就足夠了呢?no。我們先問(wèn)一個(gè)簡(jiǎn)單的問(wèn)題吧:在Spring MVC里我們是不是只能配置一個(gè)Servlet呢?Struts就是那么干的,所以在Struts里所有的request過(guò)來(lái)都會(huì)交給一個(gè)Servlet去處理。但是在Spring MVC里,我們卻可以有好多個(gè)Servlet!它們可以處理不同類型的request,而且更重要的是它們的ApplicationContext不是相同的,它們共享了一個(gè)父ApplicationContext,也就是從ServletContext里取出來(lái)的那個(gè),但是它們卻會(huì)根據(jù)自己的配置作擴(kuò)展,形成這個(gè)Servlet特有的ApplicationContext。這個(gè)子的ApplicationContext里有自己的namespace,也就是將一個(gè)叫做(假如servlet名稱叫xiecc) xiecc-servlet.xml的配置文件讀進(jìn)來(lái),行成一個(gè)自己的ServletContext。所以這些過(guò)程全是在DispatchSevlet的一個(gè)父類FrameworkServlet里干的。 3、初始化接來(lái)要干的一件最重要的事情是初始DispatchServlet里的接口。如果用IOC容器的角度來(lái)說(shuō),其實(shí)是將ApplicationContext里定義好的接口注入到一個(gè)叫做DispatchServlet的bean里,只不過(guò)這個(gè)注入過(guò)程是手動(dòng)的。注入的代碼大致如果下(部分角色的含義和作用以后會(huì)解釋): initMultipartResolver(); initLocaleResolver(); initThemeResolver(); initHandlerMappings(); initHandlerAdapters(); initHandlerExceptionResolvers(); initViewResolvers(); 每個(gè)init方法其實(shí)是屬性設(shè)置的過(guò)程,因?yàn)槲覀兛梢阅玫阶约旱?/span>ApplicationContext,所以這一切都變得很輕松啦。比如說(shuō)multipartResolver,直接用getWebApplicationContext().getBean(MULTIPART_RESOLVER_BEAN_NAME);就能拿到了。 不過(guò)很多東西在配置文件里是不需要寫(xiě)的,比如說(shuō)multipartResolver,如果在xml里取不到,Spring會(huì)初始化默認(rèn)的MultipartResolver。
接下來(lái)我們分析一下DispatchServlet怎么處理Request的執(zhí)行流程吧: 看一下DispatchServlet的源碼會(huì)發(fā)現(xiàn)它出奇的簡(jiǎn)單。簡(jiǎn)單的原因是類Template Method的Strategy模式,呵呵,這是我取的一個(gè)怪名字。因?yàn)?/span>DispatchServlet是不負(fù)責(zé)任何具體的操作的,它將具體的操作都delegate給了相應(yīng)的接口,這是典型的Strategy模式。但是DispatchServlet里的doDispatch方法卻控制了執(zhí)行流程,所有的request過(guò)來(lái),我們都會(huì)按這樣的一個(gè)流程處理,這和Template Method里的由父類控制流程不謀而合。所以它仍然是Strategy模式,只不過(guò)主類還多了個(gè)控制流程的職責(zé)。 所以DispatchServlet本身的doService方法只是負(fù)責(zé)控制執(zhí)行流程,而將所有具體的實(shí)現(xiàn)細(xì)節(jié)全都delegate給相應(yīng)接口,難怪會(huì)那么簡(jiǎn)單。整個(gè)流程也異常的簡(jiǎn)單,我們甚至找不到循環(huán)跳轉(zhuǎn),所有的東西都是一直線下來(lái)的,撇開(kāi)一些細(xì)節(jié)不說(shuō),剩下的就是這以幾步了:
OK.是不是寫(xiě)得很亂?我自己都覺(jué)得慚愧啦,沒(méi)辦法,只好讓我們?cè)倩仡^分析一下我們碰到幾個(gè)角色吧:
1、HandlerMapping HandlerMapping這個(gè)接口的定義非常簡(jiǎn)單: public interface HandlerMapping { HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; } 不就是根據(jù)request的URL path來(lái)取得相應(yīng)的HandlerExecutionChain。 例如:我的URL是 http://localhost:8080/blog/xiecc.htm RequestURL的字符串一截,拿到了”/xiecc.htm”,再去每個(gè)HandlerMapping里一查(還記得初始化時(shí)我們已經(jīng)將所有的HandlerMapping都從配置文件里注入進(jìn)來(lái)了吧),假如我們?cè)谀硞€(gè)SimpleUrlHandlerMapping里找到了”/xiecc.htm”,我就立刻可以拿到它對(duì)應(yīng)Controller和一組intercetpors了,拿過(guò)來(lái)組裝一下就是一個(gè)HandlerExecutionChain啦。 下面是HandlerMapping的類圖: 看上去有點(diǎn)麻煩,其實(shí)挺簡(jiǎn)單,具體的類我就不分析啦。它的核心是:it’s all about HashMap。 還記得我們?cè)?/span>Spring MVC最常用的HandlerMapping嗎?是SimpleUrlHandlerMapping,我們?cè)谂渲盟臅r(shí)候,最核心的結(jié)構(gòu)就是HashMap,哈哈!東西都在HashMap里,只要通過(guò)URL分析找到HashMap的key,比如說(shuō)”/xiecc.htm”,用個(gè)get方法不就啥都取到了。 在Spring的配置文件里我們可以配置多個(gè)HandlerMapping,它會(huì)一個(gè)一個(gè)去找到的,直到找到跟URL匹配的那個(gè)Controller,要不然就返回null啦。
2、HandlerExecutionChain 我前面說(shuō)了HandlerExecutionChain就是一個(gè)Controller和一組interceptors。這是我們執(zhí)行一個(gè)request最基本的單元啦。 不過(guò)現(xiàn)實(shí)情況會(huì)稍有些出入,HandlerExecutionChain實(shí)際上包括的一個(gè)Object和一組interceptor。這個(gè)Object是Adaptor,它可以是Controller的Adaptor,也可以是其它類的Adaptor。但現(xiàn)實(shí)中我們一般用到的都是Controller,因此不詳細(xì)分析啦,這里用了Adaptor后大大降低了代碼的可讀性,來(lái)?yè)Q取與Controller非緊耦合的靈活性。至少我現(xiàn)在認(rèn)為這樣做不是太值。
3、Controller Controller是Spring MVC執(zhí)行的核心單元,也是程序員需要自完成的重要部分。用過(guò)Spring MVC的人應(yīng)該都對(duì)它非常熟悉啦。所以不做太具體的分析。以下是它的類圖:
看一下類圖就知道啦,這又是Template Method的典型應(yīng)用。Controller最大的優(yōu)勢(shì)也正是利用Template Method,把Controller分解成不同功能的子類。想要把request里的東西populate到一個(gè)bean里嗎?直接繼承SimpleFormController就行啦。想要在Controller里寫(xiě)多個(gè)方法嗎?用MultiActionController。這些Controller設(shè)計(jì)得面面俱到,但因?yàn)?/span>Controller的類層次太多,有的人會(huì)覺(jué)得煩。呵呵,隨個(gè)人喜好啦。 不過(guò)最核心的是不管這些Controller如何千變?nèi)f化,它們都實(shí)現(xiàn)了統(tǒng)一的Controller接口,這使DispatchAction調(diào)它時(shí)候根據(jù)不需要知道Controller的細(xì)節(jié),嗯,the power of interface。 Controller里的數(shù)據(jù)綁定也是一個(gè)值得研究的東西,挺好玩的,不過(guò)這次沒(méi)空寫(xiě)啦。
4、interceptor Interceptor的接口定義如下: public interface HandlerInterceptor { boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception; void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; } 具體的我就不展開(kāi)分析啦,我們只要記住interceptor的三個(gè)hook point(在AOP里叫join point,哈哈):Controller執(zhí)行前,Controller執(zhí)行后,頁(yè)面顯示完成后。
5、ViewResolver ViewResolver是一個(gè)有趣的角色,它本身完成兩個(gè)功能:一是完成了View與實(shí)際頁(yè)面名稱對(duì)應(yīng)關(guān)系的配置,二是View的工廠(這可是標(biāo)準(zhǔn)的工廠模式啊,每個(gè)ViewResolver負(fù)責(zé)生產(chǎn)自己的View)。 以下是ViewResolver接口的定義: public interface ViewResolver { View resolveViewName(String viewName, Locale locale) throws Exception; } 用過(guò)Spring MVC的人都配置過(guò)ViewResolver,因此這里不詳細(xì)展開(kāi)。 我將它對(duì)屬性分成兩類:一類是頁(yè)面文件配置,包括prefix, suffix;另一類是作為view的工廠注入到View里的屬性,如ContentType之類的。 以下是ViewResolver的類圖:
6、View View是真正負(fù)責(zé)顯示頁(yè)面的地方。它的接口如下: public interface View { void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception; } 其實(shí)這是一個(gè)簡(jiǎn)單任務(wù),給我一個(gè)HashMap,再給個(gè)頁(yè)面URI,把這個(gè)頁(yè)面顯示出來(lái)還不是小Case。不過(guò)不同的View顯示到頁(yè)面上的區(qū)別還是挺大的,如果是JSP,我只要把HashMap里的東西填到request里,再交給RequestDispatcher來(lái)forward一下就行了;如果是Velocity,那就把HashMap里的東西填到Veloticy的Context里,再把模板生成的東西merge到response的writer里就完事了。當(dāng)然還有pdf或xls的View,我還沒(méi)空研究它,哪天有興趣了再看看吧,以下是View的類圖:
寫(xiě)完這篇文章后,終于明白了什么叫做眼高手低。本來(lái)很希望寫(xiě)得抽象些,最后卻發(fā)現(xiàn)我寫(xiě)的東西跟一般的流水帳式的源碼分析其實(shí)區(qū)別不太大。嗚嗚,從具體到抽象很難,再把抽象的東西轉(zhuǎn)化成具體更難,但只有經(jīng)過(guò)這樣的一層轉(zhuǎn)換,我們的認(rèn)識(shí)才能有很大的提高,我們的水平才能進(jìn)步。 |
|
|