Spring提供了一个相当灵活和可扩展的MVC实现——Spring MVC。Spring MVC框架主要由DispatcherServlet、处理器映射(handler)、处理器(controller)、视图解析器(ViewResolver)、视图(view)组成。
Spring MVC的处理过程从一个HTTP请求开始:
1)DispatcherServlet接收到请求后,根据对应配置文件中配置的处理器映射,找到对应的处理器映射(HandlerMapping),根据配置的映射规则,找到对应的处理器(Handler)。
2)调用相应处理器中的处理方法,处理该请求,处理器处理结束后会将一个ModelAndView类型的数据传给DispatcherServlet,这其中包含了处理结果的视图和视图中要使用的数据。
3)DispatcherServlet根据得到的ModelAndView中的视图对象,找到一个合适的ViewResolver(视图解析器),根据视图解析器的配置,DispatcherServlet将视图要显示的数据传给对应的视图,最后给浏览器构造一个HTTP响应。
DispatcherServlet是整个Spring MVC的核心。它负责接收HTTP请求组织协调Spring MVC的各个组成部分。其主要工作有以下三项:
1)截获符合特定格式的URL请求。
2)初始化DispatcherServlet上下文对应的WebApplicationContext,并将其与业务层、持久化层的WebApplicationContext建立关联。
3)初始化Spring MVC的各个组成组件,并装配到DispatcherServlet中。
我们从一个多视图、多控制器的配置入手来理解Spring MVC.
使用多视图、多控制器时,我们常采用的处理器映射是SimpleUrlHandlerMapping,并采用路径匹配算法将web请求映射到正确的处理器(handler)上。视图解析器根据使用的模板、JSP选择合适的解析器,当多个视图解析器一起使用,Spring可以组成一个视图解析链顺序查找,直到找到对应的 “视图解析器”。
1)首先在web.xml中配置多个Dispatcher,不同的视图转发不同,如下:
<servlet-name>sample</servlet-name>
<servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class>
</servlet>
<servlet-mapping> <servlet-name>sample</servlet-name>
<url-pattern>/**/*.jstl</url-pattern>
</servlet-mapping>
<servlet-mapping> <servlet-name>sample</servlet-name>
<url-pattern>/**/*.form</url-pattern>
</servlet-mapping>
上面的web.xml设置允许所有以.jstl和.form结尾的请求都由这个sample DispatcherServlet处理。
2)配置处理器映射
<beans>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/jstl/*.form=myFormController
/ex/*.jstl=myJstlController
</value>
</property>
</bean>
<bean id="myJstlController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController">
<property name="validator"> <!-- 增加校验器 -->
<bean class="sample.myJstlValidator" />
</property> </bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- property name="prefix"><value>/WEB-INF/sample/jsp/</value></property -->
<property name="suffix"><value>.jstl</value></property>
<property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>
<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="suffix"><value>.form</value></property>
<property name="viewClass">
<value>org.springframework.web.servlet.view.freemarker.FreeMarkerView</value>
</property>
<property name="contentType"><value>text/html; charset=utf-8</value></property>
</bean>
</beans> 如果使用视图链,那么freemarker和jsp/jstl多视图配置文件如下:
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views" />
<property name="order" value="0" />
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/sample/jsp/" />
<property name="suffix" value=".jstl" />
</bean>
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath">
<value>/WEB-INF/sample/freemarker/</value>
</property>
</bean>
上面配置中,ResourceBundleViewResolver设置了order属性,用于定义顺序,注意,一般将jsp处理放在视图处理链的未尾。
ResourceBundleViewResolver还需要有它的配置文件(这里配置的是views.properties文件),并把它放置在classpath的下。views.properties文件内容如下:
sample.(class)=org.springframework.web.servlet.view.freemarker.FreeMarkerView
sample.url=sample.ftl
文件内容中的sample是spring mvc返回字符串的名称,url对应freemarkerConfig中设置的templateLoaderPath下的文件路径。
对多视图、多控制器一个更好的选择是采用注解方式配置。注解方式配置中有两个主要的类:
DefaultAnnotationHandlerMapping,支持通过直接扫描Controller类中的Annotation来确定请求映射关系。
AnnotationMethodHandlerAdapter,支持对某个 Controller 注册属性编辑器,包括基本数据类型及其包裹类的属性编辑器、String 属性编辑器、JavaBean 的属性编辑器等,以及注册一些自定义的属性编辑器,如时间格式编辑器等,该类在实际调用handlermethod前使用属性编辑器对其参数进行处理。
注解方式还有一种简写方式:<mvc:annotation-driven />。使用<mvc:annotation-driven />,Spring会自动注册DefaultAnnotationHandlerMapping 与AnnotationMethodHandlerAdapter 这两个bean。
注:在spring mvc 3.1中,这两个Bean对应变如下:
DefaultAnnotationHandlerMapping -> RequestMappingHandlerMapping
AnnotationMethodHandlerAdapter -> RequestMappingHandlerAdapter
AnnotationMethodHandlerExceptionResolver -> ExceptionHandlerExceptionResolver
具体配置如下:
<!-- 对web包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能 -->
<context:component-scan base-package="sample.contoller"/>
<!--Spring3.1开始的注解 HandlerMapping -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="interceptors">
<list>
<bean class="sample.MyInteceptor"></bean> <!-- 进行安全校验 -->
</list>
</property>
</bean>
<!--Spring3.1开始的注解 HandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="webBindingInitializer">
<bean class="sample.MyBindingInitializer"/><!-- 对参数进行格式化处理 -->
</property>
</bean>
或者使用简写方式:
<!-- 拦截器 -->
<mvc:interceptors>
<bean class="sample.MyInteceptor" /> <!-- 进行安全校验 -->
</mvc:interceptors>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="sample.MyDataConverter" /><!-- 对参数进行格式化处理 -->
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService" />
配置完成以上的文件后,我们来看看Controller的书写,Spring提供了一些关于合法的请求处理方法(见参考手册):
1) 标准格式(跟Controller接口定义的一样)
public ModelAndView displayCatalog(HttpServletRequest, HttpServletResponse)
2) 下面这个方法接收Login参数,该参数中包含从请求中抽取出来的信息。
public ModelAndView login(HttpServletRequest, HttpServletResponse, Login)
3) 下面这个方法要求请求中已经存在合法的session对象。
public ModelAndView viewCart(HttpServletRequest, HttpServletResponse, HttpSession)
4) 下面这个方法接受一个Product参数,这个参数包含从请求中抽取出来的信息,并且要求请求中已经存在一个 合法的session对象。注意参数的顺序很重要:session必须是第三个参数,而绑定参数必须是final的,并位于session之后。
public ModelAndView updateCart(HttpServletRequest, HttpServletResponse, HttpSession, Product)
5) 下面这个方法声明返回void类型,这说明它会直接写response。
public void home(HttpServletRequest, HttpServletResponse)
6) 下面这个方法返回Map,表明视图解析器应该从请求中抽取视图名,而返回数据将被放入model。
public Map list(HttpServletRequest, HttpServletResponse)
7)可以声明自己的方法来处理请求处理过程中产生的Exceptions。该方法的签名与请求处理方法的签名类似:第一个参数必须是 HttpServletRequest,第二个参数必须是HttpServletResponse。不过与请求处理 方法不同的是,该方法的名字可以任意,具体匹配策略由该方法的第三个参数(参数类型必须是一种Exception)决定。Spring根据最接近的 异常类型进行匹配。下面是一个这种异常处理方法签名的例子:
public ModelAndView processException(HttpServletRequest, HttpServletResponse, IllegalArgumentException)
Spring引入Annotation来完成请求-响应的映射关系,可以让一个Controller来处理多个请求。并且SpringMVC在响应方法上,可以支持多种多样不同的参数类型和返回值类型。例如,当参数类型为Model时,SpringMVC将会自动将请求参数封装于Model内部而传入请求方法;当返回值类型是String时,直接表示SpringMVC需要返回的视图类型和视图内容。
请求处理方法为标注了 @RequestMapping 注解的 Controller 方法,Spring MVC 允许极其灵活的请求处理方法签名方式。对于方法入参来说,它允许多种类型的入参,通过下表进行说明(参考:使用 Spring 2.5 基于注解驱动的 Spring MVC http://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/):
请求处理方法入参的可选类型 | 说明 |
Java 基本数据类型和 String | 默认情况下将按名称匹配的方式绑定到 URL 参数上,可以通过 @RequestParam 注解改变默认的绑定规则 |
request/response/session | 既可以是 Servlet API 的也可以是 Portlet API 对应的对象,Spring 会将它们绑定到 Servlet 和 Portlet 容器的相应对象上 |
org.springframework.web.context.request.WebRequest | 内部包含了 request 对象 |
java.util.Locale | 绑定到 request 对应的 Locale 对象上 |
java.io.InputStream/java.io.Reader | 可以借此访问 request 的内容 |
java.io.OutputStream / java.io.Writer | 可以借此操作 response 的内容 |
任何标注了 @RequestParam 注解的入参 | 被标注 @RequestParam 注解的入参将绑定到特定的 request 参数上 |
java.util.Map / org.springframework.ui.ModelMap | 它绑定 Spring MVC 框架中每个请求所创建的潜在的模型对象,它们可以被 Web 视图对象访问(如 JSP) |
命令/表单对象(注:一般称绑定使用 HTTP GET 发送的 URL 参数的对象为命令对象,而称绑定使用 HTTP POST 发送的 URL 参数的对象为表单对象) | 它们的属性将以名称匹配的规则绑定到 URL 参数上,同时完成类型的转换。而类型转换的规则可以通过 @InitBinder 注解或通过 HandlerAdapter 的配置进行调整 |
org.springframework.validation.Errors / org.springframework.validation.BindingResult | 为属性列表中的命令/表单对象的校验结果,注意检验结果参数必须紧跟在命令/表单对象的后面 |
org.springframework.web.bind.support.SessionStatus | 可以通过该类型 status 对象显式结束表单的处理,这相当于触发 session 清除其中的通过 @SessionAttributes 定义的属性 |
方法返回参数,通过下表进行说明:(参考:使用 Spring 2.5 基于注解驱动的 Spring MVC http://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/)
请求处理方法入参的可选类型 | 说明 |
void | 此时逻辑视图名由请求处理方法对应的 URL 确定,如以下的方法: @RequestMapping("/welcome.do") public void welcomeHandler() { } 对应的逻辑视图名为“welcome” |
String | 此时逻辑视图名为返回的字符,如以下的方法: @RequestMapping(method = RequestMethod.GET) public String setupForm(@RequestParam("ownerId") int ownerId, ModelMap model) { Owner owner = this.clinic.loadOwner(ownerId); model.addAttribute(owner); return "ownerForm"; } 对应的逻辑视图名为“ownerForm” |
org.springframework.ui.ModelMap | 和返回类型为 void 一样,逻辑视图名取决于对应请求的 URL,如下面的例子: @RequestMapping("/vets.do") public ModelMap vetsHandler() { return new ModelMap(this.clinic.getVets()); } 对应的逻辑视图名为“vets”,返回的 ModelMap 将被作为请求对应的模型对象,可以在 JSP 视图页面中访问到。 |
ModelAndView | 当然还可以是传统的 ModelAndView |
我们常用的书写方式:
@RequestMapping({"/sample/enableshop/{id}"})
public String enableShop(HttpServletRequest request, HttpServletResponse response, @PathVariable("id")String shopId) ; //返回url地址
@RequestMapping({"/sample/addproduct"})
public String addProduct(HttpServletRequest request, HttpServletResponse response, Product product) ; //返回url地址
@RequestMapping(value = {"/sample/savestorestatus"}, method = {org.springframework.web.bind.annotation.RequestMethod.GET})
public @ResponseBody String saveShopStatus(ModelMap modelMap, HttpServletRequest request, HttpServletResponse response);
@RequestMapping(value = {"/sample/getcurrentorders"}, method = {
org.springframework.web.bind.annotation.RequestMethod.GET})
public void getCurrentOrders(HttpServletRequest request, HttpServletResponse response);//可以使用Ajax方式来调用,数据采用JSON返回; @RequestMapping(value = {"/sample/saveshopparams"}, method = {
org.springframework.web.bind.annotation.RequestMethod.POST})
public void saveShopParams(HttpServletRequest request, HttpServletResponse response) ; //可用使用Ajax方式来调用
访问静态资源
我们可以用Web服务器的defaultServlet来处理静态文件,也可用Spring框架来处理静态文件。使用Spring来处理,可以在配置中加入以下代码:
<mvc:default-servlet-handler/>
这样spring会用默认的Servlet来响应静态文件,(DefaultServletHttpRequestHandler在容器启动是会使用主流web容器默认servlet的名称列表自动查找容器的默认servlet,包括Tomcat, Jetty, Glassfish, JBoss, Resin, WebLogic, and WebSphere。),如果为默认servlet配置了新的名称,或者这个容器servlet名字不在spring列表中是,必须显式配置默认servlet的名字,如下:
<mvc:default-servlet-handler default-servlet-name="customServlet"/>
或者使用mvc:resources方式来处理,如下:
<mvc:resources mapping="/images/**" location="/images/" />
使用<mvc:resources/>元素把images/**映射到ResourceHttpRequestHandler进行处理,location指定静态资源的位置.可以是web application根目录下、jar包里面,这样可以把静态资源压缩到jar包中。cache-period 可以使得静态资源进行web cache
文件上传
Spring是通过MultipartResolver来处理文件上传,它支持 Commons FileUpload 和 COS FileUpload。 想要使用Spring的multipart支持文件上传,首先需要在web应用的上下文中添加multipart解析器。这样,对每个请求就会检查是否包含multipart,当请求中包含multipart,上下文中定义的MultipartResolver就会解析它。
<!-- 添加上传拦截 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1048576"/>
<property name="defaultEncoding" value="utf-8"/>
</bean>在controller中增加处理逻辑,如下
@RequestMapping(value = "/uploadfile", method = RequestMethod.POST)
public String handleFormUpload(@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
// ….
}
方法验证
如前面在controller中增加校验类sample.MyJstlValidator,该类实现了Validate接口。如果使用注解方式,则首先实现一个Validator接口的类,如下:
@Component("myJstlValidator")
public class MyJstlValidator implements Validator {
@SuppressWarnings("unchecked")
@Override
public boolean supports(Class clazz) {
return …;
}
@Override
public void validate(Object object, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "name","field.required");
}
}
在Controller中通过注解获取:
@Resource(name = " myJstlValidator ")
private Validator validator;
...
@RequestMapping("/jstl/save.form")
public ModelAndView save(Product product, BindingResult result) {
this.validator.validate(product, result);
if (result.hasErrors()) {
return new ModelAndView("input");
}
….
}
国际化
首先配置资源文件绑定
<
!-- 资源文件绑定器 -->
<bean id="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n/Messages" />
<property name="useCodeAsDefaultMessage" value="true" />
</bean>
配置好这个bean后,在JSP中设置JSTL支持的渲染器,那么在JSP文件中使用fmt标记就可以实现客户浏览器语言国际化了。如:
<fmt:message key="sample.title" />
其中的sample.title和你的资源文件对应.
另外一种方式是使用spring自带的标签显示国际化信息,如:
<spring:message code="sample.title" />
实际操作中,针对不同语言要求进行切换的方式大多有以下几种:
1. 根据语言种类,部署时手动替换*.properties文件
2. 根据当前系统Locale设置,自动匹配(如上例)
3. 根据客户浏览器语言设定,自动切换界面语种。
前两种部署方式依赖于部署时的设定和系统环境。第三种最为灵活,系统一旦部署后,仍可自动根据客户浏览器的语言设定来自动切换语言种类。Spring中提供了以下三种语言自动切换机制的实现(均实现了LocaleResolver接口):
1) AcceptHeaderLocaleResolver:根据浏览器Http Header中的accept-language域判定。
2)SessionLocaleResolver:根据用户本次会话过程中的语言设定决定语言种类(一般用户在登录时选择语言种类,此后本次登录周期内统一使用该语言设定)。
3) CookieLocaleResolver:根据Cookie判定用户的语言设定(Cookie中保存着用户前一次的语言设定参数)。
如果改变locale设置时,需要增加LocaleChangeInterceptor拦截器,这个时候,但凡有了符合UrlMapping的请求,就会被拦截,并且开始配置国际化参数。