SpringMVC 模型数据的处理 详解
目录
3.2 @ModelAttribute 和 Spring AOP 前置通知的区别?
一、模型数据放入 request 域
1.如何将数据放入 request 域?
1.1 默认方式(default)
我们在上一篇博文 “SpringMVC 请求数据绑定与参数映射” 中提到 —— “在 SpringMVC 中,有一个非常强大的特性,那就是 POJO 自动绑定,也就是说,我们只需在 Handler 方法的形参上直接定义一个 JavaBean(例如 Buyer 对象),当表单提交时,SpringMVC 底层就会自动实例化这个类,并将请求参数填充进去,自动完成数据的封装,并且还支持级联属性注入”。
而且我们也了解了,SpringMVC 实现这种自动封装,底层靠的是 反射调用、getter和setter调用、以及类型转换。
但是其实up 还是少说了关键的一点:SpringMVC 在自动完成数据封装的同时,还会自动把封装好的数据放入 request 域中。
1.2 默认方式放入的局限性
虽然 SpringMVC 的默认方式(自动封装并放入 request 域)极大地方便了表单数据的处理,但在实际开发中,它的局限性也非常明显:
首先是 数据来源单一,默认方式仅能处理从前端传来的数据。如果我们需要将从数据库查询到的结果、系统的计算数据或某些业务状态码传递给页面,默认方式就无能为力了。
第二个局限性是 控制粒度不够:默认方式会自动以类名首字母小写作为 key 存入域对象,有时我们希望自定义更加直观的 key。
除了默认方式外,还要三种手动放入的方式,分别是HttpServletRequest、请求方法的形参Map、ModelAndVIew对象。
1.3 手动放入的方式
(1) 使用 HttpServletRequest 对象:
其实就是原生的 Servlet 那套,最 “原始” 但也最直观。由于 SpringMVC 底层依然是基于 Servlet 运行的,所以我们可以在 Handler 方法的形参中直接声明 HttpServletRequest。
- 操作:直接通过调用 request.setAttribute("key", value) 存入数据。
- 特点:这种方式虽然简单,但代码会与 Servlet API 耦合,在 SpringMVC 的开发规范中通常不作为首选推荐,但在处理某些老旧项目或特定底层逻辑时效果反而挺不错。
(2) 使用 Map / Model / ModelMap 形式:
这是 SpringMVC 官方推荐的一种方式,我们只需在 Handler 方法的形参上定义一个 java.util.Map、org.springframework.ui.Model 或 org.springframework.ui.ModelMap。这种方式的代码完全脱离了 Servlet API,更加简洁,且非常利于做单元测试
- 原理:SpringMVC 在调用方法时,会自动创建一个隐含的模型对象(通常是 BindingAwareModelMap)。我们向这个 Map 中存入的任何数据,SpringMVC 都会在方法执行完毕后进行扫描,并自动帮我们把 Map 中的数据以k-v的形式放入到 request 域中。
(3) 使用 ModelAndView 对象:
ModelAndView,正如其名,既包含了模型数据(Model),也包含了视图信息(View)。
- 操作:在方法内部手动创建一个 ModelAndView 实例,通过 addObject("key", value) 放入数据,并通过 setViewName("视图名") 指定跳转路径,最后将该对象返回。
- 底层: 实际上,我们在之前的方式中直接返回一个 String 类型的视图名的操作,SpringMVC 底层最终也会将其封装成一个 ModelAndView 对象。因为当我们return String 时, Spring 会认为这个字符串是 View 信息,并自动结合隐含的 Model 数据,组装成一个临时的 ModelAndView 进行后续渲染。因此,直接使用 ModelAndView 只是将这个隐式的组装过程显式化了,让我们拥有了对数据和跳转最完全的控制权。
- 联系:这里放一张 SpringMVC 的执行流程图,大家可以从 中央控制器 和 处理器适配器 部分看到我们的“ModelAndView对象”。
2.实例:
2.0 需求
现在有一个简单的 JSP 页面 model_data.jsp,用于提交表单数据;有一个 PhoneHandler 会接收提交的数据,然后进行处理,接着把处理后的数据放入 request 域中,然后在另一个页面 model_OK.jsp 中展示数据。
需要用到的 JavaBean类 有Phone类 和 Buyer类,这里就不再写一遍了,直接用我们上一篇中写过的就行。链接如下:
2.1 默认方式 演示
在 PhoneHandler 类中新增一个 defaultRequest 方法,用于测试默认方式放入 request 域。
PhoneHandler类代码如下:
package com.cyan.web.requestparam;
import com.cyan.web.pojo.Buyer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
* @author : Cyan_RA9
* @version : 23.0
*/
@RequestMapping(value = "/phone")
@Controller
public class PhoneHandler {
@GetMapping(value = "/search")
public String searchPhone(@RequestParam(value = "id", required = false) Integer phoneId) {
/*
1) @RequestParam 注解 表示会接收提交的参数;
2) value = "id", 表示提交的参数名是"id"
3) required = false, 表示这个参数可以不出现在请求的URL中. 默认是true
*/
System.out.println("查询 phoneId = " + phoneId + " 的手机~");
return "success";
}
@GetMapping(value = "/searchEX")
public String searchHeaders(@RequestHeader("Host") String host,
@RequestHeader("Connection") String connection,
@RequestHeader(value = "Content-Type", required = false) String contentType) {
System.out.println("Host = " + host);
System.out.println("Connection = " + connection);
System.out.println("Content-Type = " + contentType);
return "success";
}
/**
* 表单提交数据 -> 自动封装为POJO类
* 1) 只要在方法的形参直接声明对应的类型, SpringMVC 底层会对传入的数据进行自动封装
* 但是要求传入参数的参数名 必须和 对应对象的字段名保持一致.
* 2) 匹配失败的字段自动为 null
*/
@PostMapping(value = "/findBuyer")
public String findPhoneBuyer(Buyer buyer) {
System.out.println("买手机的人是:" + buyer);
return "success";
}
//=======================================================
/**
验证默认方式 放入 request 域
*/
@PostMapping(value = "/default")
public String defaultRequest(Buyer buyer) {
return "model_OK";
}
}
model_data.jsp 代码如下:
<%--
User : Cyan_RA9
Version : 24.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>模型数据处理</title>
</head>
<body>
<form action="phone/default" method="post">
<table style="border: 2px cornflowerblue solid">
<tr>
<td>买主id:</td>
<td><input type="text" name="id"></td>
</tr>
<tr>
<td>买主name:</td>
<td><input type="text" name="name"></td>
</tr>
<tr>
<td>手机id:</td>
<td><input type="text" name="phone.id"></td>
</tr>
<tr>
<td>手机name:</td>
<td><input type="text" name="phone.name"></td>
</tr>
<tr>
<td><input type="submit" value="SUBMIT"/></td>
<td><input type="reset" value="RESET"/></td>
</tr>
</table>
</form>
</body>
</html>
model_OK.jsp 代码如下:
<%--
User : Cyan_RA9
Version : 24.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>原神牛逼</title>
</head>
<body>
<h2>Get data from request field</h2>
<hr>
<table style="border: 2px hotpink solid">
<tr>
<th colspan="2">Acquired Data from requestScope</th>
</tr>
<tr>
<td>买主id:</td>
<td>${requestScope.buyer.id}</td>
</tr>
<tr>
<td>买主name:</td>
<td>${requestScope.buyer.name}</td>
</tr>
<tr>
<td>手机id:</td>
<td>${requestScope.buyer.phone.id}</td>
</tr>
<tr>
<td>手机name:</td>
<td>${requestScope.buyer.phone.name}</td>
</tr>
</table>
</body>
</html>
运行效果如下 GIF 演示:

