编辑推荐: |
本文来自于博客园,Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。 |
|
在MVC框架中,控制器(Controller)用于执行业务逻辑并产生模型数据(Model),而视图(View)则用于渲染模型数据
作为一个MVC框架,它们都会封装并提供一些基本的组件和功能以便解放程序员的双手:
--分发请求的前端控制器(SpringMVC中的DispatcherServlet)
--处理请求的业务控制器(SpringMVC中的Controller)
--请求URI与请求处理方法的匹配(SpringMVC中的HandlerMapping)
--请求处理方法的调用(SpringMVC中的HandlerAdapter)
--类型转换问题 —— 前后台数据的流转
--数据校验
--异常配置
--国际化和标签库
--文件上传/下载
SpringMVC的执行流程
![](images/2018040431.png)
1、用户向服务器发送请求,请求被Spring MVC的前端控制器DispatcherServlet截获;
2、DispatcherServlet对请求URL(统一资源定位符)进行解析,得到URI(请求资源标识符)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关对象,包括Handler对象以及Handler对象对应的拦截器,这些对象会被封装到一个 HandlerExecutionChain对象 当中返回;
3、DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter。一个HandlerAdapter会被用于处理多种(一类)Handler,并调用Handler实际处理请求的方法;
4、在调用Handler实际处理请求的方法之前,HandlerAdapter 首先会结合用户配置对请求消息进行转换(例如,将JSON/XML请求消息转换成一个Java对象),然后通过DataBinder将请求中的模型数据绑定到Handler(Controller)对应的处理方法的参数中。在消息转换和数据绑定过程中,Spring MVC会做一些额外的处理,比如数据类型转换、数据格式化工作和数据合法性校验等;
5、Handler调用业务逻辑组件完成对请求的处理后,向DispatcherServlet返回一个ModelAndView对象,ModelAndView对象中应该包含视图名或者视图名和模型;
6、DispatcherServlet根据返回的ModelAndView对象,选择一个合适的ViewResolver(视图解析器)返回给DispatcherServlet;
7、DispatcherServlet调用视图解析器ViewResolver结合Model来渲染视图View;
8、DispatcherServlet将视图渲染结果返回给客户端。
在以上八个步骤中,DispatcherServlet、HandlerMapping、HandlerAdapter和ViewResolver等核心组件相互配合来完成Spring MVC 请求-响应的整个工作流程。这些核心组件所完成的工作对开发者是透明的,也就是说,开发者并不需要关心这些组件是如何工作的,开发者只需要专注在Handler(Controller)当中完成对请求的业务逻辑处理即可,这也正是MVC框架的价值体现。
2、SpringMVC的消息转换器机制:HttpMessageConveter
在调用模型组件处理业务逻辑时常常是以一个个有业务意义的对象为处理维度的,那么在请求消息到达SpringMVC和响应消息从SpringMVC出去的过程中就存在一个消息转换的问题,即请求消息(字符串)到Java对象的转换问题。
在SpringMVC中,采用的是HttpMessageConverter机制。具体而言,HttpMessageConveter负责将请求信息转换为一个对象,并通过DataBinder组件将该对象绑定请求方法的参数中或输出为响应信息。
在SpringMVC中可以使用@RequestBody和@ResponseBody两个注解分别完成请求消息到对象和对象到响应消息的转换,而底层这种灵活的消息转换机制就是由HttpMessageConverter支持的。
SpringMVC的请求处理流程可概括如下:当SpringMVC收到请求时,前端控制器DispatcherServlet会根据请求URI调用HandlerMapping将请求分发给具有一系列拦截器和业务控制器Controller的HandlerExecutionChain对象,然后该请求将依次通过该执行链的各个拦截器并最终到达业务控制器Controller。在业务控制器Controller处理该请求前,HandlerAdapter会对请求消息作进一步转换和解析并绑定到业务控制器Controller的具体请求处理方法上,然后该方法根据结合请求参数调用一系列业务逻辑组件去处理请求,并将包含模型数据和具体视图的处理结果交给视图解析器ViewResolver进行渲染,最终DispatcherServlet将视图渲染结果返回给客户端。
web.xml中配置前端控制器 DispatcherServlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- 配置 spring上下文 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- 监听所有的servlet和filter -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置的前端控制器 -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- SpringMVC配置文件路径和名称设定 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<!-- 第一个加载 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<!-- 拦截所有请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app> |
ervlet-context.xml中指定路径配置SpringMVC的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
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">
<!-- 配置mvc的各种bean(资源,视图) -->
<!-- 注解驱动 -->
<annotation-driven />
<!-- 静态资源访问 -->
<resources mapping="/resources/**" location="/resources/" />
<!-- 视图解析 -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<!-- 注解扫描包 -->
<context:component-scan base-package="com.wph.springmvc" />
</beans:beans> |
root-context.xml中指定路径配置 非MVC的资源 的配置文件
<?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: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-3.1.xsd">
<!-- 配置 非mvc的bean 比如 Database -->
</beans> |
SpringMVC 数据绑定机制的应用:使用注解完成请求参数绑定
- 处理Request URI 部分(这里指URI Template中Variable,不含QueryString部分)的注解: @PathVariable;
- 处理Request Header部分的注解: @RequestHeader, @CookieValue;
- 处理Request Body部分的注解:@RequestParam,@RequestBody;
- 处理Attribute类型的注解: @SessionAttributes, @ModelAttribute;
1、@PathVariable
当使用@RequestMapping URI template 样式映射时, 即 someUrl/{paramId}, 这时的paramId可通过 @Pathvariable注解绑定它传过来的值到方法的参数上。
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping("/pets/{petId}")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
} |
上面代码把URI template中变量ownerId的值和petId的值,绑定到方法的参数上。若方法参数名称和需要绑定的uri template中变量名称不一致,需要在@PathVariable(“name”)指定uri template中的名称。
2、@RequestHeader,@CookieValue
(1). @RequestHeader
@RequestHeader 注解可以把Request请求header部分的值绑定到方法的参数上,如下所示 :
Request Header:
// 这是一个 Request 的header部分
Host localhost:8080
Accept text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300 |
使用@RequestHeader注解获取Request Header中的一些字段:
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
} |
上面的代码将把request header部分的Accept-Encoding的值绑定到参数encoding上, 把Keep-Alive header的值绑定到参数keepAlive上。
(2). @CookieValue
@CookieValue 可以把Request header中关于cookie的值绑定到方法的参数上,例如有如下Cookie值:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84 |
通过下列方式就可以把JSESSIONID的值绑定到参数cookie上,如下:
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
//...
} |
3、@RequestParam,@RequestBody,@ResponseBody
(1). @RequestParam
- 常用来处理简单类型的绑定,通过Request.getParameter() 获取的String可直接转换为简单类型的情况( String–> 简单类型的转换操作由ConversionService配置的转换器来完成);因为其内部使用request.getParameter()方式获取参数,所以可以处理get方式中queryString的值,也可以处理post方式中body data的值;
- 用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容,提交方式GET、POST;
@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {
// ...
@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";
}
// ... |
(2). @RequestBody
@RequestBody通过使用HandlerAdapter默认配置的HttpMessageConverters来解析Request请求的Body部分数据并将相应的数据绑定到Controller中方法的参数上,其常用来处理Content-Type不是application/x-www-form-urlencoded编码的内容,例如application/json,application/xml等。注意,request的body部分的数据编码格式由header部分的Content-Type指定。@RequestBody的具体使用场景如下:
1). GET、POST方式提时,根据Request Header Content-Type的值来判断:
- application/x-www-form-urlencoded, 可选(即非必须,因为这种情况的数据@RequestParam, @ModelAttribute也可以处理,当然@RequestBody也能处理);
- multipart/form-data, 不能处理(即使用@RequestBody不能处理这种格式的数据);
- 其他格式, 必须(其他格式包括application/json,application/xml等。这些格式的数据必须使用@RequestBody来处理);
2). PUT方式提交时,根据Request Header Content-Typee的值来判断:
- application/x-www-form-urlencoded, 必须;
- multipart/form-data, 不能处理;
- 其他格式, 必须;
@RequestMapping(value = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
writer.write(body);
} |
(3). @ResponseBody
@ResponseBody注解用于将Controller的方法返回的对象通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区,其在返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用,例如:
@RequestMapping(value="/testRequestBody")
// 将Controller的方法返回的对象通过适当的消息转换器转换为指定格式后写入到Response对象的body数据区
@ResponseBody
public Book setJson(@RequestBody Book book,
HttpServletResponse response) throws Exception{ // @RequestBody根据json数据,转换成对应的Object
book.setAuthor("肖文吉");
logger.info(JSONObject.toJSONString(book));
return book;
} |
4、@SessionAttributes,@ModelAttribute
(1). @SessionAttributes
@SessionAttributes注解用来绑定HttpSession中的attribute对象的值,便于在方法中的参数里使用,例如:
@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
// ...
} |
(2). @ModelAttribute
@ModelAttribute注解有两个用法,一个是用于方法上,一个是用于参数上。用于方法上时,被其注释的方法会在Controller每个方法执行前被执行,因此通常用来在处理@RequestMapping之前为请求绑定需要从后台查询的model;用于参数上时,用来通过名称对应把相应名称的值绑定到注解的参数bean上,其中要绑定的值常常来源于:
- @SessionAttributes 启用的attribute 对象上;
- @ModelAttribute 用于方法上时指定的model对象;
@ModelAttribute在方法上使用示例:
// 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);
}
|
这种方式实际的效果就是在调用@RequestMapping的方法之前向request对象的model里put(“account”, Account)。
@ModelAttribute用在参数上的示例代码:
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute Pet pet) {
}
|
首先查询 @SessionAttributes有无绑定的Pet对象,若没有则查询@ModelAttribute方法层面上是否绑定了Pet对象,若没有则将URI template中的值按对应的名称绑定到Pet对象的各属性上。
|