javascript
SpringMVC深度探险(二) —— SpringMVC概览
本文是專欄文章(SpringMVC深度探險)系列的文章之一,博客地址為:http://downpour.iteye.com/blog/1330596。
對于任何事物的研究,總是由表及里、由淺入深地進行。在本系列的第二篇文章中,我們將通過不同的觀察視角,對SpringMVC做一些概要性的分析,幫助大家了解SpringMVC的基本構成要素、SpringMVC的發展歷程以及SpringMVC的設計原則。
SpringMVC的構成要素
了解一個框架的首要任務就是搞清楚這個框架的基本構成要素。當然,這里所說的構成要素實際上還可以被挖掘為兩個不同的層次:
- 基于框架所編寫的應用程序的構成要素
- 框架自身的運行主線以及微觀構成要素
我們在這里首先來關注一下第一個層次,因為第一個層次是廣大程序員直接能夠接觸得到的部分。而第二個層次的討論,我們不得不在第一個層次的討論基礎之上通過不斷分析和邏輯論證慢慢給出答案。
在上一篇文章中,我們曾經列舉了一段SpringMVC的代碼示例,用于說明MVC框架的構成結構。我們在這里不妨將這個示例細化,總結歸納出構成SpringMVC應用程序的基本要素。
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作為入口程序是絕大多數MVC框架都遵循的基本設計方案。這里的DispatcherServlet被我們稱之為核心分發器,是SpringMVC最重要的類之一,之后我們會對其單獨展開進行分析。
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自身由眾多不同的組件共同構成,而每一個組件又有眾多不同的實現模式。這里的SpringMVC核心配置文件是定義SpringMVC行為方式的一個窗口,用于指定每一個組件的實現模式。有關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類并不依賴于任何接口。只是在響應類和響應方法上使用了Annotation的語法將它與Http請求對應起來。
從這個例子中,我們實際上已經歸納了構成基于SpringMVC應用程序的最基本要素。它們分別是:
- 入口程序 —— DispatcherServlet
- 核心配置 —— [servlet-name]-servlet.xml
- 控制邏輯 —— UserController
從應用程序自身的角度來看,入口程序和核心配置一旦確定之后將保持固定不變的,而控制邏輯則隨著整個應用程序功能模塊的擴展而不斷增加。所以在這種編程模式下,應用程序的縱向擴展非常簡單并且顯得游刃有余。
基于SpringMVC的應用程序能夠表現為現在這個樣子,經歷了一個不斷重構不斷改造的過程。接下來的討論,我們就來試圖為大家揭秘這個過程。
SpringMVC的發展歷程
在上一篇文章中,我們曾經討論過MVC的發展軌跡。當時我們總結了一個MVC框架的發展軌跡圖:
從圖中我們可以發現,所有的MVC框架都是從基本的Servlet模型發展而來。因此,要了解SpringMVC的發展歷程,我們還是從最基本的Servlet模型開始,探究SpringMVC對于Servlet模型的改造過程中究竟經歷了哪些階段、碰到了哪些問題、并看看SpringMVC是如何解決這些問題的。
【核心Servlet的提煉】
在Servlet模型中,請求-響應的實現依賴于兩大元素的共同配合:
1. 配置Servlet及其映射關系(在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>定義了整個請求-響應的映射載體:URL;而<servlet-name>則將<servlet>節點和<servlet-mapping>節點聯系在一起形成請求-響應的映射關系;<servlet-class>則定義了具體進行響應的Servlet實現類。
2. 在Servlet實現類中完成響應邏輯
Java代碼 ?
public class RegisterServlet extends HttpServlet {
?
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
???? ?
// 從request獲取參數
String name = req.getParameter("name");
String birthdayString = req.getParameter("birthday");
?
// 做必要的類型轉化
Date birthday = null;
try {
birthday = new SmpleDateFormat("yyyy-MM-dd").parse(birthdayString);
} catch (ParseException e) {
???? e.printStackTrace();
}
?
// 初始化User類,并設置字段到user對象中去
User user = new User();
user.setName(name);
user.setBirthday(birthday);
?
// 調用業務邏輯代碼完成注冊
UserService userService = new UserService();
userService.register(user);
?
// 設置返回數據
request.setAttribute("user", user);
?
// 返回成功頁面
req.getRequestDispatcher("/success.jsp").forward(req, resp);
}
}
Servlet實現類本質上是一個Java類。通過Servlet接口定義中的HttpServletRequest對象,我們可以處理整個請求生命周期中的數據;通過HttpServletResponse對象,我們可以處理Http響應行為。
整個過程并不復雜,因為作為一個底層規范,所規定的編程元素和實現方式應該盡可能直觀和簡單。在這一點上,Servlet規范似乎可以滿足我們的要求。如果將上述過程中的主要過程加以抽象,我們可以發現有兩個非常重要概念蘊含在了Servlet的規范之中:
控制流和數據流的問題幾乎貫穿了所有MVC框架的始末,因而我們不得不在這里率先提出來,希望對讀者有一些警示作用。
注:對于控制流和數據流的相關概念,請參考另外一篇博客:《Struts2技術內幕》 新書部分篇章連載(五)—— 請求響應哲學。這一對概念,幾乎是所有MVC框架背后最為重要的支撐,讀者應該尤其重視!
所有MVC框架的核心問題也由控制流和數據流這兩大體系延伸開來。比如,在Servlet編程模型之下,"請求-響應映射關系的定義"這一問題就會隨著項目規模的擴大而顯得力不從心:
問題1 寫道
項目規模擴大之后,請求-響應的映射關系全部定義在web.xml中,將造成web.xml的不斷膨脹而變得難以維護。
針對這個問題,SpringMVC提出的方案就是:提煉一個核心的Servlet覆蓋對所有Http請求的處理。。
這一被提煉出來的Servlet,通常被我們稱之為:核心分發器。在SpringMVC中,核心分發器就是org.springframework.web.servlet.DispatcherServlet。
注:核心分發器的概念并非SpringMVC獨創。我們可以看到,核心分發器的提煉幾乎是所有MVC框架設計中的必經之路。在Struts2中,也有核心分發器(Dispatcher)的概念,只是它并不以Servlet的形式出現。因此,讀者應該把關注點放在核心分發器這個概念的提煉之上,而不是糾結于其形式。
有了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,我們只相當于邁出了堅實的第一步,因為對核心Servlet的提煉不僅僅是將所有的Servlet集中在一起那么簡單,我們還將面臨兩大問題:
問題2 寫道
核心Servlet應該能夠根據一定的規則對不同的Http請求分發到不同的Servlet對象上去進行處理。
?
問題3 寫道
核心Servlet應該能夠建立起一整套完整的對所有Http請求進行規范化處理的流程。
而這兩大問題的解決,涉及到了DispatcherServlet的設計核心。我們也不得不引入另外一個重要的編程元素,那就是:組件。
【組件的引入】
DispatcherServlet的引入是我們通過加入新的編程元素來對基本的Servlet規范進行抽象概括所邁出的第一步。不過接下來,有關DispatcherServlet的設計問題又一次擺到了我們的面前。
如果仔細分析一下上一節末尾所提出的兩個問題,我們可以發現這兩個問題實際上都涉及到了DispatcherServlet的處理過程,這一處理過程首先必須是一劑萬能藥,能夠處理所有的Http請求;同時,DispatcherServlet還需要完成不同協議之間的轉化工作(從Http協議到Java世界的轉化)。
對此,SpringMVC所提出的方案是:將整個處理流程規范化,并把每一個處理步驟分派到不同的組件中進行處理。
這個方案實際上涉及到兩個方面:
- 處理流程規范化 —— 將處理流程劃分為若干個步驟(任務),并使用一條明確的邏輯主線將所有的步驟串聯起來
- 處理流程組件化 —— 將處理流程中的每一個步驟(任務)都定義為接口,并為每個接口賦予不同的實現模式
在SpringMVC的設計中,這兩個方面的內容總是在一個不斷交叉、互為補充的過程中逐步完善的。
處理流程規范化是目的,對于處理過程的步驟劃分和流程定義則是手段。因而處理流程規范化的首要內容就是考慮一個通用的Servlet響應程序大致應該包含的邏輯步驟:
- 步驟1 —— 對Http請求進行初步處理,查找與之對應的Controller處理類(方法)
- 步驟2 —— 調用相應的Controller處理類(方法)完成業務邏輯
- 步驟3 —— 對Controller處理類(方法)調用時可能發生的異常進行處理
- 步驟4 —— 根據Controller處理類(方法)的調用結果,進行Http響應處理
這些邏輯步驟雖然還在我們的腦海中,不過這些過程恰恰正是我們對整個處理過程的流程化概括,稍后我們就會把它們進行程序化處理。
所謂的程序化,實際上也就是使用編程語言將這些邏輯語義表達出來。在Java語言中,最適合表達邏輯處理語義的語法結構是接口,因此上述的四個流程也就被定義為了四個不同接口,它們分別是:
- 步驟1 —— HandlerMapping
- 步驟2 —— HandlerAdapter
- 步驟3 —— HandlerExceptionResolver
- 步驟4 —— ViewResolver
結合之前我們對流程組件化的解釋,這些接口的定義不正是處理流程組件化的步驟嘛?這些接口,就是組件。
除了上述組件之外,SpringMVC所定義的組件幾乎涵蓋了每一個處理過程中的重要節點。我們在這里引用Spring官方reference中對于最基本的組件的一些說明:
我們在之后篇文章中將重點對這里所提到的所有組件做深入的分析。大家在這里需要理解的是SpringMVC定義這些組件的目的和初衷。
這些組件一旦被定義,自然而然也就引出了下一個問題:這些組件是如何串聯在一起的?這個過程,是在DispatcherServlet中完成的。有關這一點,我們可以從兩個不同的角度加以證明。
1. 從DispatcherServlet自身數據結構的角度
如圖中所示,DispatcherServlet中包含了眾多SpringMVC的組件,這些組件是實現DispatcherServlet核心邏輯的基礎。
2. 從DispatcherServlet的核心源碼的角度
Java代碼 ?
try {
????// 這里省略了部分代碼
?
????// 獲取HandlerMapping組件返回的執行鏈
????mappedHandler = getHandler(processedRequest, false);
????if (mappedHandler == null || mappedHandler.getHandler() == null) {
????????noHandlerFound(processedRequest, response);
????????return;
????}
?
????// 獲取HandlerAdapter組件
????HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
?
????// 這里省略了部分源碼
????
????// 調用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);
????// 調用HandlerExceptionResolver進行異常處理
????mv = processHandlerException(processedRequest, response, handler, ex);
????errorView = (mv != null);
}
從上面的代碼片段中,我們可以看到DispatcherServlet的核心邏輯不過是對組件的獲取和調用。
除此之外,SpringMVC對處理流程的規范化和組件化所引出的另外一個問題就是如何針對所有的組件進行管理。
先說說管理。其實管理這些組件對于SpringMVC來說完全不是問題,因為SpringMVC作為Spring Framework的一部分,其自身的運行環境就是Spring所定義的容器之中。我們知道,Spring Framework的核心作用之一就是對整個應用程序的組件進行管理。所以SpringMVC對于這些已定義組件的管理,只不過是借用了Spring自身已經提供的容器功能而已。
注:SpringMVC在進行組件管理時,會單獨為SpringMVC相關的組件構建一個容器環境,這一容器環境可以獨立于應用程序自身所創建的Spring容器。有關這一點,我們在之后的討論中將詳細給出分析。
而SpringMVC對這些組件的管理載體,就是我們在上一節中所提到的核心配置文件。我們可以看到,核心配置文件在整個SpringMVC的構成要素中占有一席之地的重要原因就是在于:我們必須借助一個有效的手段對整個SpringMVC的組件進行定義,而這一點正是通過核心配置文件來完成的。
如果我們把上面的整個過程重新梳理一下,整個邏輯看起來就像這樣:
這四個方面的內容,我們是順著設計思路的不斷推進而總結歸納出來的。這也恰好證明之前所提到的一個重要觀點,我們在這里強調一下:
downpour 寫道
處理流程的規范化和組件化,是在一個不斷交叉、互為補充的過程中逐步完善的。
【行為模式的擴展】
有了組件,也有了DispatcherServlet對所有組件的串聯,我們之前所提出的兩個問題似乎已經可以迎刃而解。所以,我們可以說:
downpour 寫道
SpringMVC就是通過DispatcherServlet將一堆組件串聯起來的Web框架。
在引入組件這個概念的時候,我們所強調的是處理流程的抽象化,因而所有組件的外在表現形式是接口。接口最重要意義是定義操作規范,所以接口用來表達每一個處理單元的邏輯語義是最合適不過的。但光有接口,并不能完整地構成一個框架的行為模式。從操作規范到行為模式的變化,是由接口所對應的實現類來完成的。
在Java語言中,一個接口可以有多個不同的實現類,從而構成一個樹形的實現體系。而每一個不同的實現分支,實際上代表的是對于相同的邏輯語義的不同解讀方式。結合上面我們的描述,也可以說:一個接口的每一個不同的實現分支,代表了相同操作規范的不同行為模式。
我們可以通過之前曾經提到過的一個SpringMVC組件HandlerMapping為例進行說明。
上圖就是HandlerMapping接口的樹形實現體系。在這個實現體系結構中,每一個樹形結構的末端實現都是SpringMVC中比較具有典型意義的行為模式。我們可以截取其中的幾個實現來加以說明:
- BeanNameUrlHandlerMapping —— 根據Spring容器中的bean的定義來指定請求映射關系
- SimpleUrlHandlerMapping —— 直接指定URL與Controller的映射關系,其中的URL支持Ant風格
- DefaultAnnotationHandlerMapping —— 支持通過直接掃描Controller類中的Annotation來確定請求映射關系
- RequestMappingHandlerMapping —— 通過掃描Controller類中的Annotation來確定請求映射關系的另外一個實現類
有關這幾個實現類的具體示例和使用說明,讀者可以參考不同版本的Spring官方文檔來獲取具體的細節。
注:我們在這里之所以要強調不同版本的Spring官方文檔的原因在于這些不同的實現類,正代表了不同版本SpringMVC在默認行為模式上選擇的不同。在下圖中,我們列出了不同重大版本的SpringMVC的實現體系結構,并用紅色框圈出了每個版本默認的實現類。
我們可以看到,上述這些不同的HandlerMapping的實現類,其運行機制和行為模式完全不同。這也就意味著對于HandlerMapping這個組件而言,可以進行選擇的余地就很大。我們既可以選擇其中的一種實現模式作為默認的行為模式,也可以將這些實現類依次串聯起來成為一個執行鏈。不過這已經是實現層面和設計模式上的小技巧了。
單就HandlerMapping一個組件,我們就能看到各種不同的行為模式。如果我們將邏輯主線中所有的組件全部考慮進來,那么整個實現機制就會隨著這些組件實現體系的不同而表現出截然不同的行為方式了。因此,我們的結論是:
downpour 寫道
SpringMVC各種不同的組件實現體系成為了SpringMVC行為模式擴展的有效途徑。
有關SpringMVC的各種組件和實現體系,我們將在之后的討論中詳細展開。
SpringMVC的設計原則
最后我們來討論一下SpringMVC的設計原則。任何框架在設計的時候都必須遵循一些基本的原則,而這些原則也成為整個框架的理論基礎。對于那些有一定SpringMVC使用經驗的程序員來說,這些基本的設計原則本身也一定是給大家留下深刻印象的那些閃光點,所以我們非常有必要在這里加以總結。
【Open for extension / closed for modification】
這條重要的設計原則被寫在了Spring官方的reference中SpringMVC章節的起始段:
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的起始就強調這一原則,可見其對于整個框架的重要性。那么我們又如何來理解這段話的含義呢?筆者在這里從源碼的角度歸納了四個方面:
1. 使用final關鍵字來限定核心組件中的核心方法
有關這一點,我們還可以在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實現類RequestMappingHandlerAdapter中,核心方法handleInternal就被定義為final:
downpour 寫道
結論? As a developer you cannot override these methods to supply your own behavior
2. 大量地在核心組件中使用private方法
我們依然以SpringMVC默認的HandlerAdapter實現RequestMappingHandlerAdapter為例進行說明:
可以看到,幾乎所有的核心處理方法全部被定義成了帶有紅色標記的private方法,這就充分表明了SpringMVC對于"子類擴展"這種方式的態度:
downpour 寫道
結論? 子類不允許通過繼承的方式改變父類的默認行為。
3. 限定某些類對外部程序不可見
有關這一點,有好幾個類可以加以證明,我們不妨來看看它們的源碼定義:
Java代碼 ?
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
// 這里省略了所有的代碼
}
?
class DefaultServletHandlerBeanDefinitionParser implements BeanDefinitionParser {
// 這里省略了所有的代碼
}
?
class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {
// 這里省略了所有的代碼
}
?
class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
// 這里省略了所有的代碼
}
?
downpour 寫道
結論? 不允許外部程序對這些系統配置類進行訪問,從而杜絕外部程序對SpringMVC默認行為的任何修改。
在這些類的定義中,我們并未看到public修飾符。也就是說,這些類只能在SpringMVC的內部被調用,對于框架以外的應用程序是不可見的。有關這些類的作用,我們將在之后的討論中詳細展開。
4. 提供自定義擴展接口,卻不提供完整覆蓋默認行為的方式
這一點,需要深入到SpringMVC的請求處理內部才能夠體會得到,我們在這里截取了其中的一段源碼加以說明:
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內部的一個重要方法,用以獲取所有的參數處理實現類(HandlerMethodArgumentResolver)。從源碼中,我們可以看到雖然這個方法是一個private的方法,但是它在源碼中卻提供了getCustomArgumentResolvers()方法作為切入口,允許用戶自行進行擴展。不過我們同樣可以發現,用戶自定義的擴展類,只是被插入到整個尋址過程中,并不能通過用戶自定義的擴展類來實現對其他HandlerMethodArgumentResolver行為的覆蓋;也不能改變HandlerMethodArgumentResolver的處理順序。也就是說:
downpour 寫道
結論? SpringMVC提供的擴展切入點無法改變框架默認的行為方式。
上述這四個方面,都是這一條設計原則在源碼級別的佐證。或許有的讀者會產生這樣的疑慮:這個不能改,那個也不能改,我們對于SpringMVC的使用豈不是喪失了很多靈活性?這個疑慮的確存在,但是只說對了一半。因為SpringMVC的這一條設計原則說的是:不能動其根本,只能在一定范圍內進行擴展。
至于說到SpringMVC為什么會基于這樣一條設計原則,這里面的原因很多。除了之前所提到的編程模型和組件模型的影響,其中更加牽涉到一個編程哲學的取向問題。有關這一點,我們在之后的文章中將陸續展開。
【形散神不散】
這一條編程原則實際上與上一條原則只是在表達方式上有所不同,其表達的核心意思是比較類似的。那么我們如何來定義這里的"形"和"神"呢?
- 神 —— SpringMVC總是沿著一條固定的邏輯主線運行
- 形 —— SpringMVC卻擁有多種不同的行為模式
SpringMVC是一個基于組件的開發框架,組件的不同實現體系構成了"形";組件的邏輯串聯構成了"神"。因此,"形散神不散",實際上是說:
downpour 寫道
結論? SpringMVC的邏輯主線始終不變,而行為模式卻可以多種多樣。
我們在之前有關組件的討論中,已經見識到了組件的實現體系,也領略了在不同的SpringMVC版本中,組件的行為模式的不同。這些已經能夠充分證明"形散"的事實。接下來,我們再通過源碼來證明一下"神不散":
圖中的代碼是DispatcherServlet中的核心方法doDispatch,我們這里使用了比較工具將Spring3.1中的實現代碼和Spring2.0.8中的實現代碼做了比較,其中的區別之處比較工具使用了不同的顏色標注了出來。
我們可以很明顯地看到,雖然Spring2.0到Spring3.1之間,SpringMVC的行為方式已經有了翻天覆地的變化,然而整個DispatcherServlet的核心處理主線卻并沒有很大的變化。這種穩定性,恰巧證明了整個SpringMVC的體系結構設計的精妙之處。
【簡化、簡化、還是簡化】
在Spring2.5之前的SpringMVC版本并沒有很強的生命力,因為它只是通過組件將整個MVC的概念加以詮釋,從開發流程的簡易度來看并沒有很明顯的提升。有關SpringMVC發展的里程碑,我們將在之后篇文章中重點講述。我們在這里想要談到的SpringMVC的另外一大設計原則,實際上主要是從Spring2.5這個版本之后才不斷顯現出來的。這條設計原則可以用2個字來概括:簡化。
這里說的簡化,其實包含的內容非常廣泛。筆者在這里挑選了兩個比較重要的方面來進行說明:
- Annotation —— 簡化各類配置定義
- Schema Based XML —— 簡化組件定義
先談談Annotation。Annotation是JDK5.0帶來的一種全新的Java語法。這種語法的設計初衷眾說紛紜,并沒有一個標準的答案。筆者在這里給出一個個人觀點以供參考:
downpour 寫道
結論? Annotation的原型是注釋。作為一種對注釋的擴展而被引入成為一個語法要素,其本身就是為了對所標注的編程元素進行補充說明,從而進一步完善編程元素的邏輯語義。
從這個結論中,我們可以看到一層潛在的意思:在Annotation出現之前,Java自身語法所定義的編程元素已經不足以表達足夠多的信息或者邏輯語義。在這種情況下,過去經常使用的方法是引入新的編程元素(例如使用最多的就是XML形式的結構化配置文件)來對Java程序進行補充說明。而在Annotation出現之后,可以在一定程度上有效解決這一問題。因此Annotation在很長一段時間都被當作是XML配置文件的替代品。
這也就是Annotation經常被用來和XML進行比較的原因。孰優孰劣其實還是要視具體情況而定,并沒有什么標準答案。不過我們在這里想強調的是Annotation在整個SpringMVC中所起到的作用,并非僅僅是代替XML那么簡單。我們歸納了有三個不同的方面:
1. 簡化請求映射的定義
在Spring2.5之前,所有的Http請求與Controller核心處理器之間的映射關系都是在XML文件中定義的。作為XML配置文件的有效替代品,Annotation接過了定義映射關系的重任。我們可以將@RequestMapping加在Controller的class-level和method-level進行Http請求的抽象。
2. 消除Controller對接口的依賴
在Spring2.5之前,SpringMVC規定所有的Controller都必須實現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;
?
}
也就是說,應用程序不得不嚴重依賴于接口所規定的處理模式。而我們看到Controller接口除了對處理接口的返回值做了一次封裝以外,我們依然需要面對原生的HttpServletRequest和HttpServletResponse對象進行操作。
而在Spring2.5之后,我們可以通過@Controller來指定SpringMVC可識別的Controller,徹底消除了對接口的依賴:
Java代碼 ?
@Controller
public class UserController {
// 這里省略了許多代碼
}
3. 成為框架進行邏輯處理的標識
之前已經談到,Annotation主要被用于對編程元素進行補充說明。因而Spring就利用這一特性,使得那些被加入了特殊Annotation的編程元素可以得到特殊的處理。例如,SpringMVC引入的@SessionAttribute、@RequestBody、@ModelAttribute等等,可以說既是對Controller的一種邏輯聲明,也成為了框架本身對相關元素進行處理的一個標識符。
再談談Schema Based XML。Schema Based XML并不是一個陌生的概念,早在Spring2.0時代就被用于進行XML配置的簡化。SpringMVC在進入到3.0版本之后,正式將其引入并作為SpringMVC組件定義的一個重要手段。
在XML中引入Schema,只需要在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">
?
?
</beans>
而Schema的具體處理,則位于Spring的JAR中的/META-INF/spring.handlers文件中進行定義:
Xml代碼 ?
http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
我們會在之后的討論中詳細分析MvcNamespaceHandler的源碼。不過我們可以明確的是,在我們使用Schema Based XML的同時,有許多SpringMVC的內置對象會被預先定義成為組件,我們的配置將是對這些預先定義好的組件的一個二次配置的過程。可以想象,二次配置一定會比較省力,因為它至少省去了很多內置對象的定義過程。這也就是Schema Based XML帶來的簡化效果了。
小結
本文從邏輯上講,可以分成三個部分:
- SpringMVC的構成要素 —— 是什么 —— 闡述框架的主體結構
- SpringMVC的發展歷程 —— 為什么 —— 闡述框架各要素產生的內因
- SpringMVC的設計原則 —— 怎么樣 —— 闡述框架的共性思想
"是什么"是框架最根本的問題。我們從SpringMVC的三要素入手,幫助大家分析構成SpringMVC的基本元素主要是為了讓讀者對整個SpringMVC的架構有一個宏觀的認識。在之后的分析中,我們研究的主體內容也將始終圍繞著這些SpringMVC的構成要素,并進行逐一分析。
"為什么"是框架的存在基礎。我們可以看到,整個SpringMVC的發展歷程是一個對于開發模式不斷進行優化的過程,也是不斷解決Web開發中所面臨的一個又一個問題的過程。之前我們也曾經提到過一個重要觀點:任何框架無所謂好與壞、優與劣,它們只是在不同的領域解決問題的方式不同。所以,我們分析這些SpringMVC基本構成要素產生的原因實際上也是對整個Web開發進行重新思考的過程。
"怎么樣"是一種深層次的需求。對于SpringMVC而言,了解其基本構成和用法并不是一件難事,但是要從中提煉并總結出一些共性的東西就需要我們能夠站在一個更高的高度來進行分析。也只有了解了這些共性的東西,我們才能進一步總結出使用框架的最佳實踐。
讀到這里,希望讀者能夠回味一下本文的寫作思路,并且能夠舉一反三將這種思考問題的方式運用到其他一些框架的學習中去。這樣,本文的目的也就達到了。
轉載于:https://www.cnblogs.com/xiangxs/p/5051649.html
總結
以上是生活随笔為你收集整理的SpringMVC深度探险(二) —— SpringMVC概览的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《第十二夜》太闹太过火?莎翁可不是为博一
- 下一篇: 原创:想收的人不想拜,想拜的人拜不了,杨