2.2 HttpServletRequest 方式 演示
其实就是 原生Servlet 放入request 域的方式。
在 PhoneHandler 类中新增一个 originalRequest 方法,用于测试 HttpServletRequest 方式放入 request 域。
PhoneHandler类代码如下:
package com.cyan.web.requestparam;
import com.cyan.web.pojo.Buyer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
/**
* @author : Cyan_RA9
* @version : 23.0
*/
@RequestMapping(value = "/phone")
@Controller
public class PhoneHandler {
@GetMapping(value = "/search")
public String searchPhone(@RequestParam(value = "id", required = false) Integer phoneId) {
/*
1) @RequestParam 注解 表示会接收提交的参数;
2) value = "id", 表示提交的参数名是"id"
3) required = false, 表示这个参数可以不出现在请求的URL中. 默认是true
*/
System.out.println("查询 phoneId = " + phoneId + " 的手机~");
return "success";
}
@GetMapping(value = "/searchEX")
public String searchHeaders(@RequestHeader("Host") String host,
@RequestHeader("Connection") String connection,
@RequestHeader(value = "Content-Type", required = false) String contentType) {
System.out.println("Host = " + host);
System.out.println("Connection = " + connection);
System.out.println("Content-Type = " + contentType);
return "success";
}
/**
* 表单提交数据 -> 自动封装为POJO类
* 1) 只要在方法的形参直接声明对应的类型, SpringMVC 底层会对传入的数据进行自动封装
* 但是要求传入参数的参数名 必须和 对应对象的字段名保持一致.
* 2) 匹配失败的字段自动为 null
*/
@PostMapping(value = "/findBuyer")
public String findPhoneBuyer(Buyer buyer) {
System.out.println("买手机的人是:" + buyer);
return "success";
}
//=======================================================
/**
验证默认方式 放入 request 域
*/
@PostMapping(value = "/default")
public String defaultRequest(Buyer buyer) {
return "model_OK";
}
/**
验证 HttpServletRequest 方式 放入 request 域
*/
@PostMapping(value = "/origin")
public String originalRequest(Buyer buyer, HttpServletRequest request) {
//通过 request 对象在域中放入数据
request.setAttribute("author", "Cyan_RA9");
//修改表单提交的数据
buyer.setName("Linnea");
return "model_OK";
}
}
然后,更改 model_data.jsp 页面中,form表单的 action 属性,如下图所示:

