Java 企业级应用开发 part 6 - J2EE part 6

Spring MVC

  • 配置
  • 请求映射
  • 数据绑定
  • 页面跳转
  • 数据写回

入门程序

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--Spring MVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--JSP-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>

配置 Spring MVC

/src/main/webapp/WEB-INF/ 下编辑 web.xml 文件,内容如下:

web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<!-- 配置 Spring MVC 的前端控制器 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- 配置初始化参数,用于读取 Spring MVC 的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 应用加载时创建-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

用于配置 Spring MVC 的前端控制器 DispatcherServlet,并指定了它的初始化参数 contextConfigLocation,指向了 Spring MVC 的配置文件 spring-mvc.xml。

以及 build 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
<!--设置插件-->
<plugins>
<!--具体的插件配置-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8080</port>
<path>/chapter10</path>
</configuration>
</plugin>
</plugins>
</build>

表示目录映射从 /chapter10 开始。

配置 Spring MVC 的上下文

/src/main/resources/ 下创建 spring-mvc.xml 文件,内容如下:

spring-mvc.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置 Spring MVC 要扫描的包 -->
<context:component-scan base-package="com.itheima.controller"/>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

用于配置 Spring MVC 的上下文,指定了要扫描的包 com.itheima.controller,以及视图解析器 InternalResourceViewResolver 的前缀和后缀。

创建 jsp 页面

webapp/WEB-INF/pages/ 下创建 success.jsp 文件,内容如下:

success.jsp
1
2
3
4
5
<html>
<body>
<h2>Spring MVC FirstController!</h2>
</body>
</html>

创建控制器

FirstController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

//设置当前类为处理器类
@Controller
public class FirstController {
//设定当前方法的访问映射地址
@RequestMapping("/firstController")
//设置当前方法返回值类型为 String,用于指定请求完成后跳转的页面
public String sayHello() {
System.out.println("访问到 FirstController!");
//设定具体跳转的页面
return "success";
}
}

运行程序

采用 mvn tomcat7:run 命令运行程序,在浏览器中访问 http://localhost:8080/chapter10/firstController,即可看到页面显示 “Spring MVC FirstController!”,同时控制台输出 “访问到 FirstController!”。

Spring MVC 基本工作原理

handlerMapping 处理器映射器

当用户发送请求时,Spring MVC 会根据请求的 URL 查找对应的处理器(Handler)。这个过程由 HandlerMapping 组件完成。HandlerMapping 会扫描应用程序中的控制器类,找到与请求 URL 匹配的方法,并将其封装为 HandlerExecutionChain。

handlerAdapter 处理器适配器

当 DispatcherServlet 拿到 HandlerExecutionChain 后,会遍历所有 HandlerAdapter,找到合适的适配器来执行处理器方法。HandlerAdapter 负责调用处理器方法,并将请求参数传递给方法的参数,以及封装响应结果返回给 DispatcherServlet。

viewResolver 视图解析器

当处理器方法执行完成后,返回一个视图名称(如 “success”)。DispatcherServlet 会使用 ViewResolver 来解析这个视图名称,找到对应的视图对象(如 JSP 文件),并将模型数据传递给视图进行渲染,最终生成 HTML 响应返回给客户端。

DispatcherServlet 工作流程

本质是一个 Servlet,需要在 web.xml 中进行配置,具体来说配置分为两个部分,第一个部分是 Spring MVC 的前端控制器 DispatcherServlet 的配置,第二个部分是 DispatcherServlet 的 URL 映射配置。

代码参考 配置 Spring MVC 部分。

若不配置 <init-param>,则默认会加载 /WEB-INF/<servlet-name>-servlet.xml 作为 Spring MVC 的配置文件。
<load-on-startup> 的值为正整数或 0,表示应用加载时就加载并初始化这个 Servlet,数字越小优先级越高,越先被加载;若没有或为负数,则在第一次请求时加载。

@Controller

