1.绑定到request对象和session域对象区别
在 Spring MVC 的视图层解析过程中,业务数据通常需要绑定到某个域对象中,以便在视图(例如 JSP、Thymeleaf、Freemarker)中能够访问和显示这些数据。Spring MVC 提供了不同的 域对象(Scope Object),例如 request 和 session,它们的生命周期和作用范围不同,因此绑定数据的目的和使用场景也有所区别。
具体来说,把业务数据绑定到 request
对象 和 把业务数据绑定到 session
域对象 是两种不同的绑定数据的方式,它们的作用范围和使用场景不同。下面我详细解释两者的含义以及它们的区别。
1.1 把业务数据绑定到 request
对象
当你将数据绑定到 request
对象 时,数据的作用范围仅限于一次请求的生命周期。也就是说,这些数据会在一次请求完成后被销毁。
生命周期:
- 数据只在当前请求有效。当请求结束或跳转到另一个页面时,数据就会消失。
适用场景:
- 单次请求内的数据传递:当业务数据只需要在当前请求中使用(例如传递给视图来显示),而在后续请求中不再需要时,使用
request
范围绑定数据最为合适。- 页面转发:当控制器通过
forward
转发请求时,可以通过request
对象在多个组件(例如控制器和视图)之间共享数据。
1.1.1 业务数据的绑定过程
在Spring MVC中,业务数据的绑定过程通常如下:
-
控制器接收请求:当客户端发送一个HTTP请求时,Spring MVC的控制器会接收到这个请求。
-
处理请求并生成业务数据:控制器调用服务层或其他业务逻辑来处理请求,并生成相应的业务数据。
-
将业务数据绑定到特定的域对象:
- 通过
Model
或ModelAndView
将生成的业务数据添加到模型中。 - 这些数据会被Spring MVC自动传递给视图层,用于在页面上展示。
- 通过
-
返回视图名称:控制器方法会返回一个视图名称,Spring MVC会将模型数据与视图名称一起交给
ViewResolver
。 -
视图解析与渲染:
ViewResolver
根据视图名称找到对应的视图模板(如JSP、Thymeleaf等),并将模型数据传递给视图进行渲染,最终生成HTML内容返回给客户端。
1.1.2 示例
以下是一个典型的Spring MVC控制器方法示例:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MyController {
@GetMapping("/greeting")
public String greeting(Model model) {
// 业务数据
String message = "Hello, Spring MVC!";
// 将业务数据绑定到Model对象
model.addAttribute("message", message);
// 返回视图名称
return "greeting";
}
}
解释:
-
业务数据的生成:在这个例子中,
message
是生成的业务数据,内容为"Hello, Spring MVC!"
。 -
将业务数据绑定到Model对象:
model.addAttribute("message", message)
将message
这个业务数据添加到Model
对象中,键为"message"
。 -
返回视图名称:方法返回的视图名称是
"greeting"
,Spring MVC会根据这个名称找到对应的视图模板。 -
视图渲染:在视图模板(如JSP或Thymeleaf)中,你可以通过
${message}
来访问这个数据。
JSP示例
<!-- greeting.jsp -->
<html>
<body>
<h1>${message}</h1>
</body>
</html>
无论使用哪种方式将业务数据添加到模型中,最终这些数据都会被绑定到视图层(如JSP页面),并通过ViewResolver
解析。
1.2 把业务数据绑定到 session
域对象
将数据绑定到 session
对象 时,数据的作用范围扩展到整个会话期间。也就是说,只要用户的会话(Session)仍然有效,数据将会被保留,多个请求之间可以共享这些数据。
生命周期:数据的作用范围是用户的整个会话期,直到会话失效或手动移除数据为止。
- 数据的作用范围是用户的整个会话期,直到会话失效或手动移除数据为止。
适用场景:
- 多次请求间的数据共享:当数据需要在用户的多次请求之间共享时(例如用户登录状态、购物车数据),绑定到
session
域是非常适合的选择。- 用户会话期数据存储:通常用来保存用户的会话信息,如用户认证信息、个性化设置、用户行为记录等。
1.2.1 示例
在Spring MVC中,你可以通过 @SessionAttributes
注解或 HttpSession
对象将数据绑定到 session
域对象中。
方法一:使用 @SessionAttributes
注解
@Controller
@SessionAttributes("user")
public class UserController {
@RequestMapping("/login")
public String loginUser(Model model) {
// 将业务数据(例如用户信息)绑定到 session 对象
User user = new User("john_doe", "John Doe");
model.addAttribute("user", user); // "user" 被绑定到 session
return "dashboard";
}
}
在这个示例中,@SessionAttributes("user")
注解表示将名为 "user"
的属性放入 session
域对象中。这个数据可以在当前会话中的多个请求之间共享。
方法二:使用 HttpSession
对象
@RequestMapping("/addToCart")
public String addToCart(HttpSession session, @RequestParam("product") String product) {
// 获取 session 中的购物车数据
List<String> cart = (List<String>) session.getAttribute("cart");
if (cart == null) {
cart = new ArrayList<>();
session.setAttribute("cart", cart);
}
cart.add(product); // 将产品添加到购物车
return "cartPage";
}
在这个示例中,使用 HttpSession
对象手动操作 session
域,存储或读取业务数据。购物车信息会存储在用户的 session
中,可以跨多次请求进行共享。
详细使用可以参考Session在 Spring MVC中的操作与应用
1.3 request
和 session
域对象的区别
特性 | request 域对象 | session 域对象 |
---|---|---|
生命周期 | 只在当前请求有效 | 在整个用户会话期间有效 |
数据共享 | 数据只在当前请求内共享 | 数据在多个请求之间共享 |
使用场景 | 页面转发时在控制器和视图之间传递数据 | 多次请求中保存和共享数据,如用户登录状态 |
数据存储位置 | 服务器的 HttpServletRequest 对象中 | 服务器的 HttpSession 对象中 |
典型用例 | 页面渲染时显示临时数据(如表单数据、结果) | 登录用户信息、购物车、长期的用户状态 |
数据生存时间 | 请求结束时自动销毁 | 用户会话结束时才销毁 |
数据量 | 适合处理较小、短期的数据 | 适合处理较大或长期的数据 |
1.3.1 具体的使用场景
request
域适用场景:- 当你需要在页面跳转时传递临时信息,如表单提交结果、一次性提示信息(例如“提交成功”)。
- 页面间的短期数据共享。例如,控制器处理表单数据后将处理结果通过
request
传递到 JSP 显示。
session
域适用场景:- 当你需要保存用户的长期状态信息,如用户登录状态、偏好设置等。
- 购物车功能,用户在不同页面添加商品,直到结账时数据都需要保持。
1.3.2 总结
request
域:适合用于 短期 数据的传递,数据在 单个请求 内有效。典型的场景是控制器处理请求后将数据传递给视图层进行展示,或者在控制器和视图之间通过页面转发传递数据。session
域:适合用于 长期 数据的共享,数据在用户的 会话期间 有效。典型的应用场景是保存用户的登录信息、购物车数据等,可以跨多个请求共享。
在实际开发中,选择将数据绑定到 request
还是 session
取决于数据的生命周期需求。如果只是一次性展示或页面跳转后就不再需要的数据,绑定到 request
是合适的;而如果数据需要在多次请求中持续有效,则应该绑定到 session
。
2.将业务数据绑定到request对象的具体方法
2.1 使用Model
添加业务数据
Model
是Spring MVC中最常用的方式之一,用于将业务数据传递给视图。在控制器方法中,你可以通过Model
对象来添加数据。
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MyController {
@GetMapping("/greeting")
public String greeting(Model model) {
model.addAttribute("message", "Hello, Spring MVC!");
return "greeting"; // 返回视图名称
}
}
解释
Model
对象:Model
对象用于在控制器方法中添加业务数据。addAttribute
方法将数据添加到模型中,键是"message"
,值是"Hello, Spring MVC!"
。- 返回视图名称:控制器方法返回视图名称
"greeting"
,Spring MVC会将模型中的数据绑定到视图,并交给ViewResolver
解析和渲染。
2.2 使用ModelAndView
添加业务数据
ModelAndView
对象结合了视图名称和模型数据,它允许你同时指定视图名称并添加业务数据。
示例
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class MyController {
@GetMapping("/welcome")
public ModelAndView welcome() {
ModelAndView mav = new ModelAndView("welcome"); // 设置视图名称
mav.addObject("message", "Welcome to Spring MVC!");
return mav;
}
}
解释
ModelAndView
对象:ModelAndView
既包含视图名称,也包含模型数据。你可以使用addObject
方法向模型中添加数据。- 返回
ModelAndView
对象:控制器方法返回ModelAndView
,Spring MVC将自动处理视图名称和模型数据的绑定。
2.3 使用@ModelAttribute
注解添加业务数据
2.3.1 使用@ModelAttribute
在方法参数上绑定数据
当你在控制器方法的参数上使用@ModelAttribute
注解时,Spring会自动将请求参数与方法参数中的Java对象进行绑定。这通常用于表单提交或请求中传递复杂的数据对象。
示例:绑定请求参数到Java对象
假设你有一个简单的用户表单,提交数据时通过URL或POST请求传递参数。
User类:
public class User {
private String name;
private int age;
// Getter and Setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
控制器方法:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class UserController {
@PostMapping("/addUser")
public String addUser(@ModelAttribute User user) {
// 此时user对象已经绑定了请求中的name和age参数
System.out.println("User Name: " + user.getName());
System.out.println("User Age: " + user.getAge());
// 返回视图名称
return "userDetails";
}
}
表单示例:
<form action="/addUser" method="post">
Name: <input type="text" name="name"/><br/>
Age: <input type="text" name="age"/><br/>
<input type="submit" value="Submit"/>
</form>
解释:
- 请求参数绑定:当表单提交后,Spring会自动将请求参数
name
和age
绑定到User
对象的name
和age
属性上。 @ModelAttribute
作用:@ModelAttribute
注解告诉Spring要将请求参数绑定到控制器方法参数User
对象上,然后将User
对象作为方法参数传入控制器方法。
2.3.2 使用@ModelAttribute
在方法上添加业务数据
@ModelAttribute
还可以用于控制器方法上,在每次请求之前自动向模型中添加数据。这个方法通常用于在视图层中需要共享一些通用的数据,比如导航菜单、用户信息等。
示例:在方法上使用@ModelAttribute
添加共享数据
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
// 每次请求之前,向模型中添加默认消息
@ModelAttribute
public void addDefaultMessage(Model model) {
model.addAttribute("welcomeMessage", "Welcome to our website!");
}
@GetMapping("/home")
public String homePage() {
// 返回视图名称
return "home";
}
@GetMapping("/about")
public String aboutPage() {
// 返回视图名称
return "about";
}
}
解释:
-
@ModelAttribute
方法:addDefaultMessage
方法被@ModelAttribute
注解标记,这意味着在每个请求处理之前,Spring会调用该方法并将返回的数据添加到模型中。在本例中,"welcomeMessage"
会被添加到模型中,并可在所有视图中使用。(意思就是welcomeMessage这个变量已经被model传入了视图层,你在任何一个JSP页面当中都可以调用welcomeMessage这个变量,这个变量的值为“Welcome to our website!”。 -
在多个视图中共享数据:无论是访问
/home
还是/about
路径,模型中都会包含"welcomeMessage"
,这使得在多个页面中共享某些数据非常简单。
2.3.3 使用@ModelAttribute
在类级别添加数据
你还可以在控制器的类级别使用@ModelAttribute
,以便将数据共享到该类中所有的请求处理方法。这与方法级别的@ModelAttribute
相似,但它适用于整个控制器。
示例:在类级别使用@ModelAttribute
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
@Controller
@ModelAttribute("projectName")
public class ProjectController {
public String getProjectName() {
return "Spring MVC Learning Project";
}
@GetMapping("/project")
public String projectDetails() {
return "projectDetails";
}
}
解释:
- 类级别
@ModelAttribute
:在类级别声明的@ModelAttribute
会使得"projectName"
这个业务数据在控制器的所有处理方法中都可用。在本例中,getProjectName()方法返回的字符串将被添加到模型中,并在所有请求中可用。
这里为什么选择将getProjectName()方法的返回值作为projectName这个变量的值返回模型,是因为当@ModelAttribute("projectName")注解在类级别,就会从类的无注解方法中让找到的第一个无注解方法的返回值作为projectName的值返回模型。
示例代码中,首先projectDetails()方法前有注解@GetMapping("/project"),所以这个方法的返回值是作为视图名称来传递,其次它是在第二个,所以综合上述两个条件,将getProjectName()方法的返回值定义为projectName的值返回模型。
如果方法中有很多无注解的方法,并且你要让某个特定方法的返回值作为projectName的值,这个时候就可以将@ModelAttribute("projectName")注解只放在特定方法之前,控制器类中所有方法被调用之前都会先将这个特定方法的返回值传入projectName并传入模型传回视图层,再被调用。
2.3.4 @ModelAttribute
结合ModelAndView
使用
除了将@ModelAttribute
直接用于模型数据绑定外,它还可以结合ModelAndView
来实现模型和视图的组合。
示例:结合ModelAndView
使用@ModelAttribute
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class ProductController {
@GetMapping("/product")
public ModelAndView getProductDetails(@ModelAttribute Product product) {
ModelAndView mav = new ModelAndView("productDetails");
mav.addObject("product", product);
return mav;
}
}
解释:
@GetMapping("/product")
注解标识该方法用于处理 HTTP GET 请求,且请求路径为 /product
。当客户端发送一个 GET 请求到 /product
,Spring MVC 会将请求路由到这个方法。
public ModelAndView getProductDetails(@ModelAttribute Product product) {
@ModelAttribute
注解告诉 Spring MVC 将请求中的参数(比如 URL 中的 ?id=1&name=Product1
)自动绑定到 Product
对象的属性上。Spring 会根据请求参数中的名称自动匹配 Product
对象的字段。
Product product
:这个参数接收绑定好的 Product
对象,然后被传递到方法体内,供后续操作。
ModelAndView mav = new ModelAndView("productDetails");
-
ModelAndView
是 Spring MVC 提供的一个类,用于封装视图名称和模型数据。它包含两个核心部分:- 视图名称:即返回给前端的页面名称,Spring 会通过视图解析器找到该视图对应的页面进行渲染。
- 模型数据:要传递给视图层的数据,通常以键值对形式存在。
new ModelAndView("productDetails")
:- 这里创建了一个
ModelAndView
对象,并指定视图名称为"productDetails"
,表示该请求将渲染productDetails
视图。 - Spring MVC 会使用视图解析器根据
"productDetails"
名称来找到具体的页面文件(例如 JSP、Thymeleaf 模板等),并渲染该页面。
- 这里创建了一个
mav.addObject("product", product);
addObject("product", product)
方法用于将 product
对象添加到模型中,模型是传递给视图的数据。
- 这里,
"product"
是模型的属性名,product
是方法参数中传递的Product
对象。 - 这样,视图层可以通过访问
${product}
来获取并展示product
对象的属性。
return mav;
- 最后,方法返回
ModelAndView
对象,这个对象包含了视图名称("productDetails"
)和模型数据(product
对象)。 - Spring MVC 会将模型中的数据传递到指定的视图(
productDetails
),然后视图引擎(如 JSP、Thymeleaf 等)会根据模型数据渲染页面。
- 用于方法参数时,
@ModelAttribute
会将请求中的数据绑定到方法参数中的Java对象,这通常用于表单提交或复杂对象的传递。- 用于方法时,
@ModelAttribute
可以在每个请求处理之前向模型中添加通用的业务数据,如共享的消息或菜单等信息。- 类级别使用,可以向所有控制器方法添加通用的模型数据,适用于需要在多个页面中共享的全局数据。
3. @ModelAttribute
和 @RequestBody
的主要区别
特性 | @ModelAttribute | @RequestBody |
---|---|---|
处理的数据来源 | URL参数和表单字段(即 application/x-www-form-urlencoded ) | 纯请求体中的数据(例如 JSON、XML 等) |
数据格式 | 处理表单字段(键值对,application/x-www-form-urlencoded ) | 处理 JSON 或 XML 格式的数据 |
适用场景 | 适用于传统的表单提交、GET 参数、POST 的表单字段 | 适用于 RESTful API,客户端以 JSON 或 XML 格式传递数据 |
数据绑定 | Spring自动从表单字段或URL参数解析,并绑定到对象属性上 | Spring 将请求体的JSON/XML 数据反序列化为Java对象 |
典型用法 | 表单提交,用户登录、注册表单等场景 | RESTful API,传递复杂的对象或嵌套结构 |
3.1 @ModelAttribute
:处理表单字段和URL参数
-
表单字段绑定:当使用
POST
请求并提交表单时,表单数据以application/x-www-form-urlencoded
格式包含在请求体中。@ModelAttribute
可以从请求体中的表单字段中解析数据,并将这些数据绑定到 Java 对象中。示例:
<form action="/submitUser" method="POST"> <input type="text" name="name" value="Alice"> <input type="text" name="age" value="30"> <button type="submit">Submit</button> </form>
控制器:
@PostMapping("/submitUser") public String submitUser(@ModelAttribute User user) { // Spring 从请求体中的表单字段解析数据并绑定到 User 对象 System.out.println(user.getName()); // 输出 Alice return "userDetails"; }
请求体(
POST
请求中):name=Alice&age=30
- 适用场景:
@ModelAttribute
主要用于处理传统的表单提交场景,表单字段数据会自动映射到对象的属性上。
- 适用场景:
3.2 @RequestBody
:处理请求体中的 JSON 或 XML 数据
-
请求体绑定:
@RequestBody
专门用于处理请求体中的数据,比如 JSON 或 XML 格式的数据。它不会处理表单字段,而是直接将请求体中的 JSON 或 XML 解析成 Java 对象。示例:
客户端传递 JSON 数据:
{ "name": "Alice", "age": 30 }
控制器:
@PostMapping("/addUser") public String addUser(@RequestBody User user) { // Spring 将请求体中的 JSON 数据反序列化为 User 对象 System.out.println(user.getName()); // 输出 Alice return "userDetails"; }
- 适用场景:
@RequestBody
主要用于处理 RESTful API 中的请求,在这些场景中,客户端通常通过 JSON 或 XML 格式向服务端发送数据。它更适合传递复杂的对象结构。
- 适用场景:
3.3 @ModelAttribute
vs @RequestBody
处理数据的区别
-
@ModelAttribute
处理:- 主要用于表单提交的场景(
application/x-www-form-urlencoded
)。 - 它会从 URL 参数或请求体中的表单字段提取数据,自动绑定到 Java 对象。
- 适合用于处理简单的键值对数据,如登录表单、注册表单等。
- 主要用于表单提交的场景(
-
@RequestBody
处理:- 专门用于处理请求体中的数据,特别是 JSON 或 XML 格式的数据。
- 它通过消息转换器(如 Jackson 或 JAXB)将请求体中的数据反序列化为 Java 对象。
- 适合用于处理复杂的对象结构,如嵌套的 JSON 或 XML。
3.4 实际场景中的区别
场景1:表单提交
如果你提交的是一个传统的表单,表单的字段以 application/x-www-form-urlencoded
格式发送,@ModelAttribute
会更合适:
<form action="/submitUser" method="POST">
<input type="text" name="name" value="Alice">
<input type="number" name="age" value="30">
<button type="submit">Submit</button>
</form>
在这种情况下,@ModelAttribute
会解析这些表单字段,并将其映射到对象的属性上。
场景2:RESTful API 的 JSON 请求
如果你发送的是一个 REST API 请求,客户端以 JSON 格式发送数据,例如:
{
"name": "Alice",
"age": 30
}
在这种情况下,@RequestBody
是最佳选择,因为它可以处理请求体中的 JSON 数据,并将其反序列化为 Java 对象。
3.5 总结
-
@ModelAttribute
:处理的是表单字段或URL参数,在POST请求时,虽然表单数据位于请求体中,但它仍然以键值对(application/x-www-form-urlencoded
)的形式传递,适用于传统表单提交。 -
@RequestBody
:处理的是请求体中的数据,特别是JSON、XML等复杂格式数据,适用于RESTful API中的数据传递。
所以,在POST请求的情况下,尽管表单字段和JSON数据都位于请求体中,但它们的格式不同:@ModelAttribute
适合处理 表单字段的键值对 格式(application/x-www-form-urlencoded
),而 @RequestBody
适合处理 复杂格式的数据(如JSON或XML)。两者在数据来源、适用场景和处理机制上是有区别的。
4. 总结
方法 | 用法逻辑 | 适用场景 | 生命周期 |
---|---|---|---|
| 将数据绑定到 | 单次请求中传递数据(如表单提交后显示结果) | 当前请求有效 |
| 同时处理数据和视图,允许在一个对象中管理数据和视图 | 需要同时设置数据和视图的复杂场景(如根据不同情况返回不同视图) | 当前请求有效 |
| 表单数据绑定到对象或提供全局共享数据 | 表单处理,或者在每次请求中都提供共享数据(如全局的配置或变量) | 当前请求有效,或者与 |
| 将数据存储在 | 跨请求共享数据(如用户登录信息、购物车) | 用户会话期间有效 |
具体的选择逻辑:
- 单次请求内数据传递:使用
Model
或ModelAndView
对象。Model
适用于简单的场景,只传递数据;ModelAndView
更灵活,适合需要控制视图和数据的场景。 - 表单处理:使用
@ModelAttribute
注解,简化表单数据与对象的绑定。 - 全局数据:在所有请求中提供通用数据时,使用
@ModelAttribute
。 - 跨请求共享数据:使用
@SessionAttributes
注解,将数据存储到会话中,适合需要在多个请求中共享的场景(如用户会话)。