SpringMVC深度探险(二) —— SpringMVC概览
本文是專欄文章(SpringMVC深度探險)系列的文章之一,博客地址為:http://downpour.iteye.com/blog/1330596。
對于任何事物的研究,總是由表及里、由淺入深地進(jìn)行。在本系列的第二篇文章中,我們將通過不同的觀察視角,對SpringMVC做一些概要性的分析,幫助大家了解SpringMVC的基本構(gòu)成要素、SpringMVC的發(fā)展歷程以及SpringMVC的設(shè)計原則。
SpringMVC的構(gòu)成要素
了解一個框架的首要任務(wù)就是搞清楚這個框架的基本構(gòu)成要素。當(dāng)然,這里所說的構(gòu)成要素實際上還可以被挖掘為兩個不同的層次:
- 基于框架所編寫的應(yīng)用程序的構(gòu)成要素
- 框架自身的運行主線以及微觀構(gòu)成要素
我們在這里首先來關(guān)注一下第一個層次,因為第一個層次是廣大程序員直接能夠接觸得到的部分。而第二個層次的討論,我們不得不在第一個層次的討論基礎(chǔ)之上通過不斷分析和邏輯論證慢慢給出答案。
在上一篇文章中,我們曾經(jīng)列舉了一段SpringMVC的代碼示例,用于說明MVC框架的構(gòu)成結(jié)構(gòu)。我們在這里不妨將這個示例細(xì)化,總結(jié)歸納出構(gòu)成SpringMVC應(yīng)用程序的基本要素。
1. 指定SpringMVC的入口程序(在web.xml中)
Xml代碼 ?
<!-- Processes application requests -->
<servlet>
????<servlet-name>dispatcher</servlet-name>
????<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
????<load-on-startup>1</load-on-startup>
</servlet>
????????
<servlet-mapping>
????<servlet-name>dispatcher</servlet-name>
????<url-pattern>/**</url-pattern>
</servlet-mapping>
以一個Servlet作為入口程序是絕大多數(shù)MVC框架都遵循的基本設(shè)計方案。這里的DispatcherServlet被我們稱之為核心分發(fā)器,是SpringMVC最重要的類之一,之后我們會對其單獨展開進(jìn)行分析。
2. 編寫SpringMVC的核心配置文件(在[servlet-name]-servlet.xml中)
Xml代碼 ?
<beans xmlns="http://www.springframework.org/schema/beans"
???? xmlns:mvc="http://www.springframework.org/schema/mvc"
???? xmlns:context="http://www.springframework.org/schema/context"
???? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
???? xsi:schemaLocation="
????????????http://www.springframework.org/schema/beans
????????????http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
????????????http://www.springframework.org/schema/context
????????????http://www.springframework.org/schema/context/spring-context-3.1.xsd
????????????http://www.springframework.org/schema/mvc
????????????http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd"
???? default-autowire="byName">
????
????<!-- Enables the Spring MVC @Controller programming model -->
????<mvc:annotation-driven />
????
????<context:component-scan base-package="com.demo2do" />
???? ?
????
????<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
????????<property name="prefix" value="/" />
????????<property name="suffix" value=".jsp" />
????</bean>
????
</beans>
SpringMVC自身由眾多不同的組件共同構(gòu)成,而每一個組件又有眾多不同的實現(xiàn)模式。這里的SpringMVC核心配置文件是定義SpringMVC行為方式的一個窗口,用于指定每一個組件的實現(xiàn)模式。有關(guān)SpringMVC組件的概念,在之后的討論中也會涉及。
3. 編寫控制(Controller)層的代碼
Java代碼 ?
@Controller
@RequestMapping
public class UserController {
?
????@RequestMapping("/login")
????public ModelAndView login(String name, String password) {
???? // write your logic here????
return new ModelAndView("success");
????}
?
}
控制(Controller)層的代碼編寫在一個Java文件中。我們可以看到這個Java文件是一個普通的Java類并不依賴于任何接口。只是在響應(yīng)類和響應(yīng)方法上使用了Annotation的語法將它與Http請求對應(yīng)起來。
從這個例子中,我們實際上已經(jīng)歸納了構(gòu)成基于SpringMVC應(yīng)用程序的最基本要素。它們分別是:
- 入口程序 —— DispatcherServlet
- 核心配置 —— [servlet-name]-servlet.xml
- 控制邏輯 —— UserController
從應(yīng)用程序自身的角度來看,入口程序和核心配置一旦確定之后將保持固定不變的,而控制邏輯則隨著整個應(yīng)用程序功能模塊的擴(kuò)展而不斷增加。所以在這種編程模式下,應(yīng)用程序的縱向擴(kuò)展非常簡單并且顯得游刃有余。
基于SpringMVC的應(yīng)用程序能夠表現(xiàn)為現(xiàn)在這個樣子,經(jīng)歷了一個不斷重構(gòu)不斷改造的過程。接下來的討論,我們就來試圖為大家揭秘這個過程。
SpringMVC的發(fā)展歷程
在上一篇文章中,我們曾經(jīng)討論過MVC的發(fā)展軌跡。當(dāng)時我們總結(jié)了一個MVC框架的發(fā)展軌跡圖:
從圖中我們可以發(fā)現(xiàn),所有的MVC框架都是從基本的Servlet模型發(fā)展而來。因此,要了解SpringMVC的發(fā)展歷程,我們還是從最基本的Servlet模型開始,探究SpringMVC對于Servlet模型的改造過程中究竟經(jīng)歷了哪些階段、碰到了哪些問題、并看看SpringMVC是如何解決這些問題的。
【核心Servlet的提煉】
在Servlet模型中,請求-響應(yīng)的實現(xiàn)依賴于兩大元素的共同配合:
1. 配置Servlet及其映射關(guān)系(在web.xml中)
Xml代碼 ?
<servlet>
????<servlet-name>registerServlet</servlet-name>
????<servlet-class>com.demo2do.springmvc.web.RegisterServlet</servlet-class>
????<load-on-startup>1</load-on-startup>
</servlet>
????????
<servlet-mapping>
????<servlet-name>registerServlet</servlet-name>
????<url-pattern>/register</url-pattern>
</servlet-mapping>
在這里,<url-pattern>定義了整個請求-響應(yīng)的映射載體:URL;而<servlet-name>則將<servlet>節(jié)點和<servlet-mapping>節(jié)點聯(lián)系在一起形成請求-響應(yīng)的映射關(guān)系;<servlet-class>則定義了具體進(jìn)行響應(yīng)的Servlet實現(xiàn)類。
2. 在Servlet實現(xiàn)類中完成響應(yīng)邏輯
Java代碼 ?
public class RegisterServlet extends HttpServlet {
?
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
???? ?
// 從request獲取參數(shù)
String name = req.getParameter("name");
String birthdayString = req.getParameter("birthday");
?
// 做必要的類型轉(zhuǎn)化
Date birthday = null;
try {
birthday = new SmpleDateFormat("yyyy-MM-dd").parse(birthdayString);
} catch (ParseException e) {
???? e.printStackTrace();
}
?
// 初始化User類,并設(shè)置字段到user對象中去
User user = new User();
user.setName(name);
user.setBirthday(birthday);
?
// 調(diào)用業(yè)務(wù)邏輯代碼完成注冊
UserService userService = new UserService();
userService.register(user);
?
// 設(shè)置返回數(shù)據(jù)
request.setAttribute("user", user);
?
// 返回成功頁面
req.getRequestDispatcher("/success.jsp").forward(req, resp);
}
}
Servlet實現(xiàn)類本質(zhì)上是一個Java類。通過Servlet接口定義中的HttpServletRequest對象,我們可以處理整個請求生命周期中的數(shù)據(jù);通過HttpServletResponse對象,我們可以處理Http響應(yīng)行為。
整個過程并不復(fù)雜,因為作為一個底層規(guī)范,所規(guī)定的編程元素和實現(xiàn)方式應(yīng)該盡可能直觀和簡單。在這一點上,Servlet規(guī)范似乎可以滿足我們的要求。如果將上述過程中的主要過程加以抽象,我們可以發(fā)現(xiàn)有兩個非常重要概念蘊含在了Servlet的規(guī)范之中:
控制流和數(shù)據(jù)流的問題幾乎貫穿了所有MVC框架的始末,因而我們不得不在這里率先提出來,希望對讀者有一些警示作用。
注:對于控制流和數(shù)據(jù)流的相關(guān)概念,請參考另外一篇博客:《Struts2技術(shù)內(nèi)幕》 新書部分篇章連載(五)—— 請求響應(yīng)哲學(xué)。這一對概念,幾乎是所有MVC框架背后最為重要的支撐,讀者應(yīng)該尤其重視!
所有MVC框架的核心問題也由控制流和數(shù)據(jù)流這兩大體系延伸開來。比如,在Servlet編程模型之下,"請求-響應(yīng)映射關(guān)系的定義"這一問題就會隨著項目規(guī)模的擴(kuò)大而顯得力不從心:
問題1 寫道
項目規(guī)模擴(kuò)大之后,請求-響應(yīng)的映射關(guān)系全部定義在web.xml中,將造成web.xml的不斷膨脹而變得難以維護(hù)。
針對這個問題,SpringMVC提出的方案就是:提煉一個核心的Servlet覆蓋對所有Http請求的處理。。
這一被提煉出來的Servlet,通常被我們稱之為:核心分發(fā)器。在SpringMVC中,核心分發(fā)器就是org.springframework.web.servlet.DispatcherServlet。
注:核心分發(fā)器的概念并非SpringMVC獨創(chuàng)。我們可以看到,核心分發(fā)器的提煉幾乎是所有MVC框架設(shè)計中的必經(jīng)之路。在Struts2中,也有核心分發(fā)器(Dispatcher)的概念,只是它并不以Servlet的形式出現(xiàn)。因此,讀者應(yīng)該把關(guān)注點放在核心分發(fā)器這個概念的提煉之上,而不是糾結(jié)于其形式。
有了DispatcherServlet,我們至少從表面上解決了上面的問題。至少在web.xml中,我們的配置代碼就被固定了下來:
Xml代碼 ?
<!-- Processes application requests -->
<servlet>
????<servlet-name>dispatcherServlet</servlet-name>
????<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
????<load-on-startup>1</load-on-startup>
</servlet>
????????
<servlet-mapping>
????<servlet-name>dispatcherServlet</servlet-name>
????<url-pattern>/**</url-pattern>
</servlet-mapping>
有了DispatcherServlet,我們只相當(dāng)于邁出了堅實的第一步,因為對核心Servlet的提煉不僅僅是將所有的Servlet集中在一起那么簡單,我們還將面臨兩大問題:
問題2 寫道
核心Servlet應(yīng)該能夠根據(jù)一定的規(guī)則對不同的Http請求分發(fā)到不同的Servlet對象上去進(jìn)行處理。
?
問題3 寫道
核心Servlet應(yīng)該能夠建立起一整套完整的對所有Http請求進(jìn)行規(guī)范化處理的流程。
而這兩大問題的解決,涉及到了DispatcherServlet的設(shè)計核心。我們也不得不引入另外一個重要的編程元素,那就是:組件。
【組件的引入】
DispatcherServlet的引入是我們通過加入新的編程元素來對基本的Servlet規(guī)范進(jìn)行抽象概括所邁出的第一步。不過接下來,有關(guān)DispatcherServlet的設(shè)計問題又一次擺到了我們的面前。
如果仔細(xì)分析一下上一節(jié)末尾所提出的兩個問題,我們可以發(fā)現(xiàn)這兩個問題實際上都涉及到了DispatcherServlet的處理過程,這一處理過程首先必須是一劑萬能藥,能夠處理所有的Http請求;同時,DispatcherServlet還需要完成不同協(xié)議之間的轉(zhuǎn)化工作(從Http協(xié)議到Java世界的轉(zhuǎn)化)。
對此,SpringMVC所提出的方案是:將整個處理流程規(guī)范化,并把每一個處理步驟分派到不同的組件中進(jìn)行處理。
這個方案實際上涉及到兩個方面:
- 處理流程規(guī)范化 —— 將處理流程劃分為若干個步驟(任務(wù)),并使用一條明確的邏輯主線將所有的步驟串聯(lián)起來
- 處理流程組件化 —— 將處理流程中的每一個步驟(任務(wù))都定義為接口,并為每個接口賦予不同的實現(xiàn)模式
在SpringMVC的設(shè)計中,這兩個方面的內(nèi)容總是在一個不斷交叉、互為補充的過程中逐步完善的。
處理流程規(guī)范化是目的,對于處理過程的步驟劃分和流程定義則是手段。因而處理流程規(guī)范化的首要內(nèi)容就是考慮一個通用的Servlet響應(yīng)程序大致應(yīng)該包含的邏輯步驟:
- 步驟1 —— 對Http請求進(jìn)行初步處理,查找與之對應(yīng)的Controller處理類(方法)
- 步驟2 —— 調(diào)用相應(yīng)的Controller處理類(方法)完成業(yè)務(wù)邏輯
- 步驟3 —— 對Controller處理類(方法)調(diào)用時可能發(fā)生的異常進(jìn)行處理
- 步驟4 —— 根據(jù)Controller處理類(方法)的調(diào)用結(jié)果,進(jìn)行Http響應(yīng)處理
這些邏輯步驟雖然還在我們的腦海中,不過這些過程恰恰正是我們對整個處理過程的流程化概括,稍后我們就會把它們進(jìn)行程序化處理。
所謂的程序化,實際上也就是使用編程語言將這些邏輯語義表達(dá)出來。在Java語言中,最適合表達(dá)邏輯處理語義的語法結(jié)構(gòu)是接口,因此上述的四個流程也就被定義為了四個不同接口,它們分別是:
- 步驟1 —— HandlerMapping
- 步驟2 —— HandlerAdapter
- 步驟3 —— HandlerExceptionResolver
- 步驟4 —— ViewResolver
結(jié)合之前我們對流程組件化的解釋,這些接口的定義不正是處理流程組件化的步驟嘛?這些接口,就是組件。
除了上述組件之外,SpringMVC所定義的組件幾乎涵蓋了每一個處理過程中的重要節(jié)點。我們在這里引用Spring官方reference中對于最基本的組件的一些說明:
我們在之后篇文章中將重點對這里所提到的所有組件做深入的分析。大家在這里需要理解的是SpringMVC定義這些組件的目的和初衷。
這些組件一旦被定義,自然而然也就引出了下一個問題:這些組件是如何串聯(lián)在一起的?這個過程,是在DispatcherServlet中完成的。有關(guān)這一點,我們可以從兩個不同的角度加以證明。
1. 從DispatcherServlet自身數(shù)據(jù)結(jié)構(gòu)的角度
如圖中所示,DispatcherServlet中包含了眾多SpringMVC的組件,這些組件是實現(xiàn)DispatcherServlet核心邏輯的基礎(chǔ)。
2. 從DispatcherServlet的核心源碼的角度
Java代碼 ?
try {
????// 這里省略了部分代碼
?
????// 獲取HandlerMapping組件返回的執(zhí)行鏈
????mappedHandler = getHandler(processedRequest, false);
????if (mappedHandler == null || mappedHandler.getHandler() == null) {
????????noHandlerFound(processedRequest, response);
????????return;
????}
?
????// 獲取HandlerAdapter組件
????HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
?
????// 這里省略了部分源碼
????
????// 調(diào)用HandlerAdapter組件
????mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
?
????// 這里省略了部分源碼
?
}catch (ModelAndViewDefiningException ex) {
????logger.debug("ModelAndViewDefiningException encountered", ex);
????mv = ex.getModelAndView();
}catch (Exception ex) {
????Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
????// 調(diào)用HandlerExceptionResolver進(jìn)行異常處理
????mv = processHandlerException(processedRequest, response, handler, ex);
????errorView = (mv != null);
}
從上面的代碼片段中,我們可以看到DispatcherServlet的核心邏輯不過是對組件的獲取和調(diào)用。
除此之外,SpringMVC對處理流程的規(guī)范化和組件化所引出的另外一個問題就是如何針對所有的組件進(jìn)行管理。
先說說管理。其實管理這些組件對于SpringMVC來說完全不是問題,因為SpringMVC作為Spring Framework的一部分,其自身的運行環(huán)境就是Spring所定義的容器之中。我們知道,Spring Framework的核心作用之一就是對整個應(yīng)用程序的組件進(jìn)行管理。所以SpringMVC對于這些已定義組件的管理,只不過是借用了Spring自身已經(jīng)提供的容器功能而已。
注:SpringMVC在進(jìn)行組件管理時,會單獨為SpringMVC相關(guān)的組件構(gòu)建一個容器環(huán)境,這一容器環(huán)境可以獨立于應(yīng)用程序自身所創(chuàng)建的Spring容器。有關(guān)這一點,我們在之后的討論中將詳細(xì)給出分析。
而SpringMVC對這些組件的管理載體,就是我們在上一節(jié)中所提到的核心配置文件。我們可以看到,核心配置文件在整個SpringMVC的構(gòu)成要素中占有一席之地的重要原因就是在于:我們必須借助一個有效的手段對整個SpringMVC的組件進(jìn)行定義,而這一點正是通過核心配置文件來完成的。
如果我們把上面的整個過程重新梳理一下,整個邏輯看起來就像這樣:
這四個方面的內(nèi)容,我們是順著設(shè)計思路的不斷推進(jìn)而總結(jié)歸納出來的。這也恰好證明之前所提到的一個重要觀點,我們在這里強調(diào)一下:
downpour 寫道
處理流程的規(guī)范化和組件化,是在一個不斷交叉、互為補充的過程中逐步完善的。
【行為模式的擴(kuò)展】
有了組件,也有了DispatcherServlet對所有組件的串聯(lián),我們之前所提出的兩個問題似乎已經(jīng)可以迎刃而解。所以,我們可以說:
downpour 寫道
SpringMVC就是通過DispatcherServlet將一堆組件串聯(lián)起來的Web框架。
在引入組件這個概念的時候,我們所強調(diào)的是處理流程的抽象化,因而所有組件的外在表現(xiàn)形式是接口。接口最重要意義是定義操作規(guī)范,所以接口用來表達(dá)每一個處理單元的邏輯語義是最合適不過的。但光有接口,并不能完整地構(gòu)成一個框架的行為模式。從操作規(guī)范到行為模式的變化,是由接口所對應(yīng)的實現(xiàn)類來完成的。
在Java語言中,一個接口可以有多個不同的實現(xiàn)類,從而構(gòu)成一個樹形的實現(xiàn)體系。而每一個不同的實現(xiàn)分支,實際上代表的是對于相同的邏輯語義的不同解讀方式。結(jié)合上面我們的描述,也可以說:一個接口的每一個不同的實現(xiàn)分支,代表了相同操作規(guī)范的不同行為模式。
我們可以通過之前曾經(jīng)提到過的一個SpringMVC組件HandlerMapping為例進(jìn)行說明。
上圖就是HandlerMapping接口的樹形實現(xiàn)體系。在這個實現(xiàn)體系結(jié)構(gòu)中,每一個樹形結(jié)構(gòu)的末端實現(xiàn)都是SpringMVC中比較具有典型意義的行為模式。我們可以截取其中的幾個實現(xiàn)來加以說明:
- BeanNameUrlHandlerMapping —— 根據(jù)Spring容器中的bean的定義來指定請求映射關(guān)系
- SimpleUrlHandlerMapping —— 直接指定URL與Controller的映射關(guān)系,其中的URL支持Ant風(fēng)格
- DefaultAnnotationHandlerMapping —— 支持通過直接掃描Controller類中的Annotation來確定請求映射關(guān)系
- RequestMappingHandlerMapping —— 通過掃描Controller類中的Annotation來確定請求映射關(guān)系的另外一個實現(xiàn)類
有關(guān)這幾個實現(xiàn)類的具體示例和使用說明,讀者可以參考不同版本的Spring官方文檔來獲取具體的細(xì)節(jié)。
注:我們在這里之所以要強調(diào)不同版本的Spring官方文檔的原因在于這些不同的實現(xiàn)類,正代表了不同版本SpringMVC在默認(rèn)行為模式上選擇的不同。在下圖中,我們列出了不同重大版本的SpringMVC的實現(xiàn)體系結(jié)構(gòu),并用紅色框圈出了每個版本默認(rèn)的實現(xiàn)類。
我們可以看到,上述這些不同的HandlerMapping的實現(xiàn)類,其運行機(jī)制和行為模式完全不同。這也就意味著對于HandlerMapping這個組件而言,可以進(jìn)行選擇的余地就很大。我們既可以選擇其中的一種實現(xiàn)模式作為默認(rèn)的行為模式,也可以將這些實現(xiàn)類依次串聯(lián)起來成為一個執(zhí)行鏈。不過這已經(jīng)是實現(xiàn)層面和設(shè)計模式上的小技巧了。
單就HandlerMapping一個組件,我們就能看到各種不同的行為模式。如果我們將邏輯主線中所有的組件全部考慮進(jìn)來,那么整個實現(xiàn)機(jī)制就會隨著這些組件實現(xiàn)體系的不同而表現(xiàn)出截然不同的行為方式了。因此,我們的結(jié)論是:
downpour 寫道
SpringMVC各種不同的組件實現(xiàn)體系成為了SpringMVC行為模式擴(kuò)展的有效途徑。
有關(guān)SpringMVC的各種組件和實現(xiàn)體系,我們將在之后的討論中詳細(xì)展開。
SpringMVC的設(shè)計原則
最后我們來討論一下SpringMVC的設(shè)計原則。任何框架在設(shè)計的時候都必須遵循一些基本的原則,而這些原則也成為整個框架的理論基礎(chǔ)。對于那些有一定SpringMVC使用經(jīng)驗的程序員來說,這些基本的設(shè)計原則本身也一定是給大家留下深刻印象的那些閃光點,所以我們非常有必要在這里加以總結(jié)。
【Open for extension / closed for modification】
這條重要的設(shè)計原則被寫在了Spring官方的reference中SpringMVC章節(jié)的起始段:
Spring Reference 寫道
A key design principle in Spring Web MVC and in Spring in general is the "Open for extension, closed for modification" principle.
SpringMVC在整個官方reference的起始就強調(diào)這一原則,可見其對于整個框架的重要性。那么我們又如何來理解這段話的含義呢?筆者在這里從源碼的角度歸納了四個方面:
1. 使用final關(guān)鍵字來限定核心組件中的核心方法
有關(guān)這一點,我們還可以在Spring官方的reference中找到非常明確的說明:
Spring Reference 寫道
Some methods in the core classes of Spring Web MVC are marked final. As a developer you cannot override these methods to supply your own behavior. This has not been done arbitrarily, but specifically with this principle in mind.
在SpringMVC的源碼中,HandlerAdapter實現(xiàn)類RequestMappingHandlerAdapter中,核心方法handleInternal就被定義為final:
downpour 寫道
結(jié)論? As a developer you cannot override these methods to supply your own behavior
2. 大量地在核心組件中使用private方法
我們依然以SpringMVC默認(rèn)的HandlerAdapter實現(xiàn)RequestMappingHandlerAdapter為例進(jìn)行說明:
可以看到,幾乎所有的核心處理方法全部被定義成了帶有紅色標(biāo)記的private方法,這就充分表明了SpringMVC對于"子類擴(kuò)展"這種方式的態(tài)度:
downpour 寫道
結(jié)論? 子類不允許通過繼承的方式改變父類的默認(rèn)行為。
3. 限定某些類對外部程序不可見
有關(guān)這一點,有好幾個類可以加以證明,我們不妨來看看它們的源碼定義:
Java代碼 ?
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
// 這里省略了所有的代碼
}
?
class DefaultServletHandlerBeanDefinitionParser implements BeanDefinitionParser {
// 這里省略了所有的代碼
}
?
class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {
// 這里省略了所有的代碼
}
?
class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
// 這里省略了所有的代碼
}
?
downpour 寫道
結(jié)論? 不允許外部程序?qū)@些系統(tǒng)配置類進(jìn)行訪問,從而杜絕外部程序?qū)pringMVC默認(rèn)行為的任何修改。
在這些類的定義中,我們并未看到public修飾符。也就是說,這些類只能在SpringMVC的內(nèi)部被調(diào)用,對于框架以外的應(yīng)用程序是不可見的。有關(guān)這些類的作用,我們將在之后的討論中詳細(xì)展開。
4. 提供自定義擴(kuò)展接口,卻不提供完整覆蓋默認(rèn)行為的方式
這一點,需要深入到SpringMVC的請求處理內(nèi)部才能夠體會得到,我們在這里截取了其中的一段源碼加以說明:
Java代碼 ?
????private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
????????List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
?
????????// Annotation-based argument resolution
????????resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
????????resolvers.add(new RequestParamMapMethodArgumentResolver());
????????resolvers.add(new PathVariableMethodArgumentResolver());
????????resolvers.add(new ServletModelAttributeMethodProcessor(false));
????????resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
????????resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
????????resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
????????resolvers.add(new RequestHeaderMapMethodArgumentResolver());
????????resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
????????resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
?
????????// Type-based argument resolution
????????resolvers.add(new ServletRequestMethodArgumentResolver());
????????resolvers.add(new ServletResponseMethodArgumentResolver());
????????resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
????????resolvers.add(new RedirectAttributesMethodArgumentResolver());
????????resolvers.add(new ModelMethodProcessor());
????????resolvers.add(new MapMethodProcessor());
????????resolvers.add(new ErrorsMethodArgumentResolver());
????????resolvers.add(new SessionStatusMethodArgumentResolver());
????????resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
?
????????// Custom arguments
????????if (getCustomArgumentResolvers() != null) {
????????????resolvers.addAll(getCustomArgumentResolvers());
????????}
?
????????// Catch-all
????????resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
????????resolvers.add(new ServletModelAttributeMethodProcessor(true));
?
????????return resolvers;
????}
這是RequestMappingHandlerAdapter內(nèi)部的一個重要方法,用以獲取所有的參數(shù)處理實現(xiàn)類(HandlerMethodArgumentResolver)。從源碼中,我們可以看到雖然這個方法是一個private的方法,但是它在源碼中卻提供了getCustomArgumentResolvers()方法作為切入口,允許用戶自行進(jìn)行擴(kuò)展。不過我們同樣可以發(fā)現(xiàn),用戶自定義的擴(kuò)展類,只是被插入到整個尋址過程中,并不能通過用戶自定義的擴(kuò)展類來實現(xiàn)對其他HandlerMethodArgumentResolver行為的覆蓋;也不能改變HandlerMethodArgumentResolver的處理順序。也就是說:
downpour 寫道
結(jié)論? SpringMVC提供的擴(kuò)展切入點無法改變框架默認(rèn)的行為方式。
上述這四個方面,都是這一條設(shè)計原則在源碼級別的佐證。或許有的讀者會產(chǎn)生這樣的疑慮:這個不能改,那個也不能改,我們對于SpringMVC的使用豈不是喪失了很多靈活性?這個疑慮的確存在,但是只說對了一半。因為SpringMVC的這一條設(shè)計原則說的是:不能動其根本,只能在一定范圍內(nèi)進(jìn)行擴(kuò)展。
至于說到SpringMVC為什么會基于這樣一條設(shè)計原則,這里面的原因很多。除了之前所提到的編程模型和組件模型的影響,其中更加牽涉到一個編程哲學(xué)的取向問題。有關(guān)這一點,我們在之后的文章中將陸續(xù)展開。
【形散神不散】
這一條編程原則實際上與上一條原則只是在表達(dá)方式上有所不同,其表達(dá)的核心意思是比較類似的。那么我們?nèi)绾蝸矶x這里的"形"和"神"呢?
- 神 —— SpringMVC總是沿著一條固定的邏輯主線運行
- 形 —— SpringMVC卻擁有多種不同的行為模式
SpringMVC是一個基于組件的開發(fā)框架,組件的不同實現(xiàn)體系構(gòu)成了"形";組件的邏輯串聯(lián)構(gòu)成了"神"。因此,"形散神不散",實際上是說:
downpour 寫道
結(jié)論? SpringMVC的邏輯主線始終不變,而行為模式卻可以多種多樣。
我們在之前有關(guān)組件的討論中,已經(jīng)見識到了組件的實現(xiàn)體系,也領(lǐng)略了在不同的SpringMVC版本中,組件的行為模式的不同。這些已經(jīng)能夠充分證明"形散"的事實。接下來,我們再通過源碼來證明一下"神不散":
圖中的代碼是DispatcherServlet中的核心方法doDispatch,我們這里使用了比較工具將Spring3.1中的實現(xiàn)代碼和Spring2.0.8中的實現(xiàn)代碼做了比較,其中的區(qū)別之處比較工具使用了不同的顏色標(biāo)注了出來。
我們可以很明顯地看到,雖然Spring2.0到Spring3.1之間,SpringMVC的行為方式已經(jīng)有了翻天覆地的變化,然而整個DispatcherServlet的核心處理主線卻并沒有很大的變化。這種穩(wěn)定性,恰巧證明了整個SpringMVC的體系結(jié)構(gòu)設(shè)計的精妙之處。
【簡化、簡化、還是簡化】
在Spring2.5之前的SpringMVC版本并沒有很強的生命力,因為它只是通過組件將整個MVC的概念加以詮釋,從開發(fā)流程的簡易度來看并沒有很明顯的提升。有關(guān)SpringMVC發(fā)展的里程碑,我們將在之后篇文章中重點講述。我們在這里想要談到的SpringMVC的另外一大設(shè)計原則,實際上主要是從Spring2.5這個版本之后才不斷顯現(xiàn)出來的。這條設(shè)計原則可以用2個字來概括:簡化。
這里說的簡化,其實包含的內(nèi)容非常廣泛。筆者在這里挑選了兩個比較重要的方面來進(jìn)行說明:
- Annotation —— 簡化各類配置定義
- Schema Based XML —— 簡化組件定義
先談?wù)凙nnotation。Annotation是JDK5.0帶來的一種全新的Java語法。這種語法的設(shè)計初衷眾說紛紜,并沒有一個標(biāo)準(zhǔn)的答案。筆者在這里給出一個個人觀點以供參考:
downpour 寫道
結(jié)論? Annotation的原型是注釋。作為一種對注釋的擴(kuò)展而被引入成為一個語法要素,其本身就是為了對所標(biāo)注的編程元素進(jìn)行補充說明,從而進(jìn)一步完善編程元素的邏輯語義。
從這個結(jié)論中,我們可以看到一層潛在的意思:在Annotation出現(xiàn)之前,Java自身語法所定義的編程元素已經(jīng)不足以表達(dá)足夠多的信息或者邏輯語義。在這種情況下,過去經(jīng)常使用的方法是引入新的編程元素(例如使用最多的就是XML形式的結(jié)構(gòu)化配置文件)來對Java程序進(jìn)行補充說明。而在Annotation出現(xiàn)之后,可以在一定程度上有效解決這一問題。因此Annotation在很長一段時間都被當(dāng)作是XML配置文件的替代品。
這也就是Annotation經(jīng)常被用來和XML進(jìn)行比較的原因。孰優(yōu)孰劣其實還是要視具體情況而定,并沒有什么標(biāo)準(zhǔn)答案。不過我們在這里想強調(diào)的是Annotation在整個SpringMVC中所起到的作用,并非僅僅是代替XML那么簡單。我們歸納了有三個不同的方面:
1. 簡化請求映射的定義
在Spring2.5之前,所有的Http請求與Controller核心處理器之間的映射關(guān)系都是在XML文件中定義的。作為XML配置文件的有效替代品,Annotation接過了定義映射關(guān)系的重任。我們可以將@RequestMapping加在Controller的class-level和method-level進(jìn)行Http請求的抽象。
2. 消除Controller對接口的依賴
在Spring2.5之前,SpringMVC規(guī)定所有的Controller都必須實現(xiàn)Controller接口:
Java代碼 ?
public interface Controller {
?
????/**
???? * Process the request and return a ModelAndView object which the DispatcherServlet
???? * will render. A <code>null</code> return value is not an error: It indicates that
???? * this object completed request processing itself, thus there is no ModelAndView
???? * to render.
???? * @param request current HTTP request
???? * @param response current HTTP response
???? * @return a ModelAndView to render, or <code>null</code> if handled directly
???? * @throws Exception in case of errors
???? */
????ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
?
}
也就是說,應(yīng)用程序不得不嚴(yán)重依賴于接口所規(guī)定的處理模式。而我們看到Controller接口除了對處理接口的返回值做了一次封裝以外,我們依然需要面對原生的HttpServletRequest和HttpServletResponse對象進(jìn)行操作。
而在Spring2.5之后,我們可以通過@Controller來指定SpringMVC可識別的Controller,徹底消除了對接口的依賴:
Java代碼 ?
@Controller
public class UserController {
// 這里省略了許多代碼
}
3. 成為框架進(jìn)行邏輯處理的標(biāo)識
之前已經(jīng)談到,Annotation主要被用于對編程元素進(jìn)行補充說明。因而Spring就利用這一特性,使得那些被加入了特殊Annotation的編程元素可以得到特殊的處理。例如,SpringMVC引入的@SessionAttribute、@RequestBody、@ModelAttribute等等,可以說既是對Controller的一種邏輯聲明,也成為了框架本身對相關(guān)元素進(jìn)行處理的一個標(biāo)識符。
再談?wù)凷chema Based XML。Schema Based XML并不是一個陌生的概念,早在Spring2.0時代就被用于進(jìn)行XML配置的簡化。SpringMVC在進(jìn)入到3.0版本之后,正式將其引入并作為SpringMVC組件定義的一個重要手段。
在XML中引入Schema,只需要在XML文件的開頭加入相關(guān)的定義。例如:
Xml代碼 ?
<beans xmlns="http://www.springframework.org/schema/beans"
???? xmlns:mvc="http://www.springframework.org/schema/mvc"
???? xmlns:context="http://www.springframework.org/schema/context"
???? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
???? xsi:schemaLocation="
????????????http://www.springframework.org/schema/beans
????????????http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
????????????http://www.springframework.org/schema/context
????????????http://www.springframework.org/schema/context/spring-context-3.1.xsd
????????????http://www.springframework.org/schema/mvc
????????????http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
?
?
</beans>
而Schema的具體處理,則位于Spring的JAR中的/META-INF/spring.handlers文件中進(jìn)行定義:
Xml代碼 ?
http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
我們會在之后的討論中詳細(xì)分析MvcNamespaceHandler的源碼。不過我們可以明確的是,在我們使用Schema Based XML的同時,有許多SpringMVC的內(nèi)置對象會被預(yù)先定義成為組件,我們的配置將是對這些預(yù)先定義好的組件的一個二次配置的過程。可以想象,二次配置一定會比較省力,因為它至少省去了很多內(nèi)置對象的定義過程。這也就是Schema Based XML帶來的簡化效果了。
小結(jié)
本文從邏輯上講,可以分成三個部分:
- SpringMVC的構(gòu)成要素 —— 是什么 —— 闡述框架的主體結(jié)構(gòu)
- SpringMVC的發(fā)展歷程 —— 為什么 —— 闡述框架各要素產(chǎn)生的內(nèi)因
- SpringMVC的設(shè)計原則 —— 怎么樣 —— 闡述框架的共性思想
"是什么"是框架最根本的問題。我們從SpringMVC的三要素入手,幫助大家分析構(gòu)成SpringMVC的基本元素主要是為了讓讀者對整個SpringMVC的架構(gòu)有一個宏觀的認(rèn)識。在之后的分析中,我們研究的主體內(nèi)容也將始終圍繞著這些SpringMVC的構(gòu)成要素,并進(jìn)行逐一分析。
"為什么"是框架的存在基礎(chǔ)。我們可以看到,整個SpringMVC的發(fā)展歷程是一個對于開發(fā)模式不斷進(jìn)行優(yōu)化的過程,也是不斷解決Web開發(fā)中所面臨的一個又一個問題的過程。之前我們也曾經(jīng)提到過一個重要觀點:任何框架無所謂好與壞、優(yōu)與劣,它們只是在不同的領(lǐng)域解決問題的方式不同。所以,我們分析這些SpringMVC基本構(gòu)成要素產(chǎn)生的原因?qū)嶋H上也是對整個Web開發(fā)進(jìn)行重新思考的過程。
"怎么樣"是一種深層次的需求。對于SpringMVC而言,了解其基本構(gòu)成和用法并不是一件難事,但是要從中提煉并總結(jié)出一些共性的東西就需要我們能夠站在一個更高的高度來進(jìn)行分析。也只有了解了這些共性的東西,我們才能進(jìn)一步總結(jié)出使用框架的最佳實踐。
讀到這里,希望讀者能夠回味一下本文的寫作思路,并且能夠舉一反三將這種思考問題的方式運用到其他一些框架的學(xué)習(xí)中去。這樣,本文的目的也就達(dá)到了。
轉(zhuǎn)載于:https://www.cnblogs.com/xiangxs/p/5051649.html
總結(jié)
以上是生活随笔為你收集整理的SpringMVC深度探险(二) —— SpringMVC概览的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《第十二夜》太闹太过火?莎翁可不是为博一
- 下一篇: 在使用Cocos2d-JS 开发过程中需