传统的处理器类需要实现 Controller 接口,并重写 handleRequest 方法来处理请求。以及通过大量 XML 配置 URL 映射规则,而使用 @Controller 注解可以减少代码侵入,并通过 @RequestMapping 注解直接在方法上定义 URL 映射规则,使得代码更加简洁和易于维护。

@RequestMapping

@RequestMapping 注解可以用在类上和方法上,分别用于定义类级别和方法级别的 URL 映射规则。类级别的 @RequestMapping 定义了一个基础 URL 路径,而方法级别的 @RequestMapping 则定义了相对于基础路径的具体 URL 映射规则。

FirstController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping(value = "/springMVC")
public class FirstController {
@RequestMapping(value = "/firstController")
public void sayHello() {
System.out.println("hello Spring MVC");
}
}

在这个 Controller 中,类级别的 @RequestMapping 定义了基础路径 /springMVC,方法级别的 @RequestMapping 定义了相对于基础路径的具体 URL 映射规则 /firstController。因此,访问 URL http://localhost:8080/chapter10/springMVC/firstController 就会触发这个方法。

需要注意的我们配置的远古 Spring 最高只能用 Tomcat 9

除此之外,@RequestMapping 注解还可以指定请求方法(如 GET、POST)、请求参数、请求头等条件

AuthController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class AuthController {
//设定当前方法的访问映射地址列表
@RequestMapping(value = {"/addUser", "/deleteUser"})
public void checkAuth() {
System.out.println("增删操作校验");
}
}

在这个 Controller 中,@RequestMapping 注解的 value 属性指定了两个 URL 映射规则 /addUser/deleteUser,因此访问这两个 URL 都会触发 checkAuth() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/method")
public class MethodController {
//处理请求方式为 GET 的请求
@RequestMapping(method = RequestMethod.GET)
public void get() {
System.out.println("RequestMethod.GET");
}

//处理请求方式为 DELETE 的请求
@RequestMapping(method = RequestMethod.DELETE)
public void delete() {
System.out.println("RequestMethod.DELETE");
}

//处理请求方式为 POST 的请求
@RequestMapping(method = RequestMethod.POST)
public void post() {
System.out.println("RequestMethod.POST");
}

//处理请求方式为 PUT 的请求
@RequestMapping(method = RequestMethod.PUT)
public void put() {
System.out.println("RequestMethod.PUT");
}

@RequestMapping(value = "/method",
method = {RequestMethod.GET, RequestMethod.POST})
public void getAndPost() {
System.out.println("RequestMethod.GET+RequestMethod.POST");
}
}

在这个 Controller 中,@RequestMapping 注解的 method 属性指定了不同的请求方法(GET、DELETE、POST、PUT),因此访问 URL /method 时,根据请求方法的不同会触发不同的方法。同时,最后一个方法 getAndPost() 通过指定多个请求方法(GET 和 POST)来处理这两种请求方式。

ParamsController.java
1
2
3
4
5
6
7
8
9
10
11
12
package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ParamsController {
@RequestMapping(value = "/params", params = "id=1")
public void findById(String id) {
System.out.println("id=" + id);
}
}

在这个 Controller 中,@RequestMapping 注解的 params 属性指定了请求参数的条件,只有当请求 URL 包含参数 id=1 时才会触发 findById() 方法,并将参数值传递给方法的参数 id

数据绑定

Spring MVC 数据绑定的信息处理流程可分为以下 5 个核心步骤:

  1. 传递请求对象:Spring MVC 将 ServletRequest 对象传递给 DataBinder
  2. 传递参数对象:将处理方法的入参对象传递给 DataBinder
  3. 数据类型转换与填充DataBinder 调用 ConversionService 组件,完成数据类型转换、数据格式化等工作,并将 ServletRequest 对象中的消息填充到参数对象中。
  4. 数据合法性校验:调用 Validator 组件,对已绑定请求消息数据的参数对象进行合法性校验。
  5. 生成绑定结果并赋值:校验完成后生成数据绑定结果 BindingResult 对象,Spring MVC 会将 BindingResult 对象中的内容赋给处理方法的相应参数。

