Spring MVC
Spring MVC
Spring MVC(全称 Spring Web MVC)是 Spring 框架提供的一款基于 MVC 模式的轻量级 Web 开发框架。
Spring MVC 使用 MVC 架构模式的思想,将 Web 应用进行职责解构,把一个复杂的 Web 应用划分成模型(Model)、控制器(Contorller)以及视图(View)三层,有效地简化了 Web 应用的开发,降低了出错风险,同时也方便了开发人员之间的分工配合。
Spring MVC 各层的职责如下:
- Model:负责对请求进行处理,并将结果返回给 Controller;
- View:负责将请求的处理结果进行渲染,展示在客户端浏览器上;
- Controller:是 Model 和 View 交互的纽带;主要负责接收用户请求,并调用 Model 对请求处理,然后将 Model 的处理结果传递给 View。
Spring MVC 本质是对 Servlet 的进一步封装,其最核心的组件是 DispatcherServlet,它是 Spring MVC 的前端控制器,主要负责对请求和响应的统一地处理和分发。Controller 接收到的请求其实就是 DispatcherServlet 根据一定的规则分发给它的。
Spring MVC 框架内部采用松耦合、可插拔的组件结构,具有高度可配置性,比起其他的 MVC 框架更具有扩展性和灵活性。此外,Spring MVC 的注解驱动(annotation-driven)和对 REST 风格的支持,也是它最具有特色的功能。
Spring MVC 是 Spring 框架的众多子项目之一,自 Spring 框架诞生之日起就包含在 Spring 框架中了,它可以与 Spring 框架无缝集成,在性能方面具有先天的优越性。对于开发者来说,Spring MVC 的开发效率要明显高于其它的 Web 框架,因此 Spring MVC 在企业中得到了广泛的应用,成为目前业界最主流的 MVC 框架之一。
Spring MVC 的常用组件如下表:
| 组件 | 提供者 | 描述 |
|---|---|---|
| DispatcherServlet | 框架提供 | 前端控制器,它是整个 Spring MVC 流程控制中心,负责统一处理请求和响应,调用其他组件对用户请求进行处理。 |
| HandlerMapping | 框架提供 | 处理器映射器,根据请求的 url、method 等信息查找相应的 Handler。 |
| Handler | 开发人员提供 | 处理器,通常被称为 Controller(控制器)。它可以在 DispatcherServlet 的控制下,对具体的用户请求进行处理。 |
| HandlerAdapter | 框架提供 | 处理器适配器,负责调用具体的控制器方法,对用户发来的请求来进行处理。 |
| ViewResolver | 框架提供 | 视图解析器,其职责是对视图进行解析,得到相应的视图对象。常见的视图解析器有 ThymeleafViewResolver、InternalResourceViewResolver 等。 |
| View | 开发人员提供 | 视图,它作用是将模型(Model)数据通过页面展示给用户。 |
Start
步骤:
- 加入Jar包
- 在web.xml中配置DispatcherServlet
- 加入Spring MVC的配置文件
- 编写处理请求的处理器,并标识为处理器
- 编写视图
目录结构如下:

