Spring MVC
入门程序
引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.8.RELEASE</version> </dependency>
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
<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.xml1 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"> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <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.xml1 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"> <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.jsp1 2 3 4 5
| <html> <body> <h2>Spring MVC FirstController!</h2> </body> </html>
|
创建控制器
FirstController.java1 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") 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.java1 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.java1 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 { @RequestMapping(method = RequestMethod.GET) public void get() { System.out.println("RequestMethod.GET"); }
@RequestMapping(method = RequestMethod.DELETE) public void delete() { System.out.println("RequestMethod.DELETE"); }
@RequestMapping(method = RequestMethod.POST) public void post() { System.out.println("RequestMethod.POST"); }
@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.java1 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 个核心步骤:
- 传递请求对象:Spring MVC 将
ServletRequest 对象传递给 DataBinder。
- 传递参数对象:将处理方法的入参对象传递给
DataBinder。
- 数据类型转换与填充:
DataBinder 调用
ConversionService
组件,完成数据类型转换、数据格式化等工作,并将
ServletRequest 对象中的消息填充到参数对象中。
- 数据合法性校验:调用
Validator
组件,对已绑定请求消息数据的参数对象进行合法性校验。
- 生成绑定结果并赋值:校验完成后生成数据绑定结果
BindingResult 对象,Spring MVC 会将
BindingResult 对象中的内容赋给处理方法的相应参数。
默认类型数据绑定
当使用 Spring MVC
默认支持的数据类型作为处理器形参类型时,参数处理适配器会自动识别并完成赋值,常见类型如下:
HttpServletRequest:用于获取 HTTP
请求信息,如请求参数、请求头、请求方法等。
HttpServletResponse:用于处理 HTTP
响应信息,如设置响应头、响应状态码、输出响应内容等。
HttpSession:用于获取当前会话(Session)中存放的对象,实现会话级数据共享。
Model/ModelMap:
Model 是一个接口,ModelMap
是其实现类。
- 两者都可用于设置模型数据,这些数据会被自动填充到
request 域中,供视图层渲染使用。
UserController.java1 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 参数值。
简单数据类型绑定
对于简单数据类型(如
String、int、double
等)以及它们的包装类,Spring MVC
会自动将请求参数值转换为对应的类型,并赋值给处理方法的参数。
UserController.java1 2 3 4
| @RequestMapping("/getUserNameAndId") public void getUserNameAndId(String username, Integer id) { System.out.println("username=" + username + ",id=" + id); }
|
这个例子中的 getUserNameAndId() 方法有两个参数
username 和 id,Spring MVC
会自动从请求参数(query string 或 表单(Form)参数)中获取,并
username 的值转换为 String
类型,以及将请求参数 id 的值转换为 Integer
类型,然后赋值给这两个参数。
@RequestParam
通过 @RequestParam
注解可以指定请求参数的名称、是否必需以及默认值等属性,通常具有以下的参数:
value:指定请求参数的名称,默认为方法参数的名称。
required:指定请求参数是否必需,默认为
true,如果
defaultValue:指定请求参数的默认值,当请求参数缺失或值为空时使用。
name:与 value
属性功能相同,都是指定请求参数的名称。
UserController.java1 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.java1 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.java1 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.java1 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.jsp1 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/> 密 码:<input type="password" name="password"/> <br/> <input type="submit" value="注册"/> </form> </body> </html>
|
在使用 POJO 绑定时,请求参数名称必须和 POJO 的属性名称一致,Spring
MVC 会根据请求参数的名称自动匹配到 POJO
的属性,并进行类型转换和赋值,未接受到的参数将使用默认值
为了防止中文乱码,需要在 web.xml 中配置字符编码过滤器:
web.xml1 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"> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <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.java1 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.xml1 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"> <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:resources mapping="/js/**" location="/js/"/>
</beans>
|
UserController.java1 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.java1 2 3 4 5 6 7 8
|
@RequestMapping("/getBirthday") public void getBirthday(@DateTimeFormat( pattern = "yyyy-MM-dd") Date birthday) { System.out.println("birthday=" + birthday); }
|
复杂数据绑定
数组绑定
创建 Product 类
Product.java1 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; 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.jsp1 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.java1 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.java1 2 3 4 5 6 7 8 9
|
@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.java1 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;
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; } }
|
在 User 类中添加订单属性
User.java1 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 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 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.java1 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.jsp1 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.java1 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.java1 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.jsp1 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.java1 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; 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.java1 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(); 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.jsp1 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.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.2</version> </dependency>
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.2</version> </dependency>
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.0</version> </dependency>
|
创建商品信息页面
product.jsp1 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.java1 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.java1 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.java1 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.java1 2 3 4 5 6
| @RequestMapping("/showPageByRequest") public String showPageByRequest(HttpServletRequest request) { System.out.println("showPageByRequest running"); request.setAttribute("username", "request"); return "register"; }
|
register.jsp1
| <input type="text" name="username" value="${username}"/><br/>
|
这里添加了 value 属性来接受后端传递数据
也可以使用 Model 来实现参数传递
PageController.java1 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.jsp1
| <input type="password" name="password" value="${user.password}"/>
|
返回值为 ModelAndView
类型的页面跳转
PageController.java1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @RequestMapping("/showModelAndView") public ModelAndView showModelAndView() { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("username", "heima"); User user = new User(); user.setPassword("password"); modelAndView.addObject("user", user); modelAndView.setViewName("register"); return modelAndView; }
|
ModelAndView 是 Spring MVC
中兼顾视图和模型数据的一个类,使用 ModelAndView
可以同时设置视图名称和模型数据,Spring MVC
会根据设置的视图名称来解析视图,并将模型数据传递给视图进行渲染。
数据回写
普通字符串的写回
创建 DataController 类
DataController.java1 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.java1 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(); } }
|
其实是用 ObjectMapper 将 User 对象转换为
JSON 字符串,然后通过 HttpServletResponse 将 JSON
数据写回到 HTTP 响应中。
也可以用 @ResponseBody 注解来简化 JSON 数据的写回
DataController.java1 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 头。