默认类型数据绑定

当使用 Spring MVC 默认支持的数据类型作为处理器形参类型时,参数处理适配器会自动识别并完成赋值,常见类型如下:

  • HttpServletRequest:用于获取 HTTP 请求信息,如请求参数、请求头、请求方法等。
  • HttpServletResponse:用于处理 HTTP 响应信息,如设置响应头、响应状态码、输出响应内容等。
  • HttpSession:用于获取当前会话(Session)中存放的对象,实现会话级数据共享。
  • Model/ModelMap
    • Model 是一个接口,ModelMap 是其实现类。
    • 两者都可用于设置模型数据,这些数据会被自动填充到 request 域中,供视图层渲染使用。
UserController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.itheima.controller;

import com.itheima.pojo.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

@Controller
public class UserController {
@RequestMapping("/getUserId")
public void getUserId(HttpServletRequest request) {
String userid = request.getParameter("userid");
System.out.println("userid=" + userid);
}
}

这个例子中的,getUserId() 方法的参数类型是 HttpServletRequest,Spring MVC 会自动将当前 HTTP 请求对象传递给这个参数,使得我们可以通过 request.getParameter("userid") 来获取请求中的 userid 参数值。

简单数据类型绑定

对于简单数据类型(如 Stringintdouble 等)以及它们的包装类,Spring MVC 会自动将请求参数值转换为对应的类型,并赋值给处理方法的参数。

UserController.java
1
2
3
4
@RequestMapping("/getUserNameAndId")
public void getUserNameAndId(String username, Integer id) {
System.out.println("username=" + username + ",id=" + id);
}

这个例子中的 getUserNameAndId() 方法有两个参数 usernameid,Spring MVC 会自动从请求参数(query string 或 表单(Form)参数)中获取,并 username 的值转换为 String 类型,以及将请求参数 id 的值转换为 Integer 类型,然后赋值给这两个参数。

@RequestParam

通过 @RequestParam 注解可以指定请求参数的名称、是否必需以及默认值等属性,通常具有以下的参数:

  • value:指定请求参数的名称,默认为方法参数的名称。
  • required:指定请求参数是否必需,默认为 true,如果
  • defaultValue:指定请求参数的默认值,当请求参数缺失或值为空时使用。
  • name:与 value 属性功能相同,都是指定请求参数的名称。
UserController.java
1
2
3
4
@RequestMapping("/getUserName")
public void getUserName(@RequestParam(value = "name", required = false, defaultValue = "itheima") String username) {
System.out.println("username=" + username);
}

这里将请求中的 name 参数绑定到方法参数 username 上,并且设置了 required = false 表示这个参数不是必需的,如果请求中没有 name 参数或者值为空,则会使用默认值 “itheima”。

@PathVariable

通过 @PathVariable 注解可以将 URL 中的占位符(路径变量)绑定到处理方法的参数上,常用于 RESTful 风格的 URL 设计。

UserController.java
1
2
3
4
5
@RequestMapping("/user/{name}")
public void getPathVariable(@PathVariable(value = "name")
String username) {
System.out.println("username=" + username);
}

这里将 URL 中的 {name} 占位符绑定到方法参数 username 上,当访问 URL /user/itheima 时,username 的值将会是 “itheima”。

POJO 绑定

当处理方法的参数类型是一个普通的 Java 对象(POJO)时,Spring MVC 会自动将请求参数与 POJO 的属性进行绑定。这个过程称为数据绑定(Data Binding)。

我们定义 User

User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.itheima.pojo;

import java.util.List;

public class User {
private String username; //用户名
private String password; //用户密码

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}
UserController.java
1
2
3
4
5
6
7
8
9
/**
* 接收表单用户信息
*/
@RequestMapping("/registerUser")
public void registerUser(User user) {
String username = user.getUsername();
String password = user.getPassword();
System.out.println("username=" + username + ",password=" + password);
}