加入Jar包:
commons-logging-1.2.jar
spring-aop-4.3.2.RELEASE.jar
spring-beans-4.3.2.RELEASE.jar
spring-context-4.3.2.RELEASE.jar
spring-core-4.3.2.RELEASE.jar
spring-expression-4.3.2.RELEASE.jar
spring-web-4.3.2.RELEASE.jar
spring-webmvc-4.3.2.RELEASE.jar
在web.xml中配置DispatcherServlet:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>springmvc01_helloworld</display-name>
<!-- 配置DispatcherServlet -->
<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置DispatcherServlet的一个初始化参数:配置Spring MVC配置文件的位置和名称 -->
<!--
实际上也可以不通过contextConfigLocation来配置Spring MVC的配置文件,而使用默认的。
默认的配置文件为:/WEB-INF/<servlet-name>-servlet.xml(dispatcherServlet-servlet.xml)
-->
<!-- <init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param> -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
加入Spring MVC的配置文件dispatcherServlet-servlet.xml:
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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-4.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.stone.springmvc"></context:component-scan>
<!-- 配置视图解析器:如何把handler方法返回值解析为实际的物理视图 -->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
编写处理请求的处理器,并标识为处理器:
package com.stone.springmvc.handlers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloWorld {
/**
* 1.使用@RequestMapping注解来映射请求的URL
* 2.返回值会通过视图解析器解析为实际的物理视图,对于InternalResourceViewResolver视图解析器,会做如下的解析:
* prifix+returnVal+suffix。通过这样的方式得到实际的物理视图,然后做转发操作
* /WEB-INF/views/success.jsp
*
* @return
*/
@RequestMapping("/helloworld")
public String hello() {
System.out.println("hello world");
return "success";
}
}
编写视图index.jsp和success.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>index.jsp</title>
</head>
<body>
<a href="helloworld">Hello World</a>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>success.jsp</title>
</head>
<body>
<h4>Success Page</h4>
</body>
</html>
@RequestMapping
修饰类
Spring MVC使用@RequestMapping注解为控制器指定可以处理那些URL请求。在控制器的类定义及方法定义处都可标注@RequestMapping。
- 类定义处:提供初步的请求映射信息。相对于WEB应用的根目录
- 方法处:提供进一步的细分映射信息。相对于类定义处的URL。若类定义处未标注@RequestMapping,则方法处标记的URL相对于WEB应用的根目录。
DispatcherServlet截获请求后,就通过控制器上@RequestMapping提供的映射信息确定请求所对应的处理方法。
package com.stone.springmvc.handlers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
/**
* 1.@RequestMapping除了可以修饰方法,还可以来修饰类
* 2.
* 1)类定义处:提供初步的请求映射信息。相对于WEB应用的根目录
* 2)方法处:提供进一步的细分映射信息。相对于类定义处的URL。
* 若类定义处未标注@RequestMapping,则方法处标记的URL相对于WEB应用的根目录。
* @return
*/
@RequestMapping("/testRequestMapping")
public String testRequestMapping() {
System.out.println("testRequestMapping");
return SUCCESS;
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="helloworld">Hello World</a>
<a href="springmvc/testRequestMapping">Test RequestMapping</a>
</body>
</html>
请求参数和请求头
@RequestMapping除了可以使用请求URL映射请求外,还可以使用请求方法、请求参数及请求头映射请求。
@RequestMapping的value、method、params及heads分别表示请求URL、请求方法、请求参数及请求头的映射条件,他们之间是与的关系,联合使用多个条件可让请求映射更加精确化。
params和heads支持简单的表达式:
- param1:表示请求必须包含名为param1的请求参数
- !param1:表示请求不能包含名为param1的请求参数
- param1!=value1:表示请求包含名为param1的请求参数,但其值不能为value1
- {“param1=value1”,”param2”}:请求必须包含名为param1和param2的两个请求参数,且param1参数的值必须为value1
/**
* 常用:使用method属性来指定请求方式
* @return
*/
@RequestMapping(value="/testMethod", method=RequestMethod.POST)
public String testMethod() {
System.out.println("testMethod");
return SUCCESS;
}
index.jsp增加:
<form action="springmvc/testMethod" method="post">
<input type="submit" value="submit">
</form>
/**
* 了解:可以使用params和headers来更加精确的映射请求,params和headers支持简单的表达式
* @return
*/
@RequestMapping(value="/testParamsAndHeaders",
params={"username","age!=10"})
public String testParamsAndHeaders() {
System.out.println("testParamsAndHeaders");
return SUCCESS;
}
index.jsp增加:
<a href="springmvc/testParamsAndHeaders?username=stone&age=11">Test ParamsAndHeaders</a>
Ant路径
Ant风格资源地址支持3种匹配符:
- ?:匹配文件名的一个字符
- *:匹配文件名中的任意字符
- **:匹配多层路径
@RequestMapping还支持Ant风格的URL:
- /user/*/createUser:匹配/user/aaa/createUser、/user/bbb/createUser等URL
- /user/**/createUser:匹配/user/createUser、/user/aaa/bbb/createUser等URL
- /user/createUser??:匹配/user/createUseraa、/user/createUserbb等URL
@RequestMapping("/testAntPath/*/abc")
public String testAntPath() {
System.out.println("testAntPath");
return SUCCESS;
}
index.jsp增加:
<a href="springmvc/testAntPath/stone/abc">Test Ant Path</a>
@PathVariable
带占位符的URL是Spring3.0新增的功能,该功能在Spring MVC向REST目标挺近发展过程中具有里程碑的意义。
通过@PathVariable可以将URL中占位符参数绑定到控制器处理方法的入参中:URL中的{xxx}占位符可以通过@PathVariable(“xxx”)绑定到操作方法的入参中。
/**
* @PathVariable可以来映射URL中的占位符到目标方法的参数中
* @param id
* @return
*/
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") Integer id) {
System.out.println("testPathVariable: " + id);
return SUCCESS;
}
index.jsp增加:
<a href="springmvc/testPathVariable/1">Test PathVariable</a>
REST
REST:即Representational State Transfer。(资源)表现层状态转化。是目前最流行的一种互联网软件架构。它结构清晰,符合标准,易于理解,扩展方便,所以得到越来越多网站的采用。
资源(Resources)
REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。
所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
表现层(Representation)
"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。比如:
(1)文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式
(2)图片可以用JPG格式表现,也可以用PNG格式表现。
URI只代表资源的实体,不代表它的形式。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定。
状态转化(State Transfer)
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。
客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
示例:
- /order/1 HTTP GET:得到id=1的order
- /order/1 HTTP DELETE:删除id=1的order
- /order/1 HTTP PUT:更新id=1的order
- /order HTTP POST:新增order
HidderHttpMethodFilter:浏览器form表单只支持GET和POST请求,而DELETE、PUT等method并不支持,Spring3.0添加了这个过滤器,可以将这些请求转换为标准的http方法,使得支持GET、POST、PUT与DELETE请求。
配置web.xml:
<!-- 配置org.springframework.web.filter.HiddenHttpMethodFilter,可以把POST请求转为DELETE或PUT请求 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
index.jsp:
<a href="springmvc/testRest/1">Test Rest Get</a>
<br><br>
<form action="springmvc/testRest" method="post">
<input type="submit" value="TestRest POST">
</form>
<br><br>
<form action="springmvc/testRest/1" method="post">
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="TestRest DELETE">
</form>
<br><br>
<form action="springmvc/testRest/1" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="TestRest PUT">
</form>
测试:
/**
* REST风格的URL:
* 以CRUD为例:
* 新增:/order POST
* 修改:/order/1 PUT (update?id=1)
* 获取:/order/1 GET (get?id=1)
* 删除:/order/1 DELETE (delete?id=1)
* 如何发生PUT请求和DELETE请求呢?
* 1.需要在web.xml中配置HiddenHttpMethodFilter
* 2.需要发生POST请求
* 3.需要在发送POST请求时携带一个name="_method"的隐藏域,值为DELETE或PUT
* 4.如果使用tomcat8以上版本,则需要添加@ResponseBody,且返回的是一个字符串。
*
*在Spring MVC的目标方法中如何得到id呢?使用@PathVariable注解
* @param id
* @return
*/
@RequestMapping(value="/testRest/{id}",method=RequestMethod.GET)
public String testRestGet(@PathVariable Integer id) {
System.out.println("testRest GET: " + id);
return SUCCESS;
}
@RequestMapping(value="/testRest",method=RequestMethod.POST)
public String testRestPost() {
System.out.println("testRest POST");
return SUCCESS;
}
@RequestMapping(value="/testRest/{id}",method=RequestMethod.DELETE)
@ResponseBody
public String testRestDelete(@PathVariable Integer id) {
System.out.println("testRest Delete: " + id);
return SUCCESS;
}
@RequestMapping(value="/testRest/{id}",method=RequestMethod.PUT)
@ResponseBody
public String testRestPut(@PathVariable Integer id) {
System.out.println("testRest Put: " + id);
return SUCCESS;
}
@RequestParam
Spring MVC通过分析处理方法的签名,将HTTP请求信息绑定到处理方法的相应入参中。
Spring MVC对控制器处理方法签名的限制是很宽松的,几乎可以按喜欢的任何方式对方法进行签名。
必要时可以对方法及方法入参标注相应的注解(@PathVariable、@RequestParam、@RequestHeader等),Spring MVC框架会将HTTP请求的信息绑定到相应的方法入参中,并根据方法的返回值类型做出相应的后续处理。
在处理方法入参处,如果参数名称与请求传递过来的参数名称不一致,可以使用@RequestParam可以把请求参数传递给请求方法
- value:参数名
- required:是否必须。默认值为true,表示请求参数中必须包含对应的参数,若不存在,将抛出异常
<a href="springmvc/testRequestParam?username=stone&age=11">Test RequestParam</a>
/**
* @RequestParam来映射请求参数。
* value:请求参数的参数名
* required:该参数是否必须,默认为true
* defaultValue:请求参数的默认值
* @param username
* @param age
* @return
*/
@RequestMapping(value="/testRequestParam")
public String testRequestParam(@RequestParam(value="username") String username, @RequestParam(value="age", required=false) Integer age) {
System.out.println("testRequestParam,username: " + username + ",age: " + age );
return SUCCESS;
}
普通参数:URL 地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。
http://localhost/commonParamDifferentName?name=张三&age=18
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(String userName , int age){
System.out.println("普通参数传递 userName ==> "+userName);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param different name'}";
}
普通参数:URL 地址传参,地址参数名与形参变量名不同,使用 @RequestParam 进行指定。
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(@RequestParam("name") String userName , int age){
System.out.println("普通参数传递 userName ==> "+userName);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param different name'}";
}
POJO 参数:请求参数名与形参对象属性名相同,定义 POJO 类型形参即可接收参数。
http://localhost/commonParamDifferentName?name=张三&age=18
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}
嵌套 POJO 参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套 POJO 属性参数。
http://localhost/commonParamDifferentName?name=张三&age=18&address.city=beijing&address.province=beijing
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}
数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型即可接收参数。
http://localhost/commonParamDifferentName?likes=game&likes=music
//数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
return "{'module':'array param'}";
}
集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam 绑定参数关系。
http://localhost/commonParamDifferentName?likes=game&likes=music
//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}
总结:
- 普通参数:参数名称相同时直接传递,不同时使用
@RequestParam指定 - POJO 类型参数:直接传递
- 嵌套 POJO 类型参数:直接传递
- 数组类型参数:直接传递
- 集合类型参数:使用
@RequestParam绑定
JSON
对于 JSON 数据类型,常见的有三种:
- JSON 普通数组(
["value1","value2","value3",...]) - JSON 对象(
{key1:value1,key2:value2,...}) - JSON 对象数组(
[{key1:value1,...},{key2:value2,...}])
前端如果发送的是 JSON 数据,需要先在 pom.xml 添加 jackson 依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
然后在 Spring MVC 的配置类中开启 Spring MVC 的注解支持,这里面就包含了将 JSON 转换成对象的功能:
@Configuration
@ComponentScan("com.itheima.controller")
//开启 JSON 数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
然后参数前添加 @RequestBody:
//使用 @RequestBody 注解将外部传递的 JSON 数组数据映射到形参的集合对象中作为数据
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){
System.out.println("list common(json)参数传递 list ==> "+likes);
return "{'module':'list common for json param'}";
}
@RequestBody 与 @RequestParam 区别:
区别
@RequestParam用于接收 URL 地址传参及表单传参,格式为application/x-www-form-urlencoded@RequestBody用于接收 JSON 数据,格式为application/json
应用
- 后期开发中,以发送 JSON 格式数据为主,
@RequestBody应用较多 - 如果发送非 JSON 格式数据,选用
@RequestParam接收请求参数
- 后期开发中,以发送 JSON 格式数据为主,
日期类型参数
Spring MVC 默认可以处理的日期格式为 yyyy/MM/dd,如果是其他日期格式,则需要使用 @DateTimeFormat 注解指定日期参数的格式。
例如对于以下带不同格式的日期请求:
http://localhost/dataParam?date=2088/08/08&date1=2088-08-08
使用 @DateTimeFormat 指定日期参数的格式:
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
@DateTimeFormat(pattern="yyyy-MM-dd") Date date1)
System.out.println("参数传递 date ==> "+date);
System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
return "{'module':'data param'}";
}
对于带时间格式的请求:
http://localhost/dataParam?date=2088/08/08&date1=2088-08-08&date2=2088/08/08 8:08:08
使用 @DateTimeFormat 指定日期时间参数的格式:
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
@DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
@DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2)
System.out.println("参数传递 date ==> "+date);
System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
return "{'module':'data param'}";
}
@RequestHeader
请求头包含了若干个属性,服务器可据此获知客户端的信息,通过@RequestHeader即可将请求头中的属性值绑定到处理方法的入参中。
<a href="springmvc/testRequestHeader">Test RequestHeader</a>
/**
* 用法同@RequestParam
* 映射请求头信息
* 使用较少,了解
* @param al
* @return
*/
@RequestMapping("/testRequestHeader")
public String testRequestHeader(@RequestHeader(value="Accept-Language") String al) {
System.out.println("testRequestHeader,Accept-Language:" + al);
return SUCCESS;
}
@CookieValue
@CookieValue可以处理方法入参绑定某个Cookie值。
<a href="springmvc/testCookieValue">Test CookieValue</a>
/**
* 了解:
* @CookieValue:映射一个Cookie值,属性同@RequestParam
* @param sessionId
* @return
*/
@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue("JSESSIONID") String sessionId) {
System.out.println("testCookieValue,sessionId:" + sessionId);
return SUCCESS;
}
使用POJO作为参数
Spring MVC会按请求参数名和POJO属性名进行自动匹配,自动为该对象填充属性值,支持级联属性。如:dept.deptId、dept.address.tel等。
实体类:
package com.stone.springmvc.entities;
public class Address {
private String province;
private String city;
//省略getter,setter以及toString
}
package com.stone.springmvc.entities;
public class User {
private String username;
private String password;
private String email;
private int age;
private Address address;
//省略getter,setter以及toString
}
index.jsp:
<form action="springmvc/testPojo" method="post">
username:<input type="text" name="username">
<br>
password:<input type="password" name="password">
<br>
email:<input type="text" name="email">
<br>
age:<input type="text" name="age">
<br>
province:<input type="text" name="address.province">
<br>
city:<input type="text" name="address.city">
<br>
<input type="submit" value="Submit">
</form>
测试:
@RequestMapping("/testPojo")
public String testPojo(User user) {
System.out.println("testPojo: " + user);
return SUCCESS;
}
输出:
testPojo: User [username=a, password=123456, email=a@stone.com, age=11, address=Address [province=cq, city=bb]]
使用Servlet API作为参数
MVC的Handler方法可以接受Servlet API类型的参数:
- HttpServletRequest
- HTTPServletResponse
- HTTPSession
- java.security.Principal
- Locale
- InputStream
- OutputStream
- Reader
- Writer
index.jsp:
<a href="springmvc/testServletAPI">Test ServletAPI</a>
测试:
/**
* 可以使用Servlet原生的API作为目标方法的参数
*
* @param request
* @param response
* @return
* @throws IOException
*/
@RequestMapping("/testServletAPI")
public void testServletAPI(HttpServletRequest request, HttpServletResponse response, Writer out) throws IOException {
System.out.println("testServletAPI," + request + "," + response);
out.write("hello springmvc");
//return SUCCESS;
}
响应
对于响应,主要就包含两部分内容:
- 响应页面
- 响应数据
- 文本数据
- JSON 数据
因为异步调用是目前常用的主流方式,所以需要更关注的就是如何返回 JSON 数据。
创建一个 Web 的 Maven 项目,pom.xml 文件添加 Spring 依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>springmvc_05_response</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
创建对应的配置类:
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
//乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
创建模型类 User:
public class User {
private String name;
private int age;
//getter...setter...toString省略
}
webapp 目录下创建 page.jsp:
<html>
<body>
<h2>Hello Spring MVC!</h2>
</body>
</html>
创建 UserController:
@Controller
public class UserController {
}
返回页面:
@Controller
public class UserController {
@RequestMapping("/toJumpPage")
//注意
//1.此处不能添加 @ResponseBody,如果加了该注解,会直接将 page.jsp 当字符串返回前端
//2.方法需要指定返回类型为 String
public String toJumpPage(){
System.out.println("跳转页面");
return "page.jsp";
}
}
返回文本内容:
@Controller
public class UserController {
@RequestMapping("/toText")
//注意此处该注解就不能省略,如果省略了,会把 response text 当前页面名称去查找,如果没有回报 404 错误
@ResponseBody
public String toText(){
System.out.println("返回纯文本数据");
return "response text";
}
}
将 POJO 对象转换为 JSON 返回:
@Controller
public class UserController {
@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO(){
System.out.println("返回json对象数据");
User user = new User();
user.setName("itcast");
user.setAge(15);
return user;
}
}
返回值为实体类对象,设置返回值类型为实体类类型,即可实现返回对应对象的 JSON 数据,需要依赖 @ResponseBody 注解和 @EnableWebMvc 注解。
将 POJO 对象集合转换为 JSON 返回:
@Controller
public class UserController {
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
System.out.println("返回json集合数据");
User user1 = new User();
user1.setName("传智播客");
user1.setAge(15);
User user2 = new User();
user2.setName("黑马程序员");
user2.setAge(12);
List<User> userList = new ArrayList<User>();
userList.add(user1);
userList.add(user2);
return userList;
}
}
对于 @ResponseBody:
- 该注解可以写在类上或者方法上
- 写在类上就是该类下的所有方法都有
@ReponseBody功能 - 当方法上有
@ReponseBody注解后- 方法的返回值为字符串,会将其作为文本内容直接响应给前端
- 方法的返回值为对象,会将对象转换成 JSON 响应给前端
此处又使用到了类型转换,内部还是通过 Converter 接口的实现类完成的,所以 Converter 除了实现日期格式转换外,还可以实现:
- 对象转 JSON 数据(POJO -> JSON)
- 集合转 JSON 数据(Collection -> JSON)
处理模型数据
Spring MVC提供了以下几种途径输出模型数据:
- ModelAndView:处理方法返回值类型为ModelAndView,方法体即可通过该对象添加模型数据
- Map及Model:入参为org.springframework.ui.Model、org.springframework.ui.ModelMap或java.uti.Map时,处理方法返回时,Map中的数据会自动添加到模型中。
- @SessionAttributes:将模型中的某个属性暂存到HttpSession中,以便多个请求之间可以共享这个属性
- @ModelAttribute:方法入参标注该注解后,入参的对象就会放到数据模型中
ModelAndView
控制器处理方法的返回值如果为ModelAndView,则其既包含视图信息,也包含模型信息。
添加模型数据:
- ModelAndView addObject(String attributeName, Object attributeValue)
- ModelAndView addAllObject(Map <String, ?> modelMap)
设置视图
- void setView(View view)
- void setViewName(String viewName)
index.jps:
<a href="springmvc/testModelAndView">Test ModelAndView</a>
success.jsp:
time: ${requestScope.time }
测试:
/**
* 目标方法的返回值可以是ModelAndView类型。
* 其中可以包含视图和模型信息
* Spring MVC会把ModelAndView的model中的数据放到request域对象中
* @return
*/
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
String viewName = SUCCESS;
ModelAndView modelAndView = new ModelAndView(viewName);
//添加模型数据到ModelAndView中
modelAndView.addObject("time", new Date());
return modelAndView;
}
Map和Model
Spring MVC在内部使用了一个org.springframework.ui.Model接口存储模型数据
具体步骤:
- Spring MVC在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器。
- 如果方法的入参为Map或Model类型,Spring MVC会将隐含模型的引用传递给这些入参。在方法体内,开发者可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据。
index.jsp:
<a href="springmvc/testMap">Test Map</a>
success.jsp:
names:${requestScope.names }
测试:
/**
* 处理模型数据的第二种方式,常用:目标方法可以添加Map类型(实际上也可以是Model类型或ModelMap类型)的参数。
* @param map
* @return
*/
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map) {
System.out.println(map.getClass().getName());
map.put("names", Arrays.asList("tom","jerry","mike"));
return SUCCESS;
}
@SessionAttributes
若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个@SessionAttributes,Spring MVC将在模型中对应的属性暂存到HttpSession中。
@SessionAttributes除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中
- @SessionAttributes(types=User.class)会将隐含模型中所有类型为User.class的属性添加到会话中。
- @SessionAttributes(value={“user1”,”user2”})
- @SessionAttributes(types={User.class,Dept.class})
- @SessionAttributes(value={“user1”,”user2”},types={Dept.class})
index.jsp:
<a href="springmvc/testSessionAttributes">Test SessionAttributes</a>
success.jsp:
request user:${requestScope.user }
<br><br>
session user:${sessionScope.user }
<br><br>
request school:${requestScope.school }
<br><br>
session school:${sessionScope.school }
测试:
@SessionAttributes(value={"user"},types=String.class)
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
/**
* @SessionAttributes除了可以通过属性名指定需要放到会话中的属性外(实际上使用的是value属性值),
* 还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中(实际上使用的是types属性值)
* 注意:这个注解只能在类上面,而不能修饰方法。
* @param map
* @return
*/
@RequestMapping("/testSessionAttributes")
public String testSessionAttributes(Map<String, Object> map) {
User user = new User("stone", "123456", "stone@stonecoding.net", 20);
map.put("user", user);
map.put("school", "stone");
return SUCCESS;
}
输出:
request user:User [username=stone, password=123456, email=stone@stonecoding.net, age=20, address=null]
session user:User [username=stone, password=123456, email=stone@stonecoding.net, age=20, address=null]
request school:stone
session school:stone
@ModelAttribute
用于记录的某个字段需要保持原值(如记录的插入时间),不能被修改情况。
方法是从数据库获取不能被修改的字段的值,再赋予给对象。
实体类:
package com.stone.springmvc.entities;
public class User {
private Integer id;
private String username;
private String password;
private String email;
private int age;
private Address address;
public User() {
super();
// TODO Auto-generated constructor stub
}
public User(String username, String password, String email, int age) {
super();
this.username = username;
this.password = password;
this.email = email;
this.age = age;
}
public User(Integer id, String username, String password, String email, int age) {
super();
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.age = age;
}
////省略getter,setter以及toString
}
index.jsp:
<!--
模拟修改操作
1.原始数据:1,stone,123456,stone@stonecoding.net,12
2.密码不能被修改
3.表单回显,模拟操作直接在表单填写对应的属性值
-->
<form action="springmvc/testModelAttribute" method="post">
<input type="hidden" name="id" value="1">
username:<input type="text" name="username" value="stone">
<br>
email:<input type="text" name="email" value="stone@stonecoding.net">
<br>
age:<input type="text" name="age" value="15">
<br>
<input type="submit" value="Submit">
</form>
测试:
@SessionAttributes(value={"user"},types=String.class)
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
/**
* 1.@ModelAttribute标记的方法,会在每个目标方法执行之前被Spring MVC调用!
* 2.@ModelAttribute注解也可以来修饰目标方法POJO类型的入参,其value属性值有如下的作用:
* 1)Spring MVC会使用value属性值在implicitModel中查找对应的对象,若存在则会直接传入到目标方法的入参中
* 2)Spring MVC会以value为key,POJO类型的对象为value,存入到request中
* @param id
* @param map
*/
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id, Map<String, Object> map) {
System.out.println("modelAttribute method");
if (id != null) {
//模拟从数据库中获取对象
User user = new User(1, "stone", "123456", "stone@stonecoding.net", 12);
System.out.println("从数据库中获取一个对象:" + user);
map.put("user", user);
}
}
/**
* 运行流程:
* 1.执行@ModelAttribute注解修饰的方法:从数据库中取出对象,把对象放入到Map中,键为user
* 2.Spring MVC从Map中取出User对象,并把表单的请求参数赋给该User对象的对应属性
* 3.Spring MVC把上述对象传入目标方法的参数。
* 注意:在@ModelAttribute修饰的方法中,放入到Map中的键名称需要和目标方法入参类型的第一个字母小写的字符串一致
*
* Spring MVC确定目标方法POJO类型入参的过程
* 1.取得一个key
* 1).若目标方法的POJO类型的参数没有使用@ModelAttribute作为修饰,则key为POJO类名第一个字母的小写
* 2).若使用了@ModelAttribute来修饰,则key为@ModelAttribute注解的value属性值
* 2.在implicitModel中查找key对应的对象,若存在,则作为入参传入
* 1).若在@ModelAttribute标记的方法中在Map中保存过,且key和1确定的key一致,则会获取到
* 3.若implicitModel中不存在key对应的对象,则检查当前的handler是否使用@SessionAttributes注解修饰,
* 若使用了该注解,且@SessionAttributes注解的value属性值中包含了key,则会从HttpSession中来获取key所
* 对应的value值,若存在则直接传入到目标方法的入参中。若不存在则抛出异常
* 4.若handler没有标识@SessionAttributes注解或@SessionAttributes注解的value值中不包含key,
* 则会通过反射来创建POJO类型的参数,传入为目标方法的参数
* 5.Spring MVC会把key和POJO类型的对象保存到implicitModel中,进而会保存到request中
*
* 源码分析的流程:
* 1.调用@ModelAttribute注解修饰的方法,实际上把@ModelAttribute方法中Map中的数据放在了implicitModel中
* 2.解析请求处理器的目标参数,实际上该目标参数来自于WebDataBinder对象的target属性
* 1).创建WebDataBinder对象:
* (1).确定objectName属性:若传入的attrName属性值为“”,则objectName为类名第一个字母小写
* attrName,若目标方法的POJO属性使用了@ModelAttribute来修饰,则attrName值即为@ModelAttribute的value属性
* (2).确定target属性
* a.在implicitModel中查找attrName对应的属性值。若存在,ok
* b.若不存在,则验证当前handler是否使用了@SessionAttributes修饰,若使用了,则尝试从session中获取attrName对应的属性值
* 若session中没有对应的属性值,则抛出异常。
* c.若handler没有使用@SessionAttributes进行修饰,或@SessionAttributes中没有使用value值指定的key和attrName
* 相匹配,则通过反射创建了POJO对象
* 2).Spring MVC把表单的请求参数赋给了WebDataBinder的target对应的属性。
* 3).Spring MVC会把WebDataBinder的attrName和target给到implicitModel,进而传到request域对象中
* 4).把WebDataBinder的target作为参数传递给目标方法的入参
*
* @param user
* @return
*/
@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute("user") User user) {
System.out.println("修改:" + user);
return SUCCESS;
}
输出:
从数据库中获取一个对象:User [id=1, username=stone, password=123456, email=stone@stonecoding.net, age=12]
修改:User [id=1, username=stone, password=123456, email=stone@stonecoding.net, age=14]
视图和视图解析器
请求处理方法执行完成后,最终返回一个ModelAndView对象。对于那些返回String,View或ModelMap等类型的处理方法,Spring MVC也会在内部将它们装配成一个ModelAndView对象,它包含了逻辑名和模型对象的视图。
Spring MVC借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是JSP,有可能是EXCLE,JFreeChart等各种表现形式的视图。
对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现MVC的充分解耦。
视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。
为了实现视图模型和具体实现技术的解耦,Spring在org.springframework.web.servlet包中定义了一个高度抽象的View接口。
视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们不会有线程安全的问题。
常用的视图实现类:
| 大类 | 视图类型 | 说明 |
|---|---|---|
| URL资源视图 | InternalResourceView | 将jsp或其他资源封装成一个视图,这是InternalResourceViewResolver解析成的视图 |
| URL资源视图 | JstlView | 如果jsp文件中需要用到JSTL国际化标签功能,则需要使用该视图类,而非InternalResourceView视图类 |
| 文档视图类 | AbstractExcelView | Excel视图抽象类,开发者需要继承AbstractExcelView,获取视图模型进行填充,实现自己的文档视图,需要依赖POI |
| 文档视图类 | AbstractPdfView | PDF文档视图抽象类,可以通过该抽象类实现自己的PDF文档视图,依赖iText |
| 报表视图 | ConfigurableJasperReportsView | 使用java JasperReports报表技术的视图 |
| 报表视图 | JasperReportsCsvView | 使用java JasperReports报表技术的视图 |
| 报表视图 | JasperReportsHtmlView | 使用java JasperReports报表技术的视图 |
| 报表视图 | JasperReportsMultiFormatView | 使用java JasperReports报表技术的视图 |
| 报表视图 | JasperReportsPdgView | 使用java JasperReports报表技术的视图 |
| 报表视图 | JasperReportsXlsView | 使用java JasperReports报表技术的视图 |
| JSON视图 | MappingJackson2JsonView | 将模型数据通过Jackson开发框架的ObjectMapper已JSON方式输出 |
Spring MVC为逻辑视图名的解析提供了不同的策略,可以在Spring WEB上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。
程序员可以选择一种视图解析器或混用多种视图解析器。可以通过order属性指定解析器的优先顺序,order越小优先级越高,Spring MVC会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则抛出ServletException异常。
视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。
所有的视图解析器都必须实现ViewResolver接口。
常见的视图解析器实现类:
| 大类 | 视图类型 | 说明 |
|---|---|---|
| 解析为Bean的名字 | beanNameViewResolver | 常用,将视图名解析为一个bean,视图名是bean的id |
| 解析为URL文件 | InternalResourceViewResolver | 常用,一般通过该类配置前缀和后缀,然后解析为一个URL文件,例如jsp页面,解析优先级最低 |
| 解析为URL文件 | JasperReportsViewResolver | JasperReports是一个基于java的开源报表工具,该解析器将视图名解析为报表文件对应的路径 |
| 模板文件视图 | FreeMarkerViewResolver | 解析基于FreeMarker模版技术的模版文件 |
| 模板文件视图 | VelocityViewResovler | 解析为基于Velocity模版技术的模版文件 |
| 模板文件视图 | VelocityLayoutViewResovler | 解析为基于Velocity模版技术的模版文件 |
index.jsp:
<a href="springmvc/testViewAndViewResolver">Test ViewAndViewResolver</a>
测试:
@RequestMapping("/testViewAndViewResolver")
public String testViewAndViewResolver() {
System.out.println("testViewAndViewResolver");
return SUCCESS;
}
若项目中使用了JSTL,则Spring MVC会自动把视图由InternalResourceView转为JstlView,若使用了JSTL的fmt标签则需要在Spring MVC的配置文件中配置国际化资源文件,若希望直接响应通过Spring MVC渲染的页面,可以使用mvc:view-controller标签实现。
使用JSTL
Jar包:
jstl.jar
standard.jar
success.jsp:
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<br><br>
<fmt:message key="i18n.username"></fmt:message>
<br><br>
<fmt:message key="i18n.password"></fmt:message>
dispatcherServlet-servlet.xml:
<!-- 配置国际化资源文件 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
显示页面会根据浏览器中文或者英文设置显示中文或者英文。
使用mvc:view-controller标签
<!-- 配置直接转发的页面 -->
<!-- 可以直接响应转发的页面,而无需再经过Handler的方法 -->
<mvc:view-controller path="/success" view-name="success"/>
<!-- 在实际开发中通常都需要配置mvc:annotation-driven标签,否则配置了mvc:view-controller标签后会导致404 -->
<mvc:annotation-driven></mvc:annotation-driven>
<mvc:view-controller path=""/>标签的作用:
对应WEB-INF目录下面的JSP页面,我们知道是不能直接使用URL访问到。需要通过转发的方式,而我们一般都是在控制器中做转发映射,对应一些我们不需要其他操作的JSP页面,我们可以使用<mvc:view-controller path=""/>来配置,这样就可以不用在控制器中再去做转发映射。
自定义视图
Excel视图:
若希望使用Excel展示数据列表,仅需要扩展Spring MVC提供的AbstractExcelView或AbstractJExcelView即可。实现buildExcelDocument()方法,在方法中使用模型数据对象构建Excel文档就可以了。
AbstractExcelView基于POI API,而AbstractJExcelView是基于JExcelAPI。
视图对象需要配置IoC容器中的一个Bean,使用BeanNameViewResolver作为视图解析器即可。
若希望直接在浏览器中下载Excel文档,则可以设置响应头Content-Disposition的值为attachment;filename=xxx.xls。
BeanNameViewResolver
dispatcherServlet-servlet.xml:
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.stone.springmvc"></context:component-scan>
<!-- 配置视图解析器BeanNameViewResolver:使用视图的名字来解析视图 -->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<!-- 通过order属性定义视图解析器的优先级,order值越小,优先级越高 -->
<!-- InternalResourceViewResolver的order属性默认为Integer的最大值 -->
<property name="order" value="100"></property>
</bean>
自定义View实现类:
package com.stone.springmvc.views;
import java.util.Date;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.View;
@Component
public class HelloView implements View {
@Override
public String getContentType() {
return "text/html";
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.getWriter().print("hello view, time: " + new Date());
}
}
index.jsp:
<a href="springmvc/testView">Test View</a>
测试:
@RequestMapping("/testView")
public String testView() {
System.out.println("testView");
return "helloView";
}
重定向
- 一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理
- 如果返回的字符串中带有forward:或redirect:前缀时,Spring MVC会对他们进行特殊处理:将forword:和redirect:当成指示符,其后的字符串作为URL来处理
- redirect:success.jsp:会完成一个到success.jsp的重定向的操作
- forward:success.jsp:会完成一个到success.jsp的转发操作
index.jsp:
<a href="springmvc/testRedirect">Test Redirect</a>
测试:
@RequestMapping("/testRedirect")
public String testRedirect() {
System.out.println("testRedirect");
return "redirect:/index.jsp";
}
RESTful CRUD
按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
http://localhost/users查询全部用户信息 GET(查询)http://localhost/users/1查询指定用户信息 GET(查询)http://localhost/users添加用户信息 POST(新增/保存)http://localhost/users修改用户信息 PUT(修改/更新)http://localhost/users/1删除用户信息 DELETE(删除)
按照不同的请求方式代表不同的操作类型。
- 发送 GET 请求是用来做查询
- 发送 POST 请求是用来做新增
- 发送 PUT 请求是用来做修改
- 发送 DELETE 请求是用来做删除
Read
显示所有员工信息:
- URI:emps
- 请求方式:GET
导入Jar包:
aopalliance-1.0.jar
aspectjweaver-1.9.2.jar
c3p0-0.9.5.2.jar
cglib-2.2.2.jar
commons-logging-1.2.jar
ehcache-3.6.1.jar
ehcache-core-2.6.8.jar
jsqlparser-1.3.jar
jstl.jar
mchange-commons-java-0.2.11.jar
mybatis-3.4.6.jar
mybatis-ehcache-1.0.3.jar
mybatis-spring-1.3.2.jar
mysql-connector-java-5.1.23-bin.jar
ojdbc6_g.jar
pagehelper-5.1.8.jar
spring-aop-4.3.2.RELEASE.jar
spring-aspects-4.3.2.RELEASE.jar
spring-beans-4.3.2.RELEASE.jar
spring-context-4.3.2.RELEASE.jar
spring-context-support-4.3.2.RELEASE.jar
spring-core-4.3.2.RELEASE.jar
spring-data-commons-1.13.3.RELEASE.jar
spring-data-redis-1.8.3.RELEASE.jar
spring-expression-4.3.2.RELEASE.jar
spring-instrument-4.3.2.RELEASE.jar
spring-instrument-tomcat-4.3.2.RELEASE.jar
spring-jdbc-4.3.2.RELEASE.jar
spring-jms-4.3.2.RELEASE.jar
spring-messaging-4.3.2.RELEASE.jar
spring-orm-4.3.2.RELEASE.jar
spring-oxm-4.3.2.RELEASE.jar
spring-test-4.3.2.RELEASE.jar
spring-tx-4.3.2.RELEASE.jar
spring-web-4.3.2.RELEASE.jar
spring-webmvc-4.3.2.RELEASE.jar
spring-webmvc-portlet-4.3.2.RELEASE.jar
spring-websocket-4.3.2.RELEASE.jar
standard.jar
配置web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>springmvc02_restfulcurd</display-name>
<!-- 配置org.springframework.web.filter.HiddenHttpMethodFilter,可以把POST请求转为DELETE或PUT请求 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置Srping MVC的DispatcherServlet -->
<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置DispatcherServlet的一个初始化参数:配置Spring MVC配置文件的位置和名称 -->
<!--
实际上也可以不通过contextConfigLocation来配置Spring MVC的配置文件,而使用默认的。
默认的配置文件为:/WEB-INF/<servlet-name>-servlet.xml(dispatcherServlet-servlet.xml)
-->
<!-- <init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param> -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置Spring MVC配置文件dispatcherServlet-servlet.xml:
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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-4.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.stone.springmvc"></context:component-scan>
<!-- 配置视图解析器:如何把handler方法返回值解析为实际的物理视图 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置视图解析器BeanNameViewResolver:使用视图的名字来解析视图 -->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<!-- 通过order属性定义视图解析器的优先级,order值越小,优先级越高 -->
<!-- InternalResourceViewResolver的order属性默认为Integer的最大值 -->
<property name="order" value="100"></property>
</bean>
<!-- 配置国际化资源文件 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
<!-- 配置直接转发的页面 -->
<!-- 可以直接响应转发的页面,而无需再经过Handler的方法 -->
<mvc:view-controller path="/success" view-name="success"/>
<!-- 在实际开发中通常都需要配置mvc:annotation-driver标签,否则配置了mvc:view-controller标签后会导致404 -->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
创建实体类:
package com.stone.springmvc.entities;
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Department department;
public Employee() {
super();
// TODO Auto-generated constructor stub
}
public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.department = department;
}
//省略getter,setter,toString方法
}
package com.stone.springmvc.entities;
public class Department {
private Integer id;
private String departmentName;
public Department() {
super();
// TODO Auto-generated constructor stub
}
public Department(Integer id, String departmentName) {
super();
this.id = id;
this.departmentName = departmentName;
}
//省略getter,setter,toString方法
}
创建Dao:
package com.stone.springmvc.dao;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.stone.springmvc.entities.Department;
import com.stone.springmvc.entities.Employee;
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;
@Autowired
private DepartmentDao departmentDao;
static {
employees = new HashMap<Integer, Employee>();
employees.put(1001, new Employee(1001, "E-AA", "aa@stone.com", 1, new Department(101, "D-AA")));
employees.put(1002, new Employee(1002, "E-BB", "bb@stone.com", 1, new Department(102, "D-BB")));
employees.put(1003, new Employee(1003, "E-CC", "cc@stone.com", 0, new Department(103, "D-CC")));
employees.put(1004, new Employee(1004, "E-DD", "dd@stone.com", 0, new Department(104, "D-DD")));
employees.put(1005, new Employee(1005, "E-EE", "ee@stone.com", 1, new Department(105, "D-EE")));
}
private static Integer initId = 1006;
public void save(Employee employee) {
if (employee.getId() == null) {
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
employees.put(employee.getId(), employee);
}
public Collection<Employee> getAll(){
return employees.values();
}
public Employee get(Integer id) {
return employees.get(id);
}
public void delete(Integer id) {
employees.remove(id);
}
}
package com.stone.springmvc.dao;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Repository;
import com.stone.springmvc.entities.Department;
@Repository
public class DepartmentDao {
private static Map<Integer, Department> departments = null;
static {
departments = new HashMap<Integer, Department>();
departments.put(101, new Department(101, "D-AA"));
departments.put(102, new Department(101, "D-BB"));
departments.put(103, new Department(101, "D-CC"));
departments.put(104, new Department(101, "D-DD"));
departments.put(105, new Department(101, "D-EE"));
}
public Collection<Department> getDepartments(){
return departments.values();
}
public Department getDepartment(Integer id) {
return departments.get(id);
}
}
创建Handler添加list页面请求方法:
package com.stone.springmvc.handlers;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.stone.springmvc.dao.EmployeeDao;
@Controller
public class EmployeeHandler {
@Autowired
private EmployeeDao employeeDao;
@RequestMapping("/emps")
public String list(Map<String, Object> map) {
map.put("employees", employeeDao.getAll());
return "list";
}
}
创建首页index.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="emps">List All Employees</a>
</body>
</html>
创建显示全部员工的页面list.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<c:if test="${empty requestScope.employees }">
没有任何员工信息.
</c:if>
<c:if test="${!empty requestScope.employees }">
<table border="1" cellpadding="10" cellspacing="0">
<tr>
<th>ID</th>
<th>LastName</th>
<th>Email</th>
<th>Gender</th>
<th>Department</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<c:forEach items="${requestScope.employees }" var="emp">
<tr>
<td>${emp.id }</td>
<td>${emp.lastName }</td>
<td>${emp.email }</td>
<td>${emp.gender == 0 ? 'Female' : 'Male' }</td>
<td>${emp.department.departmentName }</td>
<td><a href="">Edit</a></td>
<td><a href="">Delete</a></td>
</tr>
</c:forEach>
</table>
</c:if>
</body>
</html>
Create
显示添加页面
- URI:emp
- 请求方式:GET
添加员工信息:
- URI:emp
- 请求方式:POST
- 显示效果:完成添加,重定向到list页面
在页面list.jsp添加新增员工链接:
<a href="emp">Add New Employee</a>
在员工Handler中添加input页面GET请求处理方法:
@RequestMapping(value="/emp", method=RequestMethod.GET)
public String input(Map<String, Object> map) {
//表单上要显示部门信息
map.put("departments", departmentDao.getDepartments());
//创建要录入到表单中的bean实例,其名称与表单的modelAttribute一致
map.put("employee", new Employee());
return "input";
}
创建input.jsp页面:
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<!--
1.为什么使用form标签?可以更快速的开发出表单页面,而且可以更方便的进行表单值的回显
2.注意:可以通过ModelAttribute属性指定绑定的模型属性,
若没有指定该属性,则默认从request域对象中读取command的表单bean
如果该属性值信息也不存在,则会发生错误。
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
-->
<form:form action="emp" method="POST" modelAttribute="employee">
<!-- path属性对应html表单标签的name属性值 -->
LastName:<form:input path="lastName"/>
<br>
Email:<form:input path="email"/>
<br>
<%
Map<String, String> genders = new HashMap();
genders.put("1", "Male");
genders.put("0", "Female");
request.setAttribute("genders", genders);
%>
Gender:
<br>
<form:radiobuttons path="gender" items="${genders }" delimiter="<br>"/>
<br>
Department:<form:select path="department.id" items="${departments }" itemLabel="departmentName" itemValue="id"></form:select>
<br>
<input type="submit" value="Submit">
</form:form>
</body>
</html>
通过Spring MVC的表单标签可以实现将模型数据中的属性和HTML表单元素相绑定。Spring MVC提供了多个表单组件标签,如<form:input/>,<form:select/>等,用于绑定表单字段的属性值,它们的共有属性如下:
- path:表单字段,对应html元素的name属性,支持级联属性
- htmlEscape:是否对表单值的HTML特殊字符进行转换,默认值为true
- cssClass:表单组件对应的CSS样式类名
- cssErrorClass:表单组件的数据存在错误时,采取的CSS样式
- form:input,form:password,form:hidden,form:textarea:对应HTML表单的text,password,hidden,textarea标签
- form:radiobutton:单选框组件标签,当表单bean对应的属性值和value值相等时,单选框被选中
- form:radiobuttons:单选框组标签,用于构造多个单选框
- items:可以是一个List,String[]或Map
- itemValue:指定radio的value值。可以是集合中bean的一个属性值
- itemLabel:指定radio的label值
- delimiter:多个单选框可以通过delimiter指定分隔符
- form:checkbox:复选框组件。用于构造单个复选框
- form:checkboxs:用于构造多个复选框。使用方式同form:radiobuttons 标签
- form:select:用于构造下拉框组件。使用方式同form:radiobuttons 标签
- form:option:下拉框选项组件标签。使用方式同form:radiobuttons 标签
- form:errors:显示表单组件或数据校验所对应的错误
- <form:errors path= “ *” /> :显示表单所有的错误
- <form:errors path= “ user*” /> :显示所有以 user 为前缀的属性对应的错误
- <form:errors path= “ username” /> :显示特定表单对象属性的错误
在员工Handler中添加input页面的POST请求处理方法:
@RequestMapping(value="/emp", method=RequestMethod.POST)
public String save(Employee employee) {
employeeDao.save(employee);
return "redirect:/emps";
}
Delete
删除操作:
- URI:emp/
- 请求方式:DELETE
- 显示效果:对应记录从数据库中删除,重定向到list.jsp页面
在页面list.jsp中为Delete栏指定URI:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<!--
Spring MVC处理静态资源:
1.为什么会有这样的问题
优雅的REST风格的资源URL不希望带.html或.do等后缀。
若将DispatcherServlet请求映射配置为/,
则Spring MVC将捕获WEB容器的所有请求,包括静态资源的请求,Spring MVC会将他们当成一个普通请求处理,
因找不到对应处理器将导致错误。
2.解决
在Spring MVC的配置文件中配置<mvc:default-servlet-handler/>
-->
<script type="text/javascript" src="scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
//此处通过jQuery将对超链接点击的GET请求转为对表单的DELETE请求
$(function() {
$(".delete").click(function() {
var href = $(this).attr("href");
$("form").attr("action",href).submit();
return false;
})
})
</script>
</head>
<body>
<form action="" method="POST">
<input type="hidden" name="_method" value="DELETE"/>
</form>
<c:if test="${empty requestScope.employees }">
没有任何员工信息.
</c:if>
<c:if test="${!empty requestScope.employees }">
<table border="1" cellpadding="10" cellspacing="0">
<tr>
<th>ID</th>
<th>LastName</th>
<th>Email</th>
<th>Gender</th>
<th>Department</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<c:forEach items="${requestScope.employees }" var="emp">
<tr>
<td>${emp.id }</td>
<td>${emp.lastName }</td>
<td>${emp.email }</td>
<td>${emp.gender == 0 ? 'Female' : 'Male' }</td>
<td>${emp.department.departmentName }</td>
<td><a href="">Edit</a></td>
<td><a class="delete" href="emp/${emp.id}">Delete</a></td>
</tr>
</c:forEach>
</table>
</c:if>
<br><br>
<a href="emp">Add New Employee</a>
</body>
</html>
在Spring MVC配置文件中配置mvc:default-servlet-handler:
<!--
<mvc:default-servlet-handler/>将在Spring MVC上下文中定义一个DefaultServletHttpRequestHandler,
它会对进入DispatcherServlet的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由WEB应用服务器默认的Servlet处理,
如果不是静态资源的请求,才由DispatcherServlet继续处理
一般WEB应用服务器默认的Servlet的名称都是default。若所使用的WEB服务器的默认Servlet名称不是default,
则需要通过default-servlet-name属性显式指定
-->
<mvc:default-servlet-handler/>
<mvc:annotation-driven></mvc:annotation-driven>
在员工Handler中添加list页面的DELETE请求处理方法:
@RequestMapping(value="/emp/{id}",method=RequestMethod.DELETE)
public String delete(@PathVariable("id") Integer id) {
employeeDao.delete(id);
return "redirect:/emps";
}
Update
修改操作:lastName不可修改
显示修改页面:
- URI:emp/
- 请求方式:GET
- 显示效果:回显表单
修改员工信息:
- URI:emp
- 请求方式:PUT
- 显示效果:完成修改,重定向到list页面
在页面list.jsp中为Edit栏指定URI:
<td><a href="emp/${emp.id}">Edit</a></td>
在员工Handler中添加list页面的Edit请求处理方法:
@RequestMapping(value="/emp/{id}", method=RequestMethod.GET)
public String input(@PathVariable("id") Integer id, Map<String, Object> map) {
//创建要修改的bean实例,其名称与表单的modelAttribute一致
map.put("employee", employeeDao.get(id));
map.put("departments", departmentDao.getDepartments());
return "input";
}
在员工Handler中添加input页面的Edit请求处理方法:
@ModelAttribute
public void getEmployee(@RequestParam(value="id",required=false) Integer id,
Map<String, Object> map) {
if (id != null) {
map.put("employee", employeeDao.get(id));
}
}
@RequestMapping(value="/emp", method=RequestMethod.PUT)
public String update(Employee employee) {
employeeDao.save(employee);
return "redirect:/emps";
}
修改input.jsp页面:
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<!--
1.为什么使用form标签?可以更快速的开发出表单页面,而且可以更方便的进行表单值的回显
2.注意:可以通过ModelAttribute属性指定绑定的模型属性,
若没有指定该属性,则默认从request域对象中读取command的表单bean
如果该属性值信息也不存在,则会发生错误。
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
-->
<!-- ${pageContext.request.contextPath }/emp表示使用绝对路径,否则在修改的时候会出现问题 -->
<form:form action="${pageContext.request.contextPath }/emp" method="POST" modelAttribute="employee">
<c:if test="${employee.id == null }">
<!-- path属性对应html表单标签的name属性值 -->
LastName:<form:input path="lastName"/>
</c:if>
<c:if test="${employee.id != null }">
<form:hidden path="id"/>
<!-- 对于_method,不能使用<form:hidden path="_method" value="PUT"/>标签,因为modelAttribute对应的bean中没有_method这个属性 -->
<input type="hidden" name="_method" value="PUT"/>
</c:if>
<br>
Email:<form:input path="email"/>
<br>
<%
Map<String, String> genders = new HashMap();
genders.put("1", "Male");
genders.put("0", "Female");
request.setAttribute("genders", genders);
%>
Gender:
<br>
<form:radiobuttons path="gender" items="${genders }" delimiter="<br>"/>
<br>
Department:<form:select path="department.id" items="${departments }" itemLabel="departmentName" itemValue="id"></form:select>
<br>
<input type="submit" value="Submit">
</form:form>
</body>
</html>
快速开发
简化 RESTful 开发:
- 将
@RequestMapping提到类上面,用来定义所有方法共同的访问路径。 - 使用
@GetMapping,@PostMapping,@PutMapping和@DeleteMapping代替@RequestMapping。 - 将
@ResponseBody提到类上面,让所有的方法都有@ResponseBody的功能。 - 使用
@RestController注解替换@Controller与@ResponseBody注解,简化书写。
@RestController //@Controller + @ReponseBody
@RequestMapping("/books")
public class BookController {
//@RequestMapping(method = RequestMethod.POST)
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}
//@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
//@RequestMapping(method = RequestMethod.PUT)
@PutMapping
public String update(@RequestBody Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}
//@RequestMapping(value = "/{id}",method = RequestMethod.GET)
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}
//@RequestMapping(method = RequestMethod.GET)
@GetMapping
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}
三个注解 @RequestBody、@RequestParam、@PathVariable,区别和应用如下:
- 区别
@RequestParam用于接收 URL 地址传参或表单传参@RequestBody用于接收 JSON 数据@PathVariable用于接收路径参数
- 应用
- 后期开发中,发送请求参数超过 1 个时,以 JSON 格式为主,
@RequestBody应用较多 - 如果发送非 JSON 格式数据,使用
@RequestParam接收请求参数 - 采用 RESTful 进行开发,当参数数量较少时,例如 1 个,可以采用
@PathVariable 接收请求路径变量,通常用于传递id值
- 后期开发中,发送请求参数超过 1 个时,以 JSON 格式为主,
数据转换、格式化及校验
- Spring MVC主框架将ServletRequest对象及目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象。
- DataBinder调用装配在Spring MVC上下文中的ConversionService组件进行数据类型转换、数据格式化工作。将Servlet中的请求信息填充到入参对象中
- 调用Validator组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData对象
- Spring MVC抽取BindingResult中的入参对象和校验错误对象,将它们赋给处理方法的响应入参
Spring MVC通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder。
自定义类型转换器:
- ConversionService是Spring类型转换体系的核心接口。可以利用ConversionServiceFactoryBean在Spring的IoC容器中定义一个ConversionService。Spring将自动识别出IoC容器中的ConversionService,并在Bean属性配置及Spring MVC处理方法入参绑定等场合使用它进行数据的转换
- 可通过ConversionServiceFactoryBean的converters属性注册自定义的类型转换器
Spring定义了3种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactroyBean中:
- Converter<S,T>:将S类型对象转为T类型对象
- ConverterFactory:将相同系列多个“同质”Converter封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将String转换为Number及Number子类(Integer、Long、Double等)对象)可使用该转换器工厂类
- GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换
<mvc:annotation-driven/>会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter与ExceptionHandlerExceptionResolver三个bean。
还将提供以下支持:
- 支持使用ConversionService实例对表单参数进行类型转换
- 支持使用@NumberFormatannotation、@DateTimeFormat注解完成数据类型的格式化
- 支持使用@Valid注解对JavaBean实例进行JSR303验证
- 支持使用@RequestBody和@ResponseBody注解
自定义类型转换器
input.jsp:
<form action="testConversionServiceConverter" method="post">
<!-- 将字符串转换为Employee对象:gg-gg@stone.com-0-105 -->
Employee:<input type="text" name="employee">
<br>
<input type="submit" value="Submit">
</form>
创建转换器类:
package com.stone.springmvc.converters;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import com.stone.springmvc.entities.Department;
import com.stone.springmvc.entities.Employee;
@Component
public class EmployeeConverter implements Converter<String, Employee> {
@Override
public Employee convert(String source) {
if (source != null) {
String[] vals = source.split("-");
if (vals != null && vals.length == 4) {
String lastName = vals[0];
String email = vals[1];
Integer gender = Integer.parseInt(vals[2]);
Department department = new Department();
department.setId(Integer.parseInt(vals[3]));
Employee employee = new Employee(null, lastName, email, gender, department);
System.out.println(source + "--convert--" + employee);
return employee;
}
}
return null;
}
}
修改Spring MVC配置文件dispatcherServlet-servlet.xml:
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!-- 配置ConversionService -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="employeeConverter"/>
</set>
</property>
</bean>
测试类:
package com.stone.springmvc.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.stone.springmvc.dao.EmployeeDao;
import com.stone.springmvc.entities.Employee;
@Controller
public class SpringMVCTest {
@Autowired
private EmployeeDao employeeDao;
@RequestMapping("/testConversionServiceConverter")
public String testConverter(@RequestParam("employee") Employee employee) {
System.out.println("save: " + employee);
employeeDao.save(employee);
return "redirect:/emps";
}
}
输出:
zz-zz@stone.com-0-105--convert--Employee [id=null, lastName=zz, email=zz@stone.com, gender=0, birth=null, department=Department [id=105, departmentName=null]]
save: Employee [id=null, lastName=zz, email=zz@stone.com, gender=0, birth=null, department=Department [id=105, departmentName=null]]
数据格式化
对属性对象的输入/输出进行格式化,从其本质上讲依然属于“类型转换”的范畴。
Spring在格式化模块中定义了一个实现ConversionService接口的FormattingConversionService实现类,该实现类扩展了GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能。
FormattingConversionService拥有一个FormattingConversionServiceFactroyBean工厂类,后者用于在Spring上下文中构造前者。
FormattingConversionServiceFactroyBean内部已经注册了:
- NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用@NumberFormat注解
- JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用@DateTimeFormat注解
装配了FormattingConversionServiceFactroyBean后,就可以在Spring MVC入参绑定及模型数据输出时使用注解驱动了。<mvc:annotation-driven/>默认创建的ConversionService实例即为FormattingConversionServiceFactroyBean。
日期格式化:
- @DateTimeFormat注解可对java.util.Date、java.util.Calendar、java.long.Long时间类型进行标注
- pattern属性:类型为字符串。指定解析/格式化字段数据的模式,如:”yyyy-MM-ddhh:mm:ss”
- iso属性:类型为DateTimeFormat.ISO。指定解析/格式化字段数据的ISO模式,包括四种:ISO.NONE(不使用,默认)、ISO.DATE(yyyy-MM-dd)、ISO.TIME(hh:mm:ss.SSSZ)、ISO.DATE_TIME(yyyy-MM-ddhh:mm:ss.SSSZ)
- style属性:字符串类型。通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式:S:短日期/时间格式、M:中日期/时间格式、L:长日期/时间格式、F:完整日期/时间格式、-:忽略日期或时间格式
数值格式化:
@NumberFormat可对类似数字类型的属性进行标注,它拥有两个互斥的属性
style属性:类型为NumberFormat.Style。用于指定样式类型,包括三种:Style.NUMBER(正常数字类型)、Style.CURRENCY(货币类型)、Style.PERCENT(百分数类型)
pattern属性:类型为String,自定义样式,如patter="#,###"
input.jsp:
Birth:<form:input path="birth"/>
<br>
Salary:<form:input path="salary"/>
<br>
修改Spring MVC配置文件dispatcherServlet-servlet.xml:
<mvc:annotation-driven></mvc:annotation-driven>
或者修改为:
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!-- 配置ConversionService -->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="employeeConverter"/>
</set>
</property>
</bean>
修改实体类:
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
@NumberFormat(pattern="#,###,###.#")
private Float salary;
修改handler:
@RequestMapping(value="/emp", method=RequestMethod.POST)
public String save(Employee employee) {
System.out.println("save: " + employee);
employeeDao.save(employee);
return "redirect:/emps";
}
输出:
save: Employee [id=null, lastName=null, email=gg@stone.com, gender=0, birth=Wed Dec 12 00:00:00 CST 1990, salary=1234567.8, department=Department [id=101, departmentName=null]]
捕获绑定错误
从BindingResult中获取绑定错误。
修改handler:
@RequestMapping(value="/emp", method=RequestMethod.POST)
public String save(Employee employee, BindingResult result) {
System.out.println("save: " + employee);
if (result.getErrorCount() > 0) {
System.out.println("出错了!");
List<FieldError> errors = result.getFieldErrors();
for(FieldError error:errors) {
System.out.println(error.getField() + ":" + error.getDefaultMessage());
}
}
employeeDao.save(employee);
return "redirect:/emps";
}
JSR303数据校验
JSR303是Java为Bean数据合法性校验提供的标准框架,它已经包含在JavaEE6.0中。
JSR303通过在Bean属性上标注类似于@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
| Constraint | 详细信息 |
|---|---|
| @Null | 被注释的元素必须为 null |
| @NotNull | 被注释的元素必须不为 null |
| @AssertTrue | 被注释的元素必须为 true |
| @AssertFalse | 被注释的元素必须为 false |
| @Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
| @Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
| @DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
| @DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
| @Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
| @Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
| @Past | 被注释的元素必须是一个过去的日期 |
| @Future | 被注释的元素必须是一个将来的日期 |
| @Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
HibernateValidator扩展注解:
HibernateValidator是JSR303的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解。
| Constraint | 详细信息 |
|---|---|
| 被注释的元素必须是电子邮箱地址 | |
| @Length | 被注释的字符串的大小必须在指定的范围内 |
| @NotEmpty | 被注释的字符串的必须非空 |
| @Range | 被注释的元素必须在合适的范围内 |
Spring MVC数据校验:
- Spring4.0拥有自己独立的数据校验框架,同时支持JSR 303标准的校验框架。
- Spring在进行数据绑定时,可同时调用校验框架完成数据校验工作。在Spring MVC中,可直接通过注解驱动的方式进行数据校验。
- Spring的LocalValidatorFactroyBean既实现了Spring的Validator接口,也实现了JSR303的Validator接口。只要在Spring容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的Bean中。
- Spring本身并没有提供JSR303的实现,所以必须将JSR303的实现者的jar包放到类路径下。
<mvc:annotation-driven/>会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上标注@valid注解即可让Spring MVC在完成数据绑定后执行数据校验的工作。- 在已经标注了JSR303注解的表单/命令对象前标注一个@Valid,Spring MVC框架在将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验。
- Spring MVC是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是BindingResult或Errors类型,这两个类都位于org.springframework.validation包中。
- 需校验的Bean对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参。
- Errors接口提供了获取错误信息的方法,如getErrorCount()或getFieldErrors(Stringfield)。
- BindingResult扩展了Errors接口。
在目标方法中获取校验结果:
在表单/命令对象类的属性中标注校验注解,在处理方法对应的入参前添加@Valid,Spring MVC就会实施校验并将校验结果保存在被校验入参对象之后的BindingResult或Errors入参中。
常用方法:
- FieldErrorgetFieldError(Stringfield)
- List<FieldError>getFieldErrors()
- ObjectgetFieldValue(Stringfield)
- IntgetErrorCount()
加入hibernate validator验证框架的Jar包:
classmate-1.3.4.jar
jboss-logging-3.3.2.Final.jar
validation-api-2.0.1.Final.jar
hibernate-validator-6.0.13.Final.jar
hibernate-validator-annotation-processor-6.0.13.Final.jar
hibernate-validator-cdi-6.0.13.Final.jar
在Spring MVC配置文件中添加<mvc:annotation-driven/>:
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
在Bean的属性上添加对应的注解:
@NotEmpty
private String lastName;
@Email
private String email;
private Integer gender;
@Past
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
在Handler的目标方法的Bean类型的前面添加@Valid注解:
@RequestMapping(value="/emp", method=RequestMethod.POST)
public String save(@Valid Employee employee, BindingResult result, Map<String, Object> map) {
//需校验的Bean对象(employee)和其绑定结果对象或错误对象(result)时成对出现的,它们之间不允许声明其他的入参
System.out.println("save: " + employee);
if (result.getErrorCount() > 0) {
System.out.println("出错了!");
List<FieldError> errors = result.getFieldErrors();
for(FieldError error:errors) {
System.out.println(error.getField() + ":" + error.getDefaultMessage());
}
//若验证出错,则转向定制的页面
map.put("departments", departmentDao.getDepartments());
return "input";
}
employeeDao.save(employee);
return "redirect:/emps";
}
输出:
save: Employee [id=null, lastName=null, email=a, gender=0, birth=Sat Dec 12 00:00:00 CST 2020, salary=null, department=Department [id=101, departmentName=null]]
出错了!
birth:需要是一个过去的时间
lastName:不能为空
email:不是一个合法的电子邮件地址
错误消息的显示和国际化
在页面上显示错误:
- Spring MVC除了会将表单/命令对象的校验结果保存到对应的BindingResult或Errors对象中外,还会将所有校验结果保存到“隐含模型”
- 即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在“隐含对象”中。
- 隐含模型中的所有数据最终将通过HttpServletRequest的属性列表暴露给JSP视图对象,因此在JSP中可以获取错误信息
- 在JSP页面上可通过
<form:errors path=“userName”>显示错误消息
错误消息的显示:
<!-- ${pageContext.request.contextPath }/emp表示使用绝对路径,否则在修改的时候会出现问题 -->
<form:form action="${pageContext.request.contextPath }/emp" method="POST" modelAttribute="employee">
<!-- 显示该表单所有错误 -->
<form:errors path="*"></form:errors>
<br><br>
<c:if test="${employee.id == null }">
<!-- path属性对应html表单标签的name属性值 -->
LastName:<form:input path="lastName"/>
<form:errors path="lastName"></form:errors>
</c:if>
<c:if test="${employee.id != null }">
<form:hidden path="id"/>
<!-- 对应_method,不能使用form:hidden标签,因为modelAttribute对应的bean中没有_method这个属性 -->
<input type="hidden" name="_method" value="PUT"/>
</c:if>
<br>
Email:<form:input path="email"/>
<form:errors path="email"></form:errors>
<br>
<%
Map<String, String> genders = new HashMap();
genders.put("1", "Male");
genders.put("0", "Female");
request.setAttribute("genders", genders);
%>
Gender:
<br>
<form:radiobuttons path="gender" items="${genders }" delimiter="<br>"/>
<br>
Department:<form:select path="department.id" items="${departments }" itemLabel="departmentName" itemValue="id"></form:select>
<br>
<!--
1.数据类型转换
2.数据类型格式化
3.数据校验
1).如何校验?注解?
(1).使用JSR 303验证标准
(2).加入hibernate validator验证框架的Jar包
(3).在Spring MVC配置文件中添加<mvc:annotation-driven/>
(4).需要在bean的属性上添加对应的注解
(5).在目标方法Bean类型的前面添加@Valid注解
2).验证出错转向到哪一个页面
3).错误消息?如何显示,如何把错误消息进行国际化
-->
Birth:<form:input path="birth"/>
<form:errors path="birth"></form:errors>
<br>
Salary:<form:input path="salary"/>
<br>
<input type="submit" value="Submit">
</form:form>
提示消息的国际化:
每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的FieldError对象。
当一个属性校验失败后,校验框架会为该属性生成4个消息代码,这些代码以校验注解类名为前缀,结合modleAttribute、属性名及属性类型名生成多个对应的消息代码:例如User类中的password属性标注了一个@Pattern注解,当该属性值不满足@Pattern所定义的规则时,就会产生以下4个错误代码:
- Pattern.user.password
- Pattern.password
- Pattern.java.lang.String
- Pattern
当使用Spring MVC标签显示错误消息时,Spring MVC会查看WEB上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。
若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:
- required:必要的参数不存在。如@RequiredParam(“param1”)标注了一个入参,但是该参数不存在
- typeMismatch:在数据绑定时,发生数据类型不匹配的问题
- methodInvocation:Spring MVC在调用处理方法时发生了错误
配置国际化资源文件:
<!-- 配置国际化资源文件 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
创建i18n.properties:
NotEmpty.employee.lastName=^^LastName\u4E0D\u80FD\u4E3A\u7A7A
Email.employee.email=Email\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684\u7535\u5B50\u90AE\u4EF6\u5730\u5740
Past.employee.birth=Birth\u9700\u8981\u4E00\u4E2A\u8FC7\u53BB\u7684\u65F6\u95F4
typeMismatch.employee.birth=Birth\u4E0D\u662F\u4E00\u4E2A\u65E5\u671F
返回JSON
加入Jar包:
jackson-annotations-2.9.8.jar
jackson-core-2.9.8.jar
jackson-databind-2.9.8.jar
index.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
$(function() {
$("#testJson").click(function () {
var url = this.href;
var args = {};
$.post(url,args,function(data){
for (var i = 0; i < data.length; i++) {
var id = data[i].id;
var lastName = data[i].lastName;
alert(id + ":" + lastName);
}
});
return false;
});
})
</script>
</head>
<body>
<a href="emps">List All Employees</a>
<br><br>
<a href="testJson" id="testJson">Test Json</a>
</body>
</html>
编写目标方法,使其返回JSON对应的对象或集合:
@ResponseBody
@RequestMapping("/testJson")
public Collection<Employee> testJson() {
return employeeDao.getAll();
}
HttpMessageConverter
HttpMessageConverter<T>是Spring3.0新添加的一个接口,负责将请求信息转换为一个对象(类型为T),将对象(类型为T)输出为响应信息。HttpMessageConverter<T>接口定义的方法:Boolean canRead(Class<?> clazz, MediaType mediaType):指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为clazz类型的对象,同时指定支持MIME类型(text/html,applaiction/json等)。Boolean canWrite(Class<?> clazz, MediaType mediaType):指定转换器是否可将clazz类型的对象写到响应流中,响应流支持的媒体类型在MediaType中定义。LIst<MediaType> getSupportMediaTypes():该转换器支持的媒体类型。T read(Class<? extends T> clazz, HttpInputMessage inputMessage):将请求信息流转换为T类型的对象。void write(Tt, MediaType contnetType, HttpOutputMessgae outputMessage):将T类型的对象写到响应流中,同时指定相应的媒体类型为contentType。
- 使用
HttpMessageConverter<T>将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的响应信息,Spring提供了两种途径:- 使用
@RequestBody/@ResponseBody对处理方法进行标注。 - 使用
HttpEntity<T>/ResponseEntity<T>作为处理方法的入参或返回值。
- 使用
- 当控制器处理方法使用到
@RequestBody/@ResponseBody或HttpEntity<T>/ResponseEntity<T>时,Spring首先根据请求头或响应头的Accept属性选择匹配的HttpMessageConverter,进而根据参数类型或泛型类型的过滤得到匹配的HttpMessageConverter,若找不到可用的HttpMessageConverter将报错。 @RequestBody和@ResponseBody不需要成对出现
index.jsp:
<form action="testHttpMessageConverter" method="post" enctype="multipart/form-data">
File:<input type="file" name="file">
Desc:<input type="text" name="desc">
<input type="submit" value="submit">
</form>
<br><br>
<a href="testResponseEntity">Test ResponseEntity</a>
测试:
@RequestMapping("/testResponseEntity")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException{
byte[] body = null;
ServletContext servletContext = session.getServletContext();
InputStream in = servletContext.getResourceAsStream("/files/abc.txt");
body = new byte[in.available()];
in.read(body);
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=abc.txt");
HttpStatus status = HttpStatus.OK;
ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(body, headers, status);
return responseEntity;
}
@ResponseBody
@RequestMapping("/testHttpMessageConverter")
public String testHttpMessageConverter(@RequestBody String body ) {
System.out.println(body);
return "helloworld! " + new Date();
}
国际化
需求:
- 在页面上能够根据浏览器语言设置的情况对文本(不是内容),时间,数值进行本地化处理。
- 可以在Bean中获取国际化资源文件Locale对应的消息。
- 可以通过超链接切换Locale,而不再依赖于浏览器的语言设置情况。
解决:
- 使用JSTL的fmt标签。
- 在Bean中注入ResourceBundleMessageSource的实例,使用其对应的getMessage方法即可。
- 配置LocaleResolver和LocaleChangeInterceptor。
国际化概述:
- 默认情况下,Spring MVC根据Accept-Language参数判断客户端的本地化类型。
- 当接受到请求时,Spring MVC会在上下文中查找一个本地化解析器(LocalResolver),找到后使用它获取请求所对应的本地化类型信息。
- Spring MVC还允许装配一个动态更改本地化类型的拦截器,这样通过指定一个请求参数就可以控制单个请求的本地化类型。
本地化解析器和本地化拦截器:
- AcceptHeaderLocaleResolver:根据HTTP请求头的Accept-Language参数确定本地化类型,如果没有显式定义本地化解析器,Spring MVC使用该解析器。
- CookieLocaleResolver:根据指定的Cookie值确定本地化类型。
- SessionLocaleResolver:根据Session中特定的属性确定本地化类型。
- LocaleChangeInterceptor:从请求参数中获取本次请求对应的本地化类型。
(1)使用JSTL的fmt标签在页面上能够根据浏览器语言设置的情况对文本(不是内容),时间,数值进行本地化处理。
在/WEB-INF/views/下创建测试的jsp页面i18n.jsp和i18n2.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<fmt:message key="i18n.username"></fmt:message>
<br><br>
<a href="i18n2">I18N2 PAGE</a>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<fmt:message key="i18n.password"></fmt:message>
<br><br>
<a href="i18n">I18N PAGE</a>
</body>
</html>
在/src下创建国际化配置文件i18n.properties,i18n_en_US.properties,i18n_zh_CN.properties:
i18n.username=username
i18n.password=password
i18n.username=username
i18n.password=password
i18n.username=\u7528\u6237\u540D
i18n.password=\u5BC6\u7801
在首页/index.jsp中增加到i18n.jsp的链接:
<a href="i18n">I18N PAGE</a>
在Spring MVC的配置文件dispatcherServlet-servlet.xml中增加国际化资源文件配置和直接转发页面配置:
<!-- 配置国际化资源文件 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
<!-- 配置直接转发的页面 -->
<!-- 可以直接响应转发的页面,而无需再经过Handler的方法 -->
<mvc:view-controller path="/i18n" view-name="i18n"/>
<mvc:view-controller path="/i18n2" view-name="i18n2"/>
根据浏览器语言配置的不同,在i18n.jsp和i18n2.jsp会显示对应的语言内容。
(2)在Bean中注入ResourceBundleMessageSource的实例,使用其对应的getMessage方法,可以在Bean中获取国际化资源文件Locale对应的消息。
修改Handler:
@Autowired
private ResourceBundleMessageSource messageSource;
@RequestMapping("/i18n")
public String testI18n(Locale locale) {
String val = messageSource.getMessage("i18n.username", null, locale);
System.out.println(val);
return "i18n";
}
点击到/i18n的链接,console输出:
用户名
(3)配置LocaleResolver和LocaleChangeInterceptor通过超链接切换Locale,而不再依赖于浏览器的语言设置情况。
在Spring MVC的配置文件dispatcherServlet-servlet.xml中配置国际化解析器和拦截器:
<!-- 配置SessionLocaleResolver -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>
<!-- 配置LocaleChangeInterceptor -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
</mvc:interceptors>
在i18n.jsp中增加链接:
<br>
<br>
<a href="i18n?locale=zh_CH">中文</a>
<br>
<br>
<a href="i18n?locale=en_US">English</a>
点击中文链接,输出中文,点击English链接,输出英文。
文件上传
Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。Spring 用Jakarta Commons FileUpload 技术实现了一个MultipartResolver 实现类:CommonsMultipartResovler。
Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver。
配置 MultipartResolver:
- defaultEncoding: 必须和用户 JSP 的 pageEncoding 属性一致,以便正确解析表单的内容。
- 为了让 CommonsMultipartResovler 正确工作,必须先将 Jakarta Commons FileUpload 及 Jakarta Commons io的类包添加到类路径下。
导入Jar包:
commons-io-2.6.jar
commons-fileupload-1.4.jar
在Spring MVC的配置文件dispatcherServlet-servlet.xml中配置MultipartResovler:
<!-- 配置MultipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="1024000"></property>
</bean>
在index.jsp中创建上传表单:
<form action="testFileUpload" method="post" enctype="multipart/form-data">
File:<input type="file" name="file">
Desc:<input type="text" name="desc">
<input type="submit" value="submit">
</form>
在handler中创建方法:
@RequestMapping("/testFileUpload")
public String testFileUpload(@RequestParam("desc") String desc, @RequestParam("file") MultipartFile file) throws IOException {
System.out.println("desc: " + desc);
System.out.println("getOriginalFilename: " + file.getOriginalFilename());
System.out.println("getInputStream: " + file.getInputStream());
return "success";
}
创建success.jsp页面:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>Success Page</h4>
</body>
</html>
输出:
desc: abcfile
getOriginalFilename: parameter.txt
getInputStream: java.io.FileInputStream@330471f7
拦截器
Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口:
- preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求request进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
- postHandle():这个方法在业务处理器处理完请求后,DispatcherServlet向客户端返回响应前被调用,在该方法中对用户请求request进行处理。
- afterCompletion():这个方法在DispatcherServlet完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
配置自定义的拦截器:
package com.stone.springmvc.interceptors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class FirstInterceptor implements HandlerInterceptor {
/**
* 该方法在目标方法之前被调用。可以考虑做权限、日志、事物等
* 若返回值为true,则继续调用后续的拦截器和目标方法。
* 若返回值为false,则不会再调用后续的拦截器和目标方法
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("[FirstInterceptor] preHandle");
return true;
}
/**
* 调用目标方法之后,渲染视图之前被调用,可以对请求域中的属性或视图做出修改。
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("[FirstInterceptor] postHandle");
}
/**
* 在渲染视图之后被调用,释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("[FirstInterceptor] afterCompletion");
}
}
package com.stone.springmvc.interceptors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class SecondInterceptor implements HandlerInterceptor {
/**
* 该方法在目标方法之前被调用。可以考虑做权限、日志、事物等
* 若返回值为true,则继续调用后续的拦截器和目标方法。
* 若返回值为false,则不会再调用后续的拦截器和目标方法
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("[SecondInterceptor] preHandle");
return true;
}
/**
* 调用目标方法之后,渲染视图之前被调用,可以对请求域中的属性或视图做出修改。
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("[SecondInterceptor] postHandle");
}
/**
* 在渲染视图之后被调用,释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("[SecondInterceptor] afterCompletion");
}
}
在Spring MVC的配置文件dispatcherServlet-servlet.xml中配置:
<mvc:interceptors>
<!-- 配置自定义的拦截器 -->
<bean class="com.stone.springmvc.interceptors.FirstInterceptor"></bean>
<!-- 配置拦截器起(不起)作用的路径 -->
<mvc:interceptor>
<mvc:mapping path="/emps"/>
<bean class="com.stone.springmvc.interceptors.SecondInterceptor"></bean>
</mvc:interceptor>
<!-- 配置LocaleChangeInterceptor -->
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
</mvc:interceptors>
输出:
[FirstInterceptor] preHandle
[SecondInterceptor] preHandle
[SecondInterceptor] postHandle
[FirstInterceptor] postHandle
[SecondInterceptor] afterCompletion
[FirstInterceptor] afterCompletion
如果两个拦截器的preHandle的返回值都是true,则执行顺序为:

如果第二个拦截器的preHandle的返回值都是false,则执行顺序为:

异常处理
Spring MVC通过HandlerExceptionResolver处理程序的异常,包括Handler映射、数据绑定以及目标方法执行时发生的异常。
- Spring MVC提供的HandlerExceptionResolver的实现类
- DispatcherServlet默认装配的HandlerExceptionResolver
ExceptionHandlerExceptionResolver:
- 主要处理Handler中用@ExceptionHandler注解定义的方法。
- @ExceptionHandler注解定义的方法优先级问题:例如发生的是NullPointerException,但是声明的异常有RuntimeException和Exception,此候会根据异常的最近继承关系找到继承深度最浅的那个@ExceptionHandler注解方法,即标记了RuntimeException的方法。
- ExceptionHandlerMethodResolver内部若找不到@ExceptionHandler注解的话,会找@ControllerAdvice中的@ExceptionHandler注解方法。
ResponseStatusExceptionResolver:
- 在异常及异常父类中找到@ResponseStatus注解,然后使用这个注解的属性进行处理。
- 定义一个@ResponseStatus注解修饰的异常类。
DefaultHandlerExceptionResolver:
- 对一些特殊的异常进行处理,比如NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException等。
SimpleMappingExceptionResolver:
- 如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。
(1)使用ExceptionHandlerExceptionResolver处理@ExceptionHandler注解修饰的方法
handler:
// @ExceptionHandler({RuntimeException.class})
// public ModelAndView handleArithmeticException2(Exception ex){
// System.out.println("[出异常了]:" + ex);
// ModelAndView mv = new ModelAndView("error");
// mv.addObject("exception", ex);
// return mv;
// }
/**
* 1.在@ExceptionHandler修饰方法的入参中可以加入Exception类型的参数,该参数即对应发生的异常对象
* 2.在@ExceptionHandler修饰方法的入参中不能传入Map,若希望把异常信息传导页面上,需要使用ModelAndView作为返回值
* 3.@ExceptionHandler方法标记的异常有优先级的问题。
* 4.@ControllerAdvice:如果在当前Handler中找不到@ExceptionHandler方法来处理当前方法出现的异常,
* 则将去@ControllerAdvice标记的类中查找@ExceptionHandler标记的方法来处理异常。
*/
// @ExceptionHandler({ArithmeticException.class})
// public ModelAndView handleArithmeticException(Exception ex){
// System.out.println("出异常了:" + ex);
// ModelAndView mv = new ModelAndView("error");
// mv.addObject("exception", ex);
// return mv;
// }
@RequestMapping("/testExceptionHandlerExceptionResolver")
public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i) {
System.out.println("result: " + (10 / i));
return "success";
}
error.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>Error Page</h4>
${exception }
</body>
</html>
全局的异常处理类:
package com.stone.springmvc.test;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@ControllerAdvice
public class HandlerException {
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception ex){
System.out.println("--->出异常了:" + ex);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", ex);
return mv;
}
}
(2)使用ResponseStatusExceptionResolver处理@ResponseStatus注解的异常类或者方法
index.jsp:
<a href="testResponseStatusExceptionResolver?i=10">Test ResponseStatusExceptionResolver</a>
handler:
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="测试") //修饰方法,调用这个方法就会抛出"value"指定的异常
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i) {
if (i == 13) {
throw new UserNameNotMatchPassswordException();
}
System.out.println("testResponseStatusExceptionResolver...");
return "success";
}
异常类:
package com.stone.springmvc.test;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名密码不匹配") //修饰类,调用这个类才会抛出"value"指定的异常
public class UserNameNotMatchPassswordException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
(3)使用SimpleMappingExceptionResolver在配置文件中将异常类名映射为视图
在Spring MVC的配置文件dispatcherServlet-servlet.xml中配置SimpleMappingExceptionResolver:
<!-- 使用SimpleMappingExceptionResolver来映射异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>
index.jsp:
<a href="testSimpleMappingExceptionResolver?i=21">Test SimpleMappingExceptionResolver</a>
handler:
@RequestMapping("/testSimpleMappingExceptionResolver")
public String testSimpleMappingExceptionResolver(@RequestParam("i") int i) {
String[] vals = new String[10];
System.out.println(vals[i]);
return "success";
}
Spring整合Spring MVC
Spring配置文件beans.xml:
<?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-4.3.xsd">
<context:component-scan base-package="com.stone.springmvc">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
<!-- 配置数据库,整合其他框架,事物等 -->
</beans>
Spring MVC配置文件springDispatcherServlet-servlet.xml:
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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-4.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!--
是否需要进行Spring整合Spring MVC吗?
还是否需要再加入Spring的IOC容器?
还是否需要在web.xml文件中配置启动Spring IOC容器的ContextLoaderListener?
1.需要:通常情况下,类似于数据源,事物,整合其他框架都是放在Spring的配置文件中(而不是放在Spring MVC的配置文件中)
实际上放入Spring配置文件对应的IOC容器中的还有Service和Dao。
2.不需要:都放在Spring MVC的配置文件中,也可以分多个Spring的配置文件,然后使用import节点导入其他的配置文件
-->
<!--
问题:若Spring的IoC容器与Spring MVC的IoC容器扫描的包有重合的部分,就会导致有的Bean会被创建2次
解决:
1.使Spring的IoC容器扫描的包和Spring MVC的IoC容器扫描的包没有重合的部分。实现起来有点困难。
2.使用exclude-filter和include-filter子节点来规定只能扫描的注解
-->
<!--
Spring MVC IoC容器中的Bean可以来引用Spring IoC容器中的Bean。
反之则不行,Spring IoC容器中的Bean却不能来引用Spring MVC IoC容器中的Bean
-->
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.stone.springmvc" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
<!-- 配置视图解析器:如何把handler方法返回值解析为实际的物理视图 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<mvc:default-servlet-handler/>
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<!-- 配置启动Spring IoC 容器的Listener -->
<!-- needed for ContextLoaderListener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
<!-- Bootstraps the root web application context before servlet initialization -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
使用Spring MVC时,整个Web应用程序按如下顺序启动:
- 启动Tomcat服务器;
- Tomcat读取
web.xml并初始化DispatcherServlet; DispatcherServlet创建IoC容器并自动注册到ServletContext中。
启动后,浏览器发出的HTTP请求全部由DispatcherServlet接收,并根据配置转发到指定Controller的指定方法处理。
