对于很多项目来说,严格遵从已有惯例和使用合理的缺省选项大概是这些项目需要的……现在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 a List of CartItem objects
User user = // get the User 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。