创建 jsp 文件实现表单

register.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>注册</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/registerUser" method="post">
用户名:<input type="text" name="username" value="${username}"/><br/>
密&nbsp;&nbsp;&nbsp;码:<input type="password" name="password"/>
<br/>
<input type="submit" value="注册"/>
</form>
</body>
</html>

在使用 POJO 绑定时,请求参数名称必须和 POJO 的属性名称一致,Spring MVC 会根据请求参数的名称自动匹配到 POJO 的属性,并进行类型转换和赋值,未接受到的参数将使用默认值

为了防止中文乱码,需要在 web.xml 中配置字符编码过滤器:

web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<!-- 配置Spring MVC的前端控制器 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- 配置初始化参数,用于读取Spring MVC的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 应用加载时创建-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置编码过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

</web-app>

自定义类型转换器

通过实现 Converter 接口,可以创建自定义的类型转换器,用于将请求参数转换为特定的 Java 类型。

DateConverter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.itheima.convert;

import org.springframework.core.convert.converter.Converter;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 自定义日期转换器
*/
public class DateConverter implements Converter<String, Date> {
// 定义日期格式
private String datePattern = "yyyy-MM-dd";

@Override
public Date convert(String source) {
// 格式化日期
SimpleDateFormat sdf = new SimpleDateFormat(datePattern);
try {
return sdf.parse(source);
} catch (Exception e) {
throw new IllegalArgumentException(
"无效的日期格式,请使用这种格式:" + datePattern);
}
}
}

在这个例子中,我们创建了一个 DateConverter 类,实现了 Converter<String, Date> 接口,用于将字符串类型的日期转换为 Date 类型。我们定义了一个日期格式 yyyy-MM-dd,并在 convert() 方法中使用 SimpleDateFormat 来解析输入的字符串,如果解析失败则抛出异常。

Spring MVC 使用这个自定义的类型转换器,需要在 spring-mvc.xml 中添加如下配置:

spring-mvc.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置创建 spring 容器要扫描的包 -->
<context:component-scan base-package="com.itheima.controller"/>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 配置类型转换器工厂 -->
<bean id="converterService" class=
"org.springframework.context.support.ConversionServiceFactoryBean">
<!-- 给工厂注入一个新的类型转换器 -->
<property name="converters">
<array>
<!-- 配置自定义类型转换器 -->
<bean class="com.itheima.convert.DateConverter"/>
</array>
</property>
</bean>
<!-- 装载转换器 -->
<mvc:annotation-driven conversion-service="converterService"/>
<!-- <mvc:annotation-driven/>-->
<!--配置静态资源的访问映射,此配置中的文件,将不被前端控制器拦截 -->
<mvc:resources mapping="/js/**" location="/js/"/>

</beans>
UserController.java
1
2
3
4
5
6
7
/**
* 使用自定义类型数据绑定日期数据
*/
@RequestMapping("/getBirthday")
public void getBirthday(Date birthday) {
System.out.println("birthday="+birthday);
}

事实上可以通过 <mvc:annotation-driven/> 来启用 Spring MVC 的默认类型转换器,通过 @DateTimeFormat 注解来指定日期格式,无需自定义类型转换器

UserController.java
1
2
3
4
5
6
7
8
/**
* 使用@DateTimeFormat注解绑定日期数据
*/
@RequestMapping("/getBirthday")
public void getBirthday(@DateTimeFormat(
pattern = "yyyy-MM-dd") Date birthday) {
System.out.println("birthday=" + birthday);
}

复杂数据绑定

数组绑定

创建 Product

Product.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.itheima.pojo;

public class Product {
private String proId; //商品id
private String proName; //商品名称

public String getProId() {
return proId;
}

public void setProId(String proId) {
this.proId = proId;
}

public String getProName() {
return proName;
}

public void setProName(String proName) {
this.proName = proName;
}
}

创建商品提交页面

