对于很多项目来说,严格遵从已有惯例和使用合理的缺省选项大概是这些项目需要的……现在Spring Web MVC明确的支持了这种惯例优先原则的主旨。
这意味着,如果建立了一套命名规范,诸如此类,就可以显著地减少系统所需配置项目的数量,
来建立处理器映射、视图解析器、ModelAndView
实例,等等。
这为快速原型开发提供了很大方便。同时提供了一定程度的(通常是好事情)代码库的一致性,进而可以从中选择并发展为成型产品。
Spring分发版本包含了一个展现了惯例优先原则支持的Web应用程序,我们将在这一节描述这一原则。
这个应用程序可以在samples/showcases/mvc-convention
目录中找到。
惯例优先原则支持体现在MVC的三个核心领域:模型、视图和控制器。
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
在它的应用上下文中找出所有不同的处理器(handler)(或Controller
)bean,
并去掉名称中的Controller
,来定义它的处理器映射。
让我们看更多的例子,这样其中的中心思想就马上就清楚了。
WelcomeController
映射到“/welcome*
”请求URL
HomeController
映射到“/home*
”请求URL
IndexController
映射到“/index*
”请求URL
RegisterController
映射到“/register*
”请求URL
DisplayShoppingCartController
映射到“/displayshoppingcart*
请求URL
(注意大小写——全部小写——对于驼峰式大小写(第一个词的首字母小写,随后的每个词首字母大写)的Controller
类名。)
当控制器是MultiActionController
处理器类时,生成的映射就(有一点点)更为复杂,但幸而没有更难理解。
下面例子中的几个Controller
名字假设都是MultiActionController
的实现。
AdminController
映射到“/admin/*
”请求URL
CatalogController
映射到“/catalog/*
”请求URL
如果遵循漂亮而且标准的规范把你的Controller
实现命名为xxxController
,
那么ControllerClassNameHandlerMapping
将使你免于忍受必须首先定义它们,
然后还要维护冗——长——的——SimpleUrlHandlerMapping
(或者类似的东西)的枯燥。
ControllerClassNameHandlerMapping
是AbstractHandlerMapping
的子类,
从而使你能够像对待大量其他HandlerMapping
实现一样的定义HandlerInterceptor
实例和其他任何东西。
ModelMap
类首先是一个绚丽的Map
实现,
它可以使新增的将要显示在View
中(或上)的对象也遵循同一命名规范。
考虑下面的Controller
实现,注意对象被加入ModelAndView
,
而并没有指定任何名称。
public class DisplayShoppingCartController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { List cartItems = // get aList
ofCartItem
objects User user = // get theUser
doing the shopping ModelAndView mav = new ModelAndView("displayShoppingCart"); <-- the logical view name mav.addObject(cartItems); <-- look ma, no name, just the object mav.addObject(user); <-- and again ma! return mav; } }
ModelAndView
类使用的ModelMap
类是一个自定义的Map
的实现。
当有一个新对象加入的时候,它就被用于为这个对象自动生成一个键。
决定某个加入的对象的名字的策略是,当它是一个标量对象(scalar object),比如User
时,
就使用这个对象所属类的简短类名。下面的几个例子中,几个为标量对象生成的名字被加入ModelMap
实例中。
将会为一个新增的x.y.User
实例生成“user
”作为名称
将会为一个新增的x.y.Registration
实例生成“registration
”作为名称
将会为一个新增的x.y.Foo
实例生成“foo
”作为名称
将会为一个新增的java.util.HashMap
实例生成“hashMap
”作为名字(在这个情形下你可能想要让名字更加明确一些,因为“hashMap
不太直观)。
新增null
将会导致抛出一个IllegalArgumentException
。
如果正在加入的这个(或这些)对象可能潜在的是null
的话,就要让名字更明确一些。
在加入一个Set
、List
或者对象数组之后,
生成名称的策略是深入这个集合,取出集合中第一个对象的简短类名,并使用这个名称并在后面加上List
。
一些例子将会让集合的名称生成方式更清晰……
将会为一个新增的包含了一个或多个x.y.User
元素的x.y.User[]
实例生成“userList
”作为名称
将会为一个新增的包含了一个或多个x.y.User
元素的x.y.Foo[]
实例生成“fooList
”作为名称
将会为一个新增的包含了一个或多个x.y.User
元素的java.util.ArrayList
实例生成“userList
”作为名称
将会为一个新增的包含了一个或多个x.y.Foo
元素的java.util.HashSet
实例生成“fooList
”作为名称
一个空java.util.ArrayList
根本不会被加入(也就是说,addObject(..)
调用其实什么都没做)。
RequestToViewNameTranslator
接口的功能是当没有显式的提供这样一个逻辑视图名称的时候,
确定一个逻辑的View
名称。
这个接口只有一个实现,精明的命名为DefaultRequestToViewNameTranslator
。
为了解释DefaultRequestToViewNameTranslator
将请求的URL映射到逻辑的视图名的方式,
最好还是求助于一个例子。
public class RegistrationController implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
// process the request...
ModelAndView mav = new ModelAndView();
// add data as necessary to the model...
return mav;
// notice that no 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.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
或者逻辑视图名称。
而是把从请求的URL生成一个逻辑视图名称的任务交给了DefaultRequestToViewNameTranslator
。
在上面这个RegistrationController
与ControllerClassNameHandlerMapping
联合使用的例子中,
一个“http://localhost/registration.html
”请求URL将会由DefaultRequestToViewNameTranslator
生成一个“registration
”逻辑视图名称。
这个逻辑视图名称接下来就会被InternalResourceViewResolver
bean解析为“/WEB-INF/jsp/registration.jsp
”视图。
甚至不需要显式的定义一个DefaultRequestToViewNameTranslator
bean。
如果DefaultRequestToViewNameTranslator
的缺省设置符合你的要求,
就可以依赖这样一个事实,Spring Web MVC的DispatcherServlet
将会在没有显式配置的情况下自动的生成这个类的一个实例。
当然,如果需要修改缺省设置,那么就需要显式的配置自己的DefaultRequestToViewNameTranslator
bean。
关于可以设置的各种属性的细节,请参阅DefaultRequestToViewNameTranslator
的相当详细的Javadoc。