在Spring MVC出现之前,有很多MVC的框架,其中最著名的要数Struts。相信,如果Spring MVC相比其它的MVC框架没有优势的话,它就没有出现的必要了。
1、Spring MVC的特性
- 清晰的角色分离。各个角色都可以由一个特别的对象来满足功能:控制器(controller)、校验器(validator)、form对象、model对象、DispatcherServlet、映射处理器、视图解析器等。
- 使用简单的JavaBean配置来构建强大的功能。在同一个Context中更容易关联各个组件,如Web Controller和业务对象、校验器之间可以很容易关联。
- 可灵活扩展、非侵入性。在普通的Controller对象上的一些方法上简单地使用一些注解(如@RequestParam, @RequestHeader, @PathVariable等)就可以提供一个服务。
- 可复用的业务代码,而不需要复制。
- 自定义绑定和校验。可以自定义form表单对象的绑定和应用级别的日期、数字等校验。
- 自定义映射处理器和视图解析器。
- 灵活的model对象传输。Model采用最简单的key/value Map来构成。
- 自定义本地化和主题解析器。
- 强大的JSP的标签库。spring.tld、spring-form.tld。
- 由HTTP request和HTTP Session来决定Bean的生命周期。
2、DispatcherServlet
像其它的MVC框架一样,Spring MVC被设计为:请求驱动、分发请求至Controller、以及其它web应用支持的功能。这些功能由一个核心类DispatcherServlet来支撑,并且它和Spring IoC容器集成,这就可以使用IoC容器的任何功能。DispatcherServlet类的工作原理如下图:
DispatcherServlet代表的就是图中的”Front controller“组件。
DispatcherServlet其实就是一个Servlet,要加载使用它,需要在web.xml中进行配置,每加载一个DispatcherServlet,就会启动一个相关联的WebApplicationContext,这个WebApplicationContext会继承自一个根上下文(它也是一个WebApplicationContext对象)。
2.1、配置DispatcherServlet
2.1.1、在web.xml中配置
<servlet> <servlet-name>golfing</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>golfing</servlet-name> <url-pattern>/golfing/*</url-pattern> </servlet-mapping>
DispatcherServlet会加载一个对应的spring xml配置文件来创建Web Ioc容器WebApplicationContext,默认配置文件的路径为/WEB-INF/[servlet-name]-servlet.xml,上例中的配置文件路径即为:/WEB-INF/golfing-servlet.xml。DispatcherServlet的个别初始化配置参数解释如下:
- contextClass:指定加载的WebApplicationContext的实现类,默认使用的是org.springframework.web.context.support.XmlWebApplicationContext。由FrameworkServlet.DEFAULT_CONTEXT_CLASS指定:
/** * Default context class for FrameworkServlet. * @see org.springframework.web.context.support.XmlWebApplicationContext */ public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
- contextConfigLocation:指定DispatcherServlet加载的对应xml配置文件的路径,相对于web工程的根目录。如/WEB-INF/Spring/servlet.xml。如果指定了此参数,则会忽略namespace参数。
- namespace:指定 WebApplicationContext的命名空间,默认为[servlet-name]-servlet;如果未指定contextConfigLocation参数,则根据此参数来生成配置文件的路径:/WEB-INF/[namespace].xml。源码XmlWebApplicationContext如下:
/** Default config location for the root context */ public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml"; /** Default prefix for building a config location for a namespace */ public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/"; /** Default suffix for building a config location for a namespace */ public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml"; //... @Override protected String[] getDefaultConfigLocations() { if (getNamespace() != null) { return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX}; } else { return new String[] {DEFAULT_CONFIG_LOCATION}; } }
2.1.2、容器WebApplicationContext的加载
在使用了Spring MVC的Web项目中,会加载两类WebApplicationContext。
第一类为Root容器,也可以叫做根容器,它是一个父容器,它保存在ServletContext上下文中,保存它的key由WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE来进行指定:
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
这个父容器是由web.xml中配置的监听器org.springframework.web.context.ContextLoaderListener进行初始加载的。它是由所有的子容器共享的。
<!-- spring ContextLoaderListener --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/Spring/applicationContext.xml</param-value> </context-param>
第二类为和DispatcherServlet相关联的WebApplicationContext。这就是上面提到的子容器,每个配置的DispatcherServlet都会关联一个子容器,子容器之间是不可以共享的,子容器都可以共享父容器中的bean。它也保存在ServletContext下下文中,保存它的key由FrameworkServlet.getServletContextAttributeName()来指定。FrameworkServlet的部分实现代码:
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT."; //... protected WebApplicationContext initWebApplicationContext() { //... if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; } //... public String getServletContextAttributeName() { return SERVLET_CONTEXT_PREFIX + getServletName(); }
如果在开发中需要访问这两类容器,Spring MVC也给开发者提供了工具方法。
访问根容器:WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)。
访问子容器:RequestContextUtils.getWebApplicationContext(ServletRequest request)。
2.2、WebApplicationContext下的功能Bean
- HandlerMapping:请求到处理器的映射,关联处理器拦截器的列表(执行前置后置的拦截程序)。
- HandlerAdapter:处理器,真正的执行请求类。
- HandlerExceptionResolver:异常处理器。将异常映射到视图。
- ViewResolver:视图解析器。将String类型的视图名称映射到View类型的视图对象。
- LocaleResolver:本地化解析器。
- ThemeResolver:主题解析器。
- MultipartResolver:文件上传解析器。
- FlashMapManager:支持将一个请求的数据可用于其它请求。
3、Controller实现
众所周知,Controller在MVC框架中代表的是“C”,它是一个相当重要的部件,它能识别请求输入,并将请求传递至一个Model,处理结束后,为客户端展示View。Spring提供了一种非常简单的方式来实现Controller,在Spring 2.5之后引入了基于注解的配置支持,并得到了广泛的应用,所以本文仅就注解的方式来说明Spring MVC。提前先看一个Controller的示例:
@Controller public class HelloWorldController { @RequestMapping("/helloWorld") public String helloWorld(Model model) { model.addAttribute("message", "Hello World!"); return "helloWorld"; } }
3.1、使用@Controller来定义Controller
使用@Controller注解的普通类就可以扮演一个Controller的角色。Spring MVC不要求Controller必须实现一个Controller基类或者遵循Servlet API。当然在Controller中还是可以使用Servlet容器的一些特性。
仅仅标注了@Controller注解,Spring MVC是识别不了的,需要在第2部分中提到的DispatcherServlet对应的xml配置文件中配置让Spring自动探测注解的类包路径。如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.springframework.samples.petclinic.web"/> <!-- ... --> </beans>
- xml中使用spring-context的schema来进行语法检验,使用<context:component-scan />标签来指定需要探测的类包路径。
3.2、@RequestMapping映射请求
注解@RequestMapping用于映射请求,它既可以注解于类上,也可以注解于方法上,一个完整的的URL映射应该由类上的URL模式和方法上的URL模式合并而成;@RequestMapping有一个变量method用于指定请求的方法,如:method=RequestMethod.GET,先看一个使用它的一些示例:
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
private final AppointmentBook appointmentBook;
@Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
@RequestMapping(method = RequestMethod.GET)
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
@RequestMapping(value="/{day}", method = RequestMethod.GET)
public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
@RequestMapping(value="/new", method = RequestMethod.GET)
public AppointmentForm getNewForm() {
return new AppointmentForm();
}
@RequestMapping(method = RequestMethod.POST)
public String add(@Valid AppointmentForm appointment, BindingResult result) {
if (result.hasErrors()) {
return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
}
}
上例中,getNewForm()方法上对应的完整URL映射应该为/appointments/new。下面来看一下,@RequestMapping的value属性所支持的URL模式:
3.2.1、全路径完全匹配
使用此方式,所请求的URL必须与配置模式完全匹配。示例:
@RequestMapping("/vets")
3.2.2、变量占位路径匹配
在路径中添加变量,使其更有Restful的风格。在这种情况下,要获得路径中的占位变量的值,可在方法的形参中使用注解@PathVariable获取,示例:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET) public String findOwner(@PathVariable String ownerId, Model model) { Owner owner = ownerService.findOwner(ownerId); model.addAttribute("owner", owner); return "displayOwner"; }
当然,@PathVariable也可以指定参数名:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET) public String findOwner(@PathVariable("ownerId") String theOwner, Model model) { // implementation omitted }
3.2.3、模糊匹配模式
@RequestMapping也支持Ant风格的模糊匹配语法,如/myPath/*.do、/owners/*/pets/{petId}。
3.2.4、正则匹配模式
如果有类似这样的一个URL请求:”/spring-web/spring-web-3.0.5.jar”。那怎么样来配置呢?@RequestMapping支持正则表达式的语法:{varName:regex},示例:
@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}") public void handle(@PathVariable String version, @PathVariable String extension) { // ... } }
3.2.5、环境变量的支持
在模式配置中,还可以使用${…}的变量,这个变量可以是在Spring中使用PropertyPlaceholderConfigurer来配置的变量值。
3.2.6、Matrix参数
在URL中使用;号进行分段的参数叫做Matrix参数,如:”/cars;color=red;year=2012″。示例:
// GET /pets/42;q=11;r=22 @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET) public void findPet(@PathVariable String petId, @MatrixVariable int q) { // petId == 42 // q == 11 }
复杂一点的示例:
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @RequestMapping(value = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET) public void findPet( @MatrixVariable Map<String, String> matrixVars, @MatrixVariable(pathVar="petId"") Map<String, String> petMatrixVars) { // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23] // petMatrixVars: ["q" : 11, "s" : 23] }
3.2.7、指定消费媒体类型(Consumable Media Types)
HTTP的请求头Content-Type指定了请求媒体类型,而@RequestMapping可以指定可消费的媒体类型,这就形成了一定的请求约束。使用变量consumes指定,示例:
@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json") public void addPet(@RequestBody Pet pet, Model model) { // implementation omitted }
consumes也可以使用“!text/plain”这种形式,表示非text/plain的媒体类型均可接受。
3.2.8、指定生产媒体类型(Producible Media Types)
和3.2.7类似,此处限制的是HTTP请求头Accept指定的媒体类型,示例:
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json") @ResponseBody public Pet getPet(@PathVariable String petId, Model model) { // implementation omitted }
它也可以使用”!”符号来配置。
3.2.9、请求参数和头参数限制
根据请求参数的存在与否,或是否等于某值来决定是否匹配。使用属性params来指定。可以取值为”myParam”, “!myParam” 或”myParam=myValue”:
@Controller @RequestMapping("/owners/{ownerId}") public class RelativePathUriTemplateController { @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // implementation omitted } }
头信息类似:
@Controller @RequestMapping("/owners/{ownerId}") public class RelativePathUriTemplateController { @RequestMapping(value = "/pets", method = RequestMethod.GET, headers="myHeader=myValue") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // implementation omitted } }
注意,3.2.7和3.2.8中提到的规则会覆盖此处的头规则。
3.2.10、访问RequestMappingHandlerMapping和RequestMappingHandlerAdapter
在注解了@Controller的类中,@RequestMapping注解的方法中使用RequestMappingHandlerMapping和RequestMappingHandlerAdapter来访问映射和处理器信息。如:
@Autowired private RequestMappingHandlerMapping requestMappingHandlerMapping; //... @RequestMapping(value = "/") public void list(HttpServletResponse response) { Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods(); }
3.3、定义处理器方法
通过@RequestMapping注解映射请求,最终的真正执行代码为处理器方法,即@RequestMapping注解的方法,此方法的定义有哪些规范呢?方法的签名非常灵活。
3.3.1、支持的方法参数
- Servlet API中的Request和Response。如:ServletRequest或HttpServletRequest。
- Session对象。如:HttpSession。注意此对象不是线程安全的,可设置RequestMappingHandlerAdapter的 “synchronizeOnSession”属性为true来同步。
- org.springframework.web.context.request.WebRequest或org.springframework.web.context.request.NativeWebRequest。
- java.util.Locale对象。需要在Servlet环境中配置 LocaleResolver。
- java.io.InputStream / java.io.Reader用于访问请求输入流。
- java.io.OutputStream / java.io.Writer用于生成的输出流。
- java.security.Principal用于保存当前的认证用户。
- @PathVariable注解的参数。
- @MatrixVariable注解的参数。
- @RequestParam注解的请求参数。
- @RequestHeader注解的请求头参数。
- @RequestBody注解的请求体参数。
- @RequestPart注解的上传文件请求体参数。请求类型为”multipart/form-data”。
- HttpEntity<?>:请求实体对象,可以访问头信息和内容体。
- java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap:用于响应模型类,存储响应数据。
- org.springframework.web.servlet.mvc.support.RedirectAttributes:用于在redirect的重定向中传递数据。
- Command或form对象。
- org.springframework.validation.Errors / org.springframework.validation.BindingResult对象。
- org.springframework.web.bind.support.SessionStatus可以用来控制Session的清理。
- org.springframework.web.util.UriComponentsBuilder可以用来查询当前请求的主机名、端口号、Schema、Context Path、Servlet Mapping信息。
3.3.2、支持的方法返回类型
- ModelAndView 对象。包含Model和View对象,可以通过它访问@ModelAttribute注解的对象。
- Model 对象。仅包含数据访问,通过 RequestToViewNameTranslator 来隐蔽地决定此请求返回的View视图对象。
- Map对象。和Model相似。
- View对象。仅包含视图数据,而model数据隐含在@ModelAttribute注解标注的对象中、或者Command对象中(方法参数的Model对象)。
- String 值。表示View视图的名称。数据信息的保存同上。
- void 值。当开发都直接操作ServletResponse / HttpServletResponse进行请求跳转,或者View由 RequestToViewNameTranslator 隐蔽地决定时,可使用此返回值。
- 任意对象。如果方法被@ResponseBody注解,可采用此值。Spring会使用HttpMessageConverters将对象转化成文本输出。
- HttpEntity<?>或ResponseEntity<?> 对象。使用此值,Spring也会使用HttpMessageConverters将对象转化成文本输出。
- Callable<?>对象。异步请求时使用。
- DeferredResult<?>对象。当Spring决定使用选择的某个线程产生值时可以使用此对象。
3.3.3、使用@RequestParam来获取请求参数
示例:
@RequestMapping(method = RequestMethod.GET) public String setupForm(@RequestParam("petId") int petId, ModelMap model) { Pet pet = this.clinic.loadPet(petId); model.addAttribute("pet", pet); return "petForm"; }
默认情况下,请求参数为必需的。可修改:@RequestParam(value=”id”, required=false)。
3.3.4、使用@RequestBody进行请求体绑定
注解@RequestBody能将请求body映射至方法的参数中,如:
@RequestMapping(value = "/something", method = RequestMethod.PUT) public void handle(@RequestBody String body, Writer writer) throws IOException { writer.write(body); }
由HttpMessageConverter来转换body至对象。它可以被转换成支持的任意类型。 RequestMappingHandlerAdapter 默认的支持有:
- ByteArrayHttpMessageConverter :byte[]转换。
- StringHttpMessageConverter :转换成String。
- FormHttpMessageConverter :转换成MultiValueMap<String, String>。
- SourceHttpMessageConverter :转换成javax.xml.transform.Source。
转换的参数还可以加上@Valid注解,以进行转换校验,如果校验失败将抛出MethodArgumentNotValidException,此异常默认情况由 DefaultHandlerExceptionResolver进行管理,将会向客户端发送400错误码。
配置自定义的转换器,如:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <util:list id="beanList"> <ref bean="stringHttpMessageConverter"/> <ref bean="marshallingHttpMessageConverter"/> </util:list> </property </bean> <bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter"/> <bean id="marshallingHttpMessageConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"> <property name="marshaller" ref="castorMarshaller" /> <property name="unmarshaller" ref="castorMarshaller" /> </bean> <bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>
3.3.5、使用@ResponseBody进行响应体映射
它和上面提到的@RequestBody类似,只不过它是用来将对象转换成响应体的。转换规则和上面提到的通用。如:
@RequestMapping(value = "/something", method = RequestMethod.PUT) @ResponseBody public String helloWorld() { return "Hello World"; }
3.3.6、使用HttpEntity<?>
HttpEntity用于接收请求数据,和@RequestBody很相似。示例:
@RequestMapping("/something") public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException { String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader")); byte[] requestBody = requestEntity.getBody(); // do something with request header and body HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.set("MyResponseHeader", "MyValue"); return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED); }
3.3.7、在方法上使用@ModelAttribute
@ModelAttribute使用在方法上,就相当于一个公共model池一样(前提是在同一个Controller中),在同一个Controller中,所有@RequestMapping注解的方法执行前,都要先执行这些由@ModelAtrribute注解的方法,将数据存入到当前请求的Model中。它可以单个添加属性,也可以批量地添加。如下示例所示:
// Add one attribute // The return value of the method is added to the model under the name "account" // You can customize the name via @ModelAttribute("myAccount") @ModelAttribute public Account addAccount(@RequestParam String number) { return accountManager.findAccount(number); } // Add multiple attributes @ModelAttribute public void populateModel(@RequestParam String number, Model model) { model.addAttribute(accountManager.findAccount(number)); // add more ... }
@ModelAttribute当然也可以注解到@RequestMapping注解的方法上。
3.3.8、在方法的参数上使用@ModelAttribute
在方法的参数上使用@ModelAttribute注解,被注解的参数会先从请求的Model中查找,如果未找到,则会实例化一个新对象,放入请求的Model中。查找的地方有以下几个地方:
- 存在于@SessionAttributes中。
- 存在于同一个Controller中的注解于方法级别上的@ModelAttribute中。
- Url的模式匹配和转换中。
@RequestMapping(value="/accounts/{account}", method = RequestMethod.PUT) public String save(@ModelAttribute("account") Account account) { }
在此示例中,account可能存在于注册的 Converter<String, Account>中,由{account}映射的Account中。
3.3.9、@SessionAttributes
在Controller类上使用此注解,会从请求的model中查找@SessionAttributes指定的属性名或属性类型,将它们保存到Session中。如:
@Controller @RequestMapping("/editPet.do") @SessionAttributes("pet") public class EditPetForm { // ... }
注:如果Controller使用了接口,则应该将@SessionAttributes或@RequestMapping注解于接口类上。
3.3.10、指定redirect转发时的属性列表
方法的签名中指定一个RedirectAttributes 类型的参数,用于在请求返回 RedirectView时传递数据。
3.3.11、处理”application/x-www-form-urlencoded”数据
Servlet API仅能处理GET和POST的请求。Spring-web为服务提供了PUT及PATCH请求的支持,使用过滤器HttpPutFormContentFilter来实现,web.xml中的配置如下:
<filter> <filter-name>httpPutFormFilter</filter-name> <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class> </filter> <filter-mapping> <filter-name>httpPutFormFilter</filter-name> <servlet-name>dispatcherServlet</servlet-name> </filter-mapping> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet>
这个过滤器可以处理内容类型(content type)为application/x-www-form-urlencoded的PUT及PATCH请求,可以使用方法参数@RequestBody MultiValueMap<String, String>或者HttpEntity<MultiValueMap<String, String>>来获取请求参数。
3.3.12、使用@CookieValue获取Cookie数据
比较简单,直接看示例吧:
@RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) { //... }
3.3.13、使用@RequestHeader获取请求头数据
获取请求头数据,示例:
@RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive) { //... }
3.3.14、方法参数及类型转换
Controller接收到的原始请求数据都是文本类型,而先前使用的@RequestParam、@CookieValue等注解的参数可以为String类型,也可以是其它,Spring默认支持一些简单的数据类型如int、long、Date等类型的自动转换,如果需要对自定义的类型或复杂类型进行转换的话,需要自定义转换器绑定。绑定的方式有两种:局部绑定、全局绑定(它还有两种实现形式):
局部绑定:在@Controller中使用@InitBinder注解方法进行转换器的绑定,用它绑定的方法返回类型应设置成void,方法参数WebDataBinder集合了WebRequest和java.util.Locale,它可以将转换器绑定至Context中:
@Controller public class MyFormController { @InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); } // ... }
上例中的CustomDateEditor就是绑定的转换器。它的作用域为当前Controller。
全局绑定:一种方式是实现一个WebBindingInitializer 接口类,将它绑定至全局配置中,如实现类ClinicBindingInitializer:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="cacheSeconds" value="0" /> <property name="webBindingInitializer"> <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer" /> </property> </bean>
另一种方式是使用@InitBinder注解于@ControllerAdvice注解的类中,来实现全局绑定。
3.3.15、如何支持Last-Modified响应
当请求头中含有If-Modified-Since时,服务端可能需要返回304告诉客户端内容无变化。在Spring中可使用对象WebRequest来实现,如:
@RequestMapping public String myHandleMethod(WebRequest webRequest, Model model) { long lastModified = // 1. application-specific calculation if (request.checkNotModified(lastModified)) { // 2. shortcut exit - no further processing necessary return null; } // 3. or otherwise further request processing, actually preparing content model.addAttribute(...); return "myViewName"; }
检查是否改变后,直接返回null,响应码为304。
4、处理器映射器(Handler mappings)
在Spring配置请求映射器需要使用到HandlerMapping接口,在老的Spring版本中,需要在xml配置一个或多个HandlerMapping来配置请求映射。而新版本在多数使用在Controller中使用@RequestMapping来配置请求映射,这些配置由HandlerMapping的实现类RequestMappingHandlerMapping自动扫描进行加载。需要知道的是,实现了AbstractHandlerMapping的类可以使用它的一些属性来自定义功能:
- interceptors:使用的拦截器列表,拦截器需要实现HandlerInterceptor接口。
- defaultHandler:默认处理器,当在处理器映射中找不到对应的处理器时使用。
- order:指定当前HandlerMapping的序号,此序号会影响Spring搜寻Handler的顺序。
- alwaysUseFullPath:指示匹配时是根据请求的全路径进行匹配(true),还是根据Servlet Mapping的子路径进行匹配,默认为false。比如:Servlet Mapping为/testing/*,而请求路径为/testing/viewpage.html,则Spring会采用/viewpage.html去HandlerMapping中进行匹配。
- urlDecode:默认为true,是否对请求URL进行解码。
4.1、配置拦截器(HandlerInterceptor)
Spring MVC提供了对拦截器的支持,由接口org.springframework.web.servlet.HandlerInterceptor定义,它提供了3个需要实现的方法:preHandle(..)在处理器方法执行之前执行;postHandle(..)在处理器方法执行完成后执行;afterCompletion(..)在请求完成、渲染完成后执行。拦截器的配置如:
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="interceptors"> <list> <ref bean="officeHoursInterceptor"/> </list> </property> </bean>
5、视图解析(Resolving views)
Spring提供了两个主要的接口(ViewResolver和View)来支持视图解析,ViewResolver提供了在视图名称和视图之间的映射关系,View接口用于请求的结果视图展示。
5.1、视图解析器(ViewResolver)
Spring MVC的处理器方法最终都要解析至一个视图(ModelAndView中包含),下面是Spring提供的一些视图解析器:
- AbstractCachingViewResolver:抽象视图解析器提供了对视图的缓存。
- XmlViewResolver:实现ViewResolver接口,提供使用xml配置视图解析映射关系,默认的配置文件为/WEB-INF/views.xml。
- ResourceBundleViewResolver:ResourceBundleViewResolver实现ViewResolver, 在一个ResourceBundle中寻找所需bean的定义。 这个bundle通常定义在一个位于classpath中的属性文件中。默认的属性文件是views.properties。
- UrlBasedViewResolver:UrlBasedViewResolver实现ViewResolver, 将视图名直接解析成对应的URL,不需要显式的映射定义。 如果你的视图名和视图资源的名字是一致的,就可使用该解析器,而无需进行映射。
- InternalResourceViewResolver:作为UrlBasedViewResolver的子类, 它支持InternalResourceView(对Servlet和JSP的包装), 以及其子类JstlView和TilesView。 通过setViewClass方法,可以指定用于该解析器生成视图使用的视图类。 更多信息请参考UrlBasedViewResolver的Javadoc。
- VelocityViewResolver / FreeMarkerViewResolver:作为UrlBasedViewResolver的子类, 它能支持VelocityView(对Velocity模版的包装)和FreeMarkerView以及它们的子类。
- ContentNegotiatingViewResolver:实现ViewResolver接口,基于请求文件名称或者Accept头来解析视图。
看个例子,使用JSP视图,可以使用UrlBasedViewResolver,如:
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
如果返回的视图名称为test,那么视图将会解析至/WEB-INF/jsp/test.jsp。
5.2、视图解析链
Spring支持多个视图同时使用,可以使用order属性来声明解析器的序列,order值越高,在解析链中的位置越靠后,如:
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver"> <property name="order" value="1"/> <property name="location" value="/WEB-INF/views.xml"/> </bean> <!-- in views.xml --> <beans> <bean name="report" class="org.springframework.example.ReportExcelView"/> </beans>
在使用解析器的时候,要查看说明,看使用的解析器是否支持发现不存在的视图,如InternalResourceViewResolver就不支持这种作用,也就是Spring在调用了这个解析器后,就不会再往后使用其它解析器了,如果找不到合适的视图,就会抛出异常。
5.3、Redirect重定向
在某此时候,需要使用重定向。Spring提供了一个RedirectView,可以让控制器返回这个类的一个实例,在这种情况下,DispatcherServlet不会使用通常的视图解析机制,RedirectView会调用HttpServletResponse.sendRedirect()方法。
5.3.1、redirect:前缀
处理器方法返回redirect:前缀这种方式,使得处理器只需要和视图解析器打交道,而不需要关心是需要重定向还是直接渲染,也就是控制器根本就不知道重定向的发生。
5.3.2、forward:前缀
使用包含有forward:前缀的视图名,这些视图名会被UrlBasedViewResolver和它的子类正确解析。 解析的内部实现是生成一个InternalResourceView, 这个视图最终会调用RequestDispatcher.forward()方法,将forward视图名的其余部分作为URL。 所以,当使用InternalResourceViewResolver/InternalResourceView, 并且你所用的视图技术是JSP时,你没有必要使用这个前缀。 但是,当你主要使用其它的视图技术,但仍需要对Servlet/JSP engine处理的页面强制forward时, 这个forward前缀还是很有用的。
5.4、内容协商解析器(ContentNegotiatingViewResolver)
ContentNegotiatingViewResolver本身并不进行视图解析,它只是根据请求的一些信息,来决定使用哪个解析器来解析。一般它有两种机制:后缀名、Accept头,如下示例:
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="mediaTypes"> <map> <entry key="atom" value="application/atom+xml"/> <entry key="html" value="text/html"/> <entry key="json" value="application/json"/> </map> </property> <property name="viewResolvers"> <list> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </list> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" /> </list> </property> </bean> <bean id="content" class="com.springsource.samples.rest.SampleContentAtomView"/>
6、文件上传支持
Spring MVC内置了对文件上传处理的支持,可以使用org.springframework.web.multipart.MultipartResolver来解析文件上传,Spring提供了一个基于Commons FileUpload的实现。默认情况下,Spring是不会自动打开文件上传处理的功能,因为一些开发者想自己来灵活地处理这一功能;如果要开启它,需要在Spring的容器中定义文件上传解析器,Spring会检查每个请求,是否有文件上传,如果有,在容器中定义的MultipartResolver将会被启用。定义示例如下:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- one of the properties available; the maximum file size in bytes --> <property name="maxUploadSize" value="100000"/> </bean>
当然使用这个实现,需要在其classpath路径中增加依赖的jar包commons-fileupload.jar。
当Spring的DispatcherServlet探测到一个multi-part的请求,就会触发定义的MultipartResolver解析器,解析器会将当前HttpServletRequest包装成MultipartHttpServletRequest来支持文件上传功能。直接使用MultipartHttpServletRequest,你可以访问包含在请求中文件信息,实际上也可以自己更灵活地处理文件。
6.1、文件上传的完整示例
先看看html中的文件上传标签定义,特别注意的是,需要将form的enctype属性指定为multipart/form-data。如:
<form method="post" action="/form" enctype="multipart/form-data"> <input type="text" name="name"/> <input type="file" name="file"/> <input type="submit"/> </form>
定义Controller,方法的参数中用于处理文件的参数,可以用MultipartHttpServletRequest,也可以用更直接的MultipartFile,如:
@RequestMapping(value = "/form", method = RequestMethod.POST) public String handleFormUpload(@RequestParam("name") String name, @RequestParam("file") MultipartFile file) { if (!file.isEmpty()) { byte[] bytes = file.getBytes(); // store the bytes somewhere return "redirect:uploadSuccess"; } else { return "redirect:uploadFailure"; } }
7、异常处理
7.1、异常处理器HandlerExceptionResolver
Spring的HandlerExceptionResolver的实现用于处理在controller执行中产生的异常,它和web.xml文件中配置的<error-page>非常相似。实现HandlerExceptionResolver的resolveException(Exception, Handler)方法,该方法返回ModelAndView对象,也可以使用Spring提供的实现类SimpleMappingExceptionResolver或者使用@ExceptionHandler定义方法来实现。@ExceptionHandler注解于@Controller类中,则此异常会应用于当前的Controller中的每个handler方法;如果需要全局生效可以定义于@ControllerAdvice类中。
7.2、注解@ExceptionHandler
HandlerExceptionResolver接口和SimpleMappingExceptionResolver实现可以将异常映射至特定的视图,也可以更方便地设置响应状态码和错误信息。下面是一个使用@ExceptionHandler注解的例子:
@Controller public class SimpleController { // @RequestMapping methods omitted ... @ExceptionHandler(IOException.class) public ResponseEntity<String> handleIOException(IOException ex) { // prepare responseEntity return responseEntity; } }
@ExceptionHandler的value值可以设置为异常类型数组,只要有一个异常匹配,就会执行其注解的方法,如果value值未设定,则将会匹配方法的参数列表类型。
7.3、处理标准的Spring MVC异常
Spring MVC在处理请求时可能会抛出一些异常,SimpleMappingExceptionResolver解析器可以将任意异常解析至一个错误视图,可能实际项目中,需要将特定的异常映射至特定的响应code码。Spring的默认解析器DefaultHandlerExceptionResolver会帮助开发者做这些事情,它会自动注解作用,它的映射关系如下:
Exception | HTTP Status Code |
BindException | 400 (Bad Request) |
ConversionNotSupportedException | 500 (Internal Server Error) |
HttpMediaTypeNotAcceptableException | 406 (Not Acceptable) |
HttpMediaTypeNotSupportedException | 415 (Unsupported Media Type) |
HttpMessageNotReadableException | 400 (Bad Request) |
HttpMessageNotWritableException | 500 (Internal Server Error) |
HttpRequestMethodNotSupportedException | 405 (Method Not Allowed) |
MethodArgumentNotValidException | 400 (Bad Request) |
MissingServletRequestParameterException | 400 (Bad Request) |
MissingServletRequestPartException | 400 (Bad Request) |
NoSuchRequestHandlingMethodException | 404 (Not Found) |
TypeMismatchException | 400 (Bad Request) |
当然,DefaultHandlerExceptionResolver仅能映射code码,对于Restful API服务,这可能不是友好的。在@ControllerAdvice注解的类上使用@ExceptionHandler注解方法来处理异常并返回ResponseEntity是非常方便的,这种方式允许自定义响应内容。详细可查看ResponseEntityExceptionHandler 的API文档。
7.4、使用@ResponseStatus注解业务异常
开发者可能希望自定义的业务异常能让Spring来自动管理,Spring提供了一个简单的机制,可以在自定义的异常上面使用注解@ResponseStatus,映射异常至特定的code码,DispatcherServlet会自动注解ResponseStatusExceptionResolver来处理这件事。
7.5、在Servlet容器中定义<error-page/>
开发者可以在web.xml中定义<error-page/>来映射特殊的错误及code码至特定的url,映射的url可以是jsp、可以是静态页面、可以是一个controller定义的请求。
8、Spring MVC的一些其它配置
Spring提供了一个<mvc:/>命名空间用于配置基于Spring MVC的相关配置,使用<mvc:annotation-driven />会自动帮助开发者注册一些关键的组件类。如:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" 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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:annotation-driven /> </beans>
它会自动注册 RequestMappingHandlerMapping、 RequestMappingHandlerAdapter、 ExceptionHandlerExceptionResolver类,它还会自动注册一些校验机制,HttpMessageConverter数据类型转换类。
8.1、自定义一些数据转换器
如:
<mvc:annotation-driven conversion-service="conversionService"> <mvc:message-converters> <bean class="org.example.MyHttpMessageConverter"/> <bean class="org.example.MyOtherHttpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="formatters"> <list> <bean class="org.example.MyFormatter"/> <bean class="org.example.MyOtherFormatter"/> </list> </property> </bean>
8.2、配置拦截器
<mvc:interceptors> <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" /> <mvc:interceptor> <mapping path="/**"/> <exclude-mapping path="/admin/**"/> <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor" /> </mvc:interceptor> <mvc:interceptor> <mapping path="/secure/*"/> <bean class="org.example.SecurityInterceptor" /> </mvc:interceptor> </mvc:interceptors>
8.3、内容协商
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" /> <bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean"> <property name="favorPathExtension" value="false" /> <property name="favorParameter" value="true" /> <property name="mediaTypes" > <value> json=application/json xml=application/xml </value> </property> </bean>
8.4、视图控制器
<mvc:view-controller path="/" view-name="home"/>
8.5、mvc:default-servlet-handler
<mvc:default-servlet-handler/>
<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>
转载请注明:子暃之路 » Spring主要功能解读(2)-Spring MVC