products.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
<title>提交商品</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/getProducts"
method="post">
<table width="220px" border="1">
<tr>
<td>选择</td>
<td>商品名称</td>
</tr>
<tr>
<td>
<input name="proIds" value="1" type="checkbox">
</td>
<td>Java基础教程</td>
</tr>
<tr>
<td>
<input name="proIds" value="2" type="checkbox">
</td>
<td>JavaWeb案例</td>
</tr>
<tr>
<td>
<input name="proIds" value="3" type="checkbox">
</td>
<td>SSM框架实战</td>
</tr>
</table>
<input type="submit" value="提交商品"/>
</form>
</body>
</html>

建立 ProductController

ProductController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.itheima.controller;

import com.itheima.pojo.Product;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
public class ProductController {
/**
* 获取商品列表
*/
@RequestMapping("/getProducts")
public void getProducts(String[] proIds) {
for (String proId : proIds) {
System.out.println("获取到了Id为" + proId + "的商品");
}
}
}

请求中相同 name 的参数被以数组的形式传递给处理方法的参数 proIds,Spring MVC 会自动将请求参数 proIds 的值转换为一个字符串数组,并赋值给方法参数 proIds

集合绑定

ProductController.java
1
2
3
4
5
6
7
8
9
/**
* 获取商品列表(使用List绑定数据)
*/
@RequestMapping("/getProducts")
public void getProducts(@RequestParam("proIds") List<String> proIds) {
for (String proId : proIds) {
System.out.println("获取到了Id为" + proId + "的商品");
}
}

在这个例子中,处理方法 getProducts() 的参数类型是 List<String>,并且使用了 @RequestParam("proIds") 注解来指定请求参数的名称。Spring MVC 会自动将请求参数 proIds 的值转换为一个字符串列表,并赋值给方法参数 proIds,若不使用 @RequestParam 注解,则会发生错误,因为 Spring MVC 会尝试创建 List 对象,但是本身 List 是接口无法创建对象。

复杂 POJO 绑定

创建订单类

Order.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.itheima.pojo;

import java.util.HashMap;

public class Order {
private String orderId; //订单id

public String getOrderId() {
return orderId;
}

public void setOrderId(String orderId) {
this.orderId = orderId;
}
}

User 类中添加订单属性

User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.itheima.pojo;

import java.util.List;

public class User {
private String username; //用户名
private String password; //用户密码
// private Order order; //订单
private List<Order> orders; //用户订单
private List<String> address; //订单地址

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

// public Order getOrder() {
// return order;
// }
//
// public void setOrder(Order order) {
// this.order = order;
// }

public List<Order> getOrders() {
return orders;
}

public void setOrders(List<Order> orders) {
this.orders = orders;
}

public List<String> getAddress() {
return address;
}

public void setAddress(List<String> address) {
this.address = address;
}
}

编辑 UserController

UserController.java
1
2
3
4
5
6
@RequestMapping("/findOrderWithUser")
public void findOrderWithUser(User user) {
String username = user.getUsername();
String orderId = user.getOrder().getOrderId();
System.out.println("username=" + username + ",orderId=" + orderId);
}

创建 order.jsp 页面

order.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>订单信息</title>
</head>
<body>
<form
action="${pageContext.request.contextPath }/findOrderWithUser"
method="post">
所属用户:<input type="text" name="username"/><br/>
订单编号:<input type="text" name="order.orderId"/><br/>
<input type="submit" value="查询"/>
</form>
</body>
</html>
List 类型数据绑定
User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.itheima.pojo;

import java.util.List;

public class User {
private String username; //用户名
private String password; //用户密码
private Order order; //订单
private List<Order> orders; //用户订单
private List<String> address; //订单地址

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public Order getOrder() {
return order;
}

public void setOrder(Order order) {
this.order = order;
}

public List<Order> getOrders() {
return orders;
}

public void setOrders(List<Order> orders) {
this.orders = orders;
}

public List<String> getAddress() {
return address;
}

public void setAddress(List<String> address) {
this.address = address;
}
}