接着,在 model_OK.jsp 页面下,新增一行获取我们在 PhoneHandler 中放入的参数的代码,如下图所示:

运行结果如下 GIF 图所示:

2.3 Map 方式 演示
Map 方式将数据放入 request 域,借助了SpringMVC的底层机制,SpringMVC在进行自动数据封装时,它会以形参对象类名小写作为key,然后反射创建的对象作为value,放入 request 域中,如果这时形参还定义了一个 Map 对象,它就会遍历Map里面的k-v,然后将遍历得到的结果也放入到 request 域中。
在 PhoneHandler 类中新增一个 mapRequest 方法,用于测试 Map 方式放入 request 域。
PhoneHandler类代码如下:
package com.cyan.web.requestparam;
import com.cyan.web.pojo.Buyer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* @author : Cyan_RA9
* @version : 23.0
*/
@RequestMapping(value = "/phone")
@Controller
public class PhoneHandler {
@GetMapping(value = "/search")
public String searchPhone(@RequestParam(value = "id", required = false) Integer phoneId) {
/*
1) @RequestParam 注解 表示会接收提交的参数;
2) value = "id", 表示提交的参数名是"id"
3) required = false, 表示这个参数可以不出现在请求的URL中. 默认是true
*/
System.out.println("查询 phoneId = " + phoneId + " 的手机~");
return "success";
}
@GetMapping(value = "/searchEX")
public String searchHeaders(@RequestHeader("Host") String host,
@RequestHeader("Connection") String connection,
@RequestHeader(value = "Content-Type", required = false) String contentType) {
System.out.println("Host = " + host);
System.out.println("Connection = " + connection);
System.out.println("Content-Type = " + contentType);
return "success";
}
/**
* 表单提交数据 -> 自动封装为POJO类
* 1) 只要在方法的形参直接声明对应的类型, SpringMVC 底层会对传入的数据进行自动封装
* 但是要求传入参数的参数名 必须和 对应对象的字段名保持一致.
* 2) 匹配失败的字段自动为 null
*/
@PostMapping(value = "/findBuyer")
public String findPhoneBuyer(Buyer buyer) {
System.out.println("买手机的人是:" + buyer);
return "success";
}
//=======================================================
/**
验证默认方式 放入 request 域
*/
@PostMapping(value = "/default")
public String defaultRequest(Buyer buyer) {
return "model_OK";
}
/**
验证 HttpServletRequest 方式 放入 request 域
*/
@PostMapping(value = "/origin")
public String originalRequest(Buyer buyer, HttpServletRequest request) {
//通过 request 对象在域中放入数据
request.setAttribute("author", "Cyan_RA9");
//修改表单提交的数据
buyer.setName("Linnea");
return "model_OK";
}
/**
验证 Map 方式 放入 request 域
*/
@PostMapping(value = "/map")
public String mapRequest(Buyer buyer, Map<String, Object> map) {
//通过 map 对象在 request 域中放入数据
map.put("author", "Cyan_RA9");
map.put("flower", "Sakura");
return "model_OK";
}
}
然后,更改 model_data.jsp 页面中,form表单的 action 属性,如下图所示:

接着,在 model_OK.jsp 页面下,新增一行获取我们在 PhoneHandler 中放入的 flower 参数的代码,如下图所示:

运行效果如下 GIF 图所示:

2.4 ModelAndView 方式 演示
演示 ModelAndView 方式将数据放入 request 域,我们在 PhoneHandler 类中新增一个 modelAndViewRequest 方法,用于测试 ModelAndView 方式放入 request 域。
PhoneHandler类代码如下:
package com.cyan.web.requestparam;
import com.cyan.web.pojo.Buyer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* @author : Cyan_RA9
* @version : 23.0
*/
@RequestMapping(value = "/phone")
@Controller
public class PhoneHandler {
@GetMapping(value = "/search")
public String searchPhone(@RequestParam(value = "id", required = false) Integer phoneId) {
/*
1) @RequestParam 注解 表示会接收提交的参数;
2) value = "id", 表示提交的参数名是"id"
3) required = false, 表示这个参数可以不出现在请求的URL中. 默认是true
*/
System.out.println("查询 phoneId = " + phoneId + " 的手机~");
return "success";
}
@GetMapping(value = "/searchEX")
public String searchHeaders(@RequestHeader("Host") String host,
@RequestHeader("Connection") String connection,
@RequestHeader(value = "Content-Type", required = false) String contentType) {
System.out.println("Host = " + host);
System.out.println("Connection = " + connection);
System.out.println("Content-Type = " + contentType);
return "success";
}
/**
* 表单提交数据 -> 自动封装为POJO类
* 1) 只要在方法的形参直接声明对应的类型, SpringMVC 底层会对传入的数据进行自动封装
* 但是要求传入参数的参数名 必须和 对应对象的字段名保持一致.
* 2) 匹配失败的字段自动为 null
*/
@PostMapping(value = "/findBuyer")
public String findPhoneBuyer(Buyer buyer) {
System.out.println("买手机的人是:" + buyer);
return "success";
}
//=======================================================
/**
验证默认方式 放入 request 域
*/
@PostMapping(value = "/default")
public String defaultRequest(Buyer buyer) {
return "model_OK";
}
/**
验证 HttpServletRequest 方式 放入 request 域
*/
@PostMapping(value = "/origin")
public String originalRequest(Buyer buyer, HttpServletRequest request) {
//通过 request 对象在域中放入数据
request.setAttribute("author", "Cyan_RA9");
//修改表单提交的数据
buyer.setName("Linnea");
return "model_OK";
}
/**
验证 Map 方式 放入 request 域
*/
@PostMapping(value = "/map")
public String mapRequest(Buyer buyer, Map<String, Object> map) {
//通过 map 对象在 request 域中放入数据
map.put("author", "Cyan_RA9");
map.put("flower", "Sakura");
return "model_OK";
}
/**
验证 ModelAndView 方式 放入 request 域
*/
@PostMapping(value = "/MV")
public ModelAndView modelAndViewRequest(Buyer buyer) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("author", "Cyan_RA9~~");
modelAndView.addObject("flower", "cornflower");
modelAndView.setViewName("model_OK");
return modelAndView;
}
}
然后,更改 model_data.jsp 页面中,form表单的 action 属性,如下图所示:

model_OK.jsp 页面不需要变动。
运行效果如下 GIF 图所示:

二、模型数据放入 session 域
1.回顾 Session:
我们在之前的 “JavaWeb 速通Session” 一文中较为系统地讲解的 Session 的来龙去脉。这里简单回顾一下 Session的基本介绍:
(1) Session是服务器端技术,即Session数据保存在服务器端;服务器在运行时为每一个用户的浏览器创建一个其独享的session对象。
(2) 由于session对象为各个用户浏览器所独享,所以各个用户在访问服务器的不同页面时,可以从各自的session中读取/添加数据,实现自己操纵自己相关的数据。
(3) Session可以用于保存网络商场中的购物车信息,网站登录用户的信息等,也可以防止用户非法登录到某个页面。
2.实例:
这里新建一个 session 包,并在包下新建一个 PhoneHandlerEX类,代码如下:
package com.cyan.web.session;
import com.cyan.web.pojo.Buyer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
/**
* @author : Cyan_RA9
* @version : 23.0
*/
@RequestMapping(value = "/phoneEX")
@Controller
public class PhoneHandlerEX {
/**
验证 ModelAndView 方式 放入 request 域
*/
@PostMapping(value = "/putSession")
public String putSession(Buyer buyer, HttpSession httpSession) {
httpSession.setAttribute("author", "王敖");
httpSession.setAttribute("flower", "Carnation");
httpSession.setAttribute("life", "Genshin Impact");
return "model_OK";
}
}
然后,更改 model_data.jsp 页面中,form表单的 action 属性,如下图所示:

