SpringMVC(16) - 約定優於配置支援
參考:https://docs.spring.io/spring/docs/4.3.20.RELEASE/spring-framework-reference/htmlsingle/#mvc-coc
對於很多專案來說,堅持已建立的約定並且具有合理的預設值正是他們(專案)所需要的,而Spring Web MVC現在明確支援約定優於配置。這意味著如果你建立了一組命名約定等,你可以大大減少設定處理器對映、檢視解析器、ModelAndView例項等所需的配置量。這對於快速原型設計是一個很大的好處,如果選擇將其推向生產階段,還可以在程式碼庫中提供一定程度的(始終是良好的)一致性。
Convention-over-configuration(約定優於配置)的支援解決了MVC的三個核心領域:模型,檢視和控制器。
1. 控制器ControllerClassNameHandlerMapping
ControllerClassNameHandlerMapping類是一個HandlerMapping實現,它使用約定來確定請求URL與要處理這些請求的Controller例項之間的對映。
考慮以下簡單的Controller實現。特別注意類名稱。
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查詢在其應用程式上下文中定義的所有各種處理器(或Controller)bean,並從名稱中刪除Controller以定義其處理器對映。因此,ViewShoppingCartController對映到 /viewshoppingcart* 請求URL。
示例: (注意URL中的所有小寫,與駝峰式的Controller類名相反。)
- WelcomeController:對映到/welcome*請求URL
- HomeController:對映到/home*請求URL
- IndexController:對映到/index*請求URL
- RegisterController:對映到/register*請求URL
對於MultiActionController處理器類,生成的對映稍微複雜一些。以下示例中的Controller名稱假定為MultiActionController實現:
- AdminController:對映到/admin/*請求URL
- CatalogController:對映到/catalog/*請求URL
如果遵循將Controller實現命名為xxxController的約定,ControllerClassNameHandlerMapping將節省定義和維護潛在的looooong SimpleUrlHandlerMapping(或類似)的繁瑣。
ControllerClassNameHandlerMapping類擴充套件了AbstractHandlerMapping基類,因此可以像許多其他HandlerMapping實現一樣定義HandlerInterceptor例項和其他所有例項。
2. 模型 ModelMap(ModelAndView)
ModelMap類本質上是一個Map,可以使新增要在View中(或在View上)顯示的物件遵循通用的命名約定。考慮以下Controller實現;請注意,在未指定任何關聯名稱的情況下將物件新增到ModelAndView。
public class DisplayShoppingCartController implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
List cartItems = // 獲取CartItem物件列表
User user = // 獲取當前購物的使用者
ModelAndView mav = new ModelAndView("displayShoppingCart"); <-- 邏輯檢視名稱
mav.addObject(cartItems); <-- 沒有名稱,只有物件
mav.addObject(user); <-- 同上
return mav;
}
}
ModelAndView類使用ModelMap類,該類是一個自定義Map實現,在物件新增到物件時自動生成物件的key。確定新增物件的名稱的策略是,在諸如User之類的純量物件的情況下,使用物件類的短類名。以下示例是為放入ModelMap例項的純量物件生成的名稱。
- x.y.User例項:生成名稱為 user 的key
- x.y.Registration例項:生成名稱為 registration 的key
- x.y.Foo例項:生成名稱為 foo 的key
- java.util.HashMap例項:生成名稱為 hashMap 的key。在這種情況下,可能希望明確名稱,因為hashMap不夠直觀。
- null:將導致丟擲IllegalArgumentException。如果要新增的物件(或多個物件)可能為null,那麼還需要明確該名稱。
注:
什麼,沒有自動複數?
Spring Web MVC的約定配置支援不支援自動為陣列、集合生成key。也就是說,不能將Person物件列表新增到ModelAndView並將生成的名稱設定為 people。
這個決定是在經過一番辯論之後作出的,最終的"Principle of Least Surprise"獲勝。
新增Set或List後生成名稱的策略是檢視集合,獲取集合中第一個物件的短類名,並使用List附加到名稱。這同樣適用於陣列,儘管使用陣列時不必檢視陣列內容。示例:
- x.y.User[]陣列:0或多個元素,生成名稱為 userList 的key
- x.y.Foo[]陣列:0或多個元素,生成名稱為 fooList 的key
- java.util.ArrayList:一個或多個x.y.User元素,生成名稱為 userList 的key
- java.util.HashSet:一個或多個x.y.Foo元素,生成名稱為 fooList 的key
- java.util.ArrayList:空集合,addObject(...) 呼叫是無操作的,即根本不會被新增
3. 預設檢視名稱
當沒有顯式提供此類邏輯檢視名稱時,RequestToViewNameTranslator介面確定邏輯檢視名稱。它只有一個實現,DefaultRequestToViewNameTranslator類。
DefaultRequestToViewNameTranslator將請求URL對映到邏輯檢視名稱,如下例所示:
public class RegistrationController implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
// 處理請求...
ModelAndView mav = new ModelAndView();
// 必要時新增資料至model...
return mav;
// 提示,沒有View或設定邏輯檢視名稱
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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.xsd">
<!-- 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.RegistrationController">
<!-- 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或邏輯檢視名稱。 DefaultRequestToViewNameTranslator的任務是從請求的URL生成邏輯檢視名稱。在上述RegistrationController(與ControllerClassNameHandlerMapping一起使用)的情況下,http://localhost/registration.html的請求URL導致由DefaultRequestToViewNameTranslator生成的註冊的邏輯檢視名稱。然後,InternalResourceViewResolver bean將此邏輯檢視名稱解析為/WEB-INF/jsp/registration.jsp檢視。
提示:
不需要顯式定義DefaultRequestToViewNameTranslator bean。如果喜歡DefaultRequestToViewNameTranslator的預設設定,則可以依賴Spring Web MVC DispatcherServlet來例項化此類的例項(如果未明確配置該例項)。
當然,如果需要更改預設設定,則需要顯式配置自己的DefaultRequestToViewNameTranslator bean。有關可配置的各種屬性的詳細資訊,參考DefaultRequestToViewNameTranslator javadoc文件。