创建 OrderController

OrderController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.itheima.controller;

import com.itheima.pojo.Order;
import com.itheima.pojo.Product;
import com.itheima.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.HashMap;
import java.util.List;
import java.util.Set;

@Controller
public class OrderController {
/**
* 获取用户中的订单信息
*/
@RequestMapping("/showOrders")
public void showOrders(User user) {
List<Order> orders = user.getOrders();
List<String> addressList = user.getAddress();
System.out.println("订单:");
for (int i = 0; i < orders.size(); i++) {
Order order = orders.get(i);
String address = addressList.get(i);
System.out.println("订单Id:" + order.getOrderId());
System.out.println("订单配送地址:" + address);
}
}
}

创建 orders.jsp 页面

orders.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head><title>订单信息</title></head>
<body>
<form action="${pageContext.request.contextPath }/showOrders"
method="post">
<table width="220px" border="1">
<tr>
<td>订单号</td>
<td>订单名称</td>
<td>配送地址</td>
</tr>
<tr>
<td>
<input name="orders[0].orderId" value="1" type="text">
</td>
<td>
<input name="orders[0].orderName" value="Java基础教程"
type="text">
</td>
<td><input name="address" value="北京海淀" type="text"></td>
</tr>
<tr>
<td>
<input name="orders[1].orderId" value="2" type="text">
</td>
<td>
<input name="orders[1].orderName" value="JavaWeb案例"
type="text">
</td>
<td><input name="address" value="北京昌平" type="text"></td>
</tr>
<tr>
<td>
<input name="orders[2].orderId" value="3" type="text">
</td>
<td>
<input name="orders[2].orderName" value="SSM框架实战"
type="text">
</td>
<td><input name="address" value="北京朝阳" type="text"></td>
</tr>
</table>
<input type="submit" value="订单信息"/>
</form>
</body>
</html>

对于简单类型的 List 绑定,参数 name 相同且和 POJO 中成员名称相同即可,而对于对象类型的 List 绑定,参数 name 需要按照数组格式来指定,并符合对象层侧结构。

Map 类型数据绑定
Order.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.itheima.pojo;

import java.util.HashMap;

public class Order {
private String orderId; //订单id
private HashMap<String, Product> productInfo; //商品信息

public String getOrderId() {
return orderId;
}

public void setOrderId(String orderId) {
this.orderId = orderId;
}

public HashMap<String, Product> getProductInfo() {
return productInfo;
}

public void setProductInfo(HashMap<String, Product> productInfo) {
this.productInfo = productInfo;
}
}

修改 OrderController

OrderController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 获取订单信息
*/
@RequestMapping("/orderInfo")
public void getOrderInfo(Order order) {
String orderId = order.getOrderId(); //获取订单id
//获取商品信息
HashMap<String, Product> orderInfo = order.getProductInfo();
Set<String> keys = orderInfo.keySet();
System.out.println("订单id:" + orderId);
System.out.println("订单商品信息:");
for (String key : keys) {
Product product = orderInfo.get(key);
String proId = product.getProId();
String proName = product.getProName();
System.out.println(key + "类~" + "商品id:" + proId +
",商品名称:" + proName);
}
}

创建订单信息页面

order_info.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>订单信息</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/orderInfo"
method="post">
<table border="1">
<tr>
<td colspan="2">
订单id:<input type="text" name="orderId" value="1">
</td>
</tr>
<tr>
<td>商品Id</td>
<td>商品名称</td>
</tr>
<tr>
<td>
<input name="productInfo['生鲜'].proId" value="1"
type="text">
</td>
<td>
<input name="productInfo['生鲜'].proName"
value="三文鱼" type="text">
</td>
</tr>
<tr>
<td>
<input name="productInfo['酒水'].proId" value="2"
type="text">
</td>
<td>
<input name="productInfo['酒水'].proName" value="红牛"
type="text">
</td>
</tr>
</table>
<input type="submit" value="提交"/>
</form>
</body>
</html>

