13.11. 慣例優先原則(convention over configuration) - Spring Framework reference 2.0.5 參考手冊中文版
13.11. 慣例優先原則(convention over configuration)
對於很多項目來說,遵從已有的慣例和使用合理的缺省選項大概是最合情合理的做法。現在Spring Web MVC框架也明確支持這種慣例優先的配置。具體來說,如果你在項目中遵守一定的慣例(比如命名規範),你可以顯著地減少系統需要的配置(比如處理器映射,視圖解析器配置,ModelAndView
的聲明,等等)。這對快速系統建模(rapid
prototyping)是非常有利的。如果你打算進一步把模型完成爲可以工作的系統,這樣寫出的代碼也具有很好的一致性。
提示
Spring的開發包中有一個web應用的範例。這個範例演示了這一節提到的慣例優先原則。你可以在samples/showcases/mvc-convention
目錄中找到這個範例。
This convention over configuration support address the three core areas of MVC - namely, the models, views, and controllers.
Spring對慣例優先原則的支持體現在MVC的3個核心領域:模型、視圖和控制器。
ControllerClassNameHandlerMapping
是HandlerMapping
接口的一個實現。它檢查請求的URL,然後通過慣例來決定與之相對應的控制器。
比如,下面有個非常簡單的控制器實現,請特別注意這個類的名字。
public class ViewShoppingCartController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { // the implementation is not hugely important for this example... }}下文是從Spring Web MVC 框架的配置文件中選出來的一段:
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping "/> <bean id="viewShoppingCart" class="x.y.z.ViewShoppingCartController"> <!-- inject dependencies as required... --></bean>
ControllerClassNameHandlerMapping
在應用的上下文中找出所有的請求處理器(handler)(或實現了Controller
接口的)bean。把這些bean的名字中Controller
後綴去掉,就得到了每個控制器所能處理的URL。
讓我們通過舉例來進一步解釋這個映射的工作原理。
-
WelcomeController
映射到'/welcome*'
這個URL -
HomeController
映射到'/home*'
這個URL -
IndexController
映射到'/index*'
這個URL -
RegisterController
映射到'/register*'
這個URL -
DisplayShoppingCartController
映射到'/displayshoppingcart*'
這個URL(注意字母的大小寫。URL全部都用小寫,但在Java類名中每個單詞的第一個字母要大寫。)
當控制器是MultiActionController
的子類時,自動生成的映射就稍有點複雜,但應該還是比較好理解的。下面例子中這幾個控制器都是MultiActionController
。
-
AdminController
映射到'/admin/*'
這個URL。 -
CatalogController
映射到'/catalog/*'
這個URL。
如果你的控制器類遵守這些命名規範(xxxController
),ControllerClassNameHandlerMapping
可以自動生成映射,這樣你就不必費勁的定義和維護一長串SimpleUrlHandlerMapping
(或者類似的映射策略)。
ControllerClassNameHandlerMapping
是AbstractHandlerMapping
的子類,所以你仍舊可以像對待其他HandlerMapping
實例一樣來定義HandlerInterceptor
的實例。
ModelMap
是一個加強版的Map
實現。在這個Map裏,每個對象的鍵都遵守一個命名規範,然後這些對象就可以顯示在視圖中。這個類的使用其實很簡單,不需要長篇大論。下面讓我們看幾個例子,然後我們結合例子進行講解。
下面是一個Controller
的實現。請注意當我們把對象加到ModelAndView
時,我們不需要聲明每個對象的鍵名。
List
of CartItem
objects User
user = // get the User
doing the shoppingModelAndView mav = new ModelAndView("displayShoppingCart"); <--
the logical view name mav.addObject(cartItems); <-- look ma, no name, just the object mav.addObject(user); <-- and a'ain
ma! return mav; }}
ModelAndView
在內部使用了ModelMap
。ModelMap
是一個自定義的Map實現,它可以爲加入其中的每個對象自動生成一個鍵名。這些鍵名是有規律的。當存入的對象是存儲單值的對象(scalar
object),比如User
,生成的鍵名就是對象的類名(不包括包的名字)。下面幾個例子詳細解釋了這個命名規範:
-
x.y.User
這個類的實例對應user
。 -
x.y.Registration
這個類的實例對應registration
-
x.y.Foo
這個類的實例對應foo
-
java.util.HashMap
的實例對應hashMap
(在這種情況下你最好還是自己聲明鍵名,hashMap
這個名字不那麼直觀) -
當你視圖把
null
這個值加入Map時,你會得到IllegalArgumentException
。 所以如果你的某個對象可能爲null,你最好也自己聲明鍵名。
當你加入ModelAndView
中的對象是Set
、List
或者數組時,Spring會檢查這個集合,取出這個集合中的第一個對象,然後用它的類名,加上List
後綴,就是最終生成的名字。下面幾個例子進一步解釋了這個命名規則:
-
一個由
x.y.User
組成的數組對應userList
這個名字。 -
一個由
x.y.Foo
組成的數組對應fooList
這個名字。 -
一個由
x.y.User
組成的java.util.ArrayList
對應userList
這個名字。 -
一個由
x.y.Foo
組成的java.util.HashSet
對應fooList
這個名字。 -
一個空的
java.util.ArrayList
根本不可能被加到這個Map中。(在這種情況下,adObject(..)
其實什麼都沒做)。
RequestToViewNameTranslator
這個接口的功能是自動尋找請求所對應的視圖名(當某個視圖名沒有明確配置的時候)。這個接口目前只有一個實現,類名爲DefaultRequestToViewNameTranslator
。
爲了解釋DefaultRequestToViewNameTranslator
是如何將請求的URL映射到視圖名,最好的方法就是舉例說明。下面是一個Controller
的實現,和它對應的配置文件。
View
or logical view name has been set }}<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"><beans> <!--
this bean with the well known name generates view names for us --> <bean id="viewNameTranslator" class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator"/> <bean class="x.y.RegistrationControllerController"> <!--
inject dependencies as necessary --> </bean> <!-- maps request URLs to Controller names --> <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver "> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean></beans>
請注意,在這個handleRequest(..)
方法中,返回的ModelAndView
實例不包含View
的實例或者邏輯視圖名。真正從請求的URL中分析出邏輯視圖名(logical
view name)的是DefaultRequestToViewNameTranslator
。在上面這個例子中,RegistrationControllerController
和ControllerClassNameHandlerMapping
在一起使用,所有對http://localhost/registration.html
這個URL的請求將會對應registration
這個邏輯視圖名。這個視圖名正是由DefaultRequestToViewNameTranslator
解析出來的。然後,InternalResourceViewResolver
這個bean會將這個邏輯視圖名進一步解析成/WEB-INF/jsp/registration.jsp
這個視圖。
提示
你甚至不需要配置類型爲DefaultRequestToViewNameTranslator
的bean。如果DefaultRequestToViewNameTranslator
的缺省行爲已經符合你的要求,你就可以使用這個類。當你沒有明確聲明時,Spring Web
MVC 中DispatcherServlet
這個類會自動生成一個DefaultRequestToViewNameTranslator
的實例。
當然,如果你有自己特殊的要求,你就需要配置DefaultRequestToViewNameTranslator
bean。如果你需要知道這個類有哪些可以設置的參數,請參閱DefaultRequestToViewNameTranslator
的Javadoc。