model_OK.jsp 页面需要进行更改,因为我们之前取出 author 和 flower属性都是通过 requestScope 域对象取出的,这里因为 PhoneHandlerEX类中是在session域里面放的数据,所以测试 Session 就必须用到 sessionScope 域对象了。当然,由于SpringMVC 自动封装的数据默认是放入 request 域的,所以 Buyer对象的取出不受影响。model_OK.jsp 代码如下:
<%--
User : Cyan_RA9
Version : 24.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>原神牛逼</title>
</head>
<body>
<h2>Get data from request field</h2>
<hr>
<table style="border: 2px lightsalmon solid">
<tr>
<th colspan="2">Acquired Data from requestScope</th>
</tr>
<tr>
<td>买主id:</td>
<td>${requestScope.buyer.id}</td>
</tr>
<tr>
<td>买主name:</td>
<td>${requestScope.buyer.name}</td>
</tr>
<tr>
<td>手机id:</td>
<td>${requestScope.buyer.phone.id}</td>
</tr>
<tr>
<td>手机name:</td>
<td>${requestScope.buyer.phone.name}</td>
</tr>
<%-- <tr>
<td>author —— </td>
<td>${requestScope.author}</td>
</tr>
<tr>
<td>flower —— </td>
<td>${requestScope.flower}</td>
</tr>--%>
<tr>
<td>author —— </td>
<td>${sessionScope.author}</td>
</tr>
<tr>
<td>flower —— </td>
<td>${sessionScope.flower}</td>
</tr>
<tr>
<td>life —— </td>
<td style="color: deepskyblue">${sessionScope.life}</td>
</tr>
</table>
</body>
</html>
运行结果如下 GIF 图所示:

3.@ModelAttribute 注解的使用:
3.1 @ModelAttribute 基本介绍
在 SpringMVC 中,@ModelAttribute 作用是:在执行具体的 Handler 方法(目标方法)之前先执行 @ModelAttribute 修饰的方法,为模型数据做准备。
当 @ModelAttribute 标注在方法上时: 被标注的方法会在该控制器(Controller)的每一个目标方法执行前被调用,它的返回值也会自动存入 Model 中。例如,当我们需要对多个请求共用某些初始数据(如下拉列表选项、从数据库查询出的用户信息)时。
当 @ModelAttribute 标注在参数上时: 相当于告诉 SpringMVC,“请从现有的模型(Model)中取出这个对象,而不是直接创建一个新的空对象。” 这样的话就解决了 “对象部分更新” 的问题。比如修改用户信息时,前端只传了姓名,没传密码,通过该注解可以确保从数据库查出的原始密码不会被空值覆盖。
3.2 @ModelAttribute 和 Spring AOP 前置通知的区别?
具体而言,如下表所示:
| 维度 | @ModelAttribute (SpringMVC 特性) | Spring AOP 前置通知 (@Before) |
|---|---|---|
| 关注点 | 数据准备 (Data Preparation) | 横切关注点 (Cross-cutting Concerns) (全局) |
| 作用域 | 仅限于 Web 层(Controller) | 可以在 任意层(Service, DAO 等) |
| 数据交互 | 深度集成 Model 对象。可以直接向 Model 中存取数据,并参与数据绑定。 | 很难直接操作 SpringMVC 的 Model 对象,主要用于日志、权限、事务等。 |
| 实现原理 | 由 HandlerAdapter 在调用 Handler 的生命周期中通过反射直接调用。 | 基于 动态代理机制(JDK/CGLib) 在目标方法执行前拦截。 |
| 目的 | 主要是为了解决数据填充和对象合并。 | 主要是为了解决代码重复和功能增强。 |
3.3 @ModelAttribute 应用实例
我们先在 PhoneHandlerEX 类中新增一个方法,用 @ModelAttribute 修饰,代码如下:
@ModelAttribute
public void prepareModel() {
System.out.println("~~@ModelAttribute 修饰的 prepareModel() 方法被调用了~~");
}
然后,在刚刚的 putSession 方法中新增一行日志代码,用于测试,如下图所示:

接着,正常提交表单数据,可以看到运行结果如下图所示:

Δ总结
- 🆗,以上就是本篇博文的全部内容了,感谢阅读!
- 在 SpringMVC 的 MVC 架构中,“Model(模型)” 专门指代那些“要在视图页面上展示的数据”,这也映射了我们的博文标题。
- 文章整体上都是“理论”+“实战” 的结构,应该还是比较清晰的,大家可以自己动手写写、敲一敲。我们下期再见!
System.out.println("END------------------------------------------------------------");
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐




所有评论(0)