命名要求类似对象类型的 List 绑定,参数 name 需要按照数组格式来指定,并符合对象层侧结构,同时还需要使用 ['key'] 的方式来指定 Map 的键。

JSON 数据绑定

通过 HttpMessageConverter 接口实现保文和对象之间的转换,Spring MVC 内置了多种 HttpMessageConverter 实现类,其中 MappingJackson2HttpMessageConverter 用于处理 JSON 数据的转换。

需要引入 Jackson 相关的依赖

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--Jackson转换核心包依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.2</version>
</dependency>
<!--Jackson转换的数据绑定包依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.2</version>
</dependency>
<!--Jackson JSON转换注解包-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>

创建商品信息页面

product.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head><title>异步提交商品</title>
<script type="text/javascript"
src="${pageContext.request.contextPath }/js/jquery-3.6.0.js"></script>
</head>
<body>
<form id="products">
<table border="1">
<tr>
<th>商品id</th>
<th>商品名称</th>
<th>提交</th>
</tr>
<tr>
<td>
<input name="proId" value="1" id="proId" type="text">
</td>
<td><input name="proName" value="三文鱼"
id="proName" type="text"></td>
<td><input type="button" value="提交单个商品"
onclick="sumbmitProduct()"></td>
</tr>
<tr>
<td><input name="proId" value="2" id="proId2"
type="text"></td>
<td><input name="proName" value="红牛"
id="proName2" type="text"></td>
<td><input type="button" value="提交多个商品"
onclick="submitProducts()"></td>
</tr>
</table>
</form>
<script type="text/javascript">
function sumbmitProduct() {
var proId = $("#proId").val();
var proName = $("#proName").val();
$.ajax({
url: "${pageContext.request.contextPath }/getProduct",
type: "post",
data: JSON.stringify({proId: proId, proName: proName}),
contentType: "application/json;charset=UTF-8",
dataType: "json",
success: function (response) {
alert(response);
}
});
}

function submitProducts() {
var pro1 = {proId: $("#proId").val(), proName: $("#proName").val()}
var pro2 = {proId: $("#proId2").val(), proName: $("#proName2").val()}
$.ajax({
url: "${pageContext.request.contextPath }/getProductList",
type: "post",
data: JSON.stringify([pro1, pro2]),
contentType: "application/json;charset=UTF-8",
dataType: "json",
success: function (response) {
alert(response);
}
});
}
</script>
</body>
</html>

引入的js需要通过 <mvc:resources mapping="/js/**" location="/js/"/> 来配置静态资源的访问映射

通过 @RequestBody 注解实现 JSON 数据绑定,修改 ProductController 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 获取单个商品信息
*/
@RequestMapping("/getProduct")
public void getProduct(@RequestBody Product product) {
String proId = product.getProId();
String proName = product.getProName();
System.out.println("获取到了Id为" + proId + "名称为" + proName + "的商品");
}

/**
* 获取多个商品信息
*/
@RequestMapping("/getProductList")
public void getProductList(@RequestBody List<Product> products) {
for (Product product : products) {
String proId = product.getProId();
String proName = product.getProName();
System.out.println("获取到了Id为" + proId + "名称为" +
proName + "的商品");
}
}

页面跳转

返回值为 void 类型的页面跳转

方法执行后会跳转到默认页面,通过视图配置器的前后缀拼接而成

创建页面跳转类

PageController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.itheima.controller;

import com.itheima.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

@Controller
public class PageController {
@RequestMapping("/register")
public void showPageByVoid() {
System.out.println("showPageByVoid running");
}
}

返回值为 String 类型的页面跳转

PageController.java
1
2
3
4
5
@RequestMapping("/showPageByString")
public String showPageByString() {
System.out.println("showPageByString running");
return "register";
}

这样就会跳转到 register.jsp 页面,因为视图解析器会将返回的字符串 “register” 与前缀和后缀拼接成完整的视图路径 /WEB-INF/pages/register.jsp

此外,还有 forward:redirect: 前缀可以用来指定页面跳转的方式

PageController.java
1
2
3
4
5
6
7
8
9
10
@RequestMapping("/showPageByForward")
public String showPageByForward() {
System.out.println("showPageByForward running");
return "forward:orders.jsp";
}
@RequestMapping("/showPageByRedirect")
public String showPageByRedirect() {
System.out.println("showPageByRedirect running");
return "redirect:http://www.itheima.com";
}

需要注意的是,使用 forward: 前缀进行页面跳转时,URL 地址栏中的路径不会发生改变,而使用 redirect: 前缀进行页面跳转时,URL 地址栏中的路径会改变为目标 URL,以及不再采用前后缀拼接的模式

还有带页面数据跳转

PageController.java
1
2
3
4
5
6
@RequestMapping("/showPageByRequest")
public String showPageByRequest(HttpServletRequest request) {
System.out.println("showPageByRequest running");
request.setAttribute("username", "request");
return "register";
}
register.jsp
1
<input type="text" name="username" value="${username}"/><br/>

这里添加了 value 属性来接受后端传递数据

也可以使用 Model 来实现参数传递

PageController.java
1
2
3
4
5
6
7
8
9
@RequestMapping("/showPageByModel")
public String showPageByModel(Model model) {
System.out.println("showPageByModel running");
model.addAttribute("username", "model");
User user = new User();
user.setPassword("password");
model.addAttribute("user", user);
return "register";
}
register.jsp
1
<input type="password" name="password" value="${user.password}"/>

返回值为 ModelAndView 类型的页面跳转

PageController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping("/showModelAndView")
public ModelAndView showModelAndView() {
//创建ModelAndView实例
ModelAndView modelAndView = new ModelAndView();
//向ModelAndView实例中添加名称为username的数据
modelAndView.addObject("username", "heima");
User user = new User();
user.setPassword("password");
//向ModelAndView实例中添加名称为user的数据
modelAndView.addObject("user", user);
//向ModelAndView实例中设置视图的名称
modelAndView.setViewName("register");
return modelAndView;
}

ModelAndView 是 Spring MVC 中兼顾视图和模型数据的一个类,使用 ModelAndView 可以同时设置视图名称和模型数据,Spring MVC 会根据设置的视图名称来解析视图,并将模型数据传递给视图进行渲染。

数据回写

普通字符串的写回

创建 DataController

DataController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.itheima.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.pojo.Product;
import com.itheima.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Controller
public class DataController {
@RequestMapping("showDataByResponse")
public void showDataByResponse(HttpServletResponse response) {
try {
response.getWriter().print("response");
} catch (IOException e) {
e.printStackTrace();
}
}
}

通过 HttpServletResponse 对象的 getWriter() 方法获取 PrintWriter 对象,然后使用 print() 方法将字符串 “response” 写回到 HTTP 响应中。

JSON 数据的写回

DataController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("showDataByJSON")
public void showDataByJSON(HttpServletResponse response) {
try {
ObjectMapper om = new ObjectMapper();
User user = new User();
user.setUsername("heima");
user.setPassword("666");
String ujson = om.writeValueAsString(user);
response.getWriter().print(ujson);
} catch (IOException e) {
e.printStackTrace();
}
}

其实是用 ObjectMapperUser 对象转换为 JSON 字符串,然后通过 HttpServletResponse 将 JSON 数据写回到 HTTP 响应中。

也可以用 @ResponseBody 注解来简化 JSON 数据的写回

DataController.java
1
2
3
4
5
6
7
@RequestMapping("getUser")
@ResponseBody
public User getUser() {
User user = new User();
user.setUsername("heima2");
return user;
}

@ResponseBody 注解表示将方法的返回值直接写回到 HTTP 响应中,而不是解析为视图名称。Spring MVC 会自动将返回的 User 对象转换为 JSON 格式,并写回到 HTTP 响应中,同时会自动修改 Content-Type 头。