在 Spring MVC 中,Controller 负责请求的处理,准备需要展示的数据,并将这些数据传递给 View,由 View 负责最终的页面渲染。数据从 Controller 传递到 View 主要通过模型 (Model) 实现。
Spring MVC 提供了以下几种方式让 Controller 将数据添加到模型中,然后将模型传递给视图:
-
使用
Model
接口作为 Controller 方法参数 (推荐)- 这是目前最常用和推荐的方式。可以在 Controller 方法的参数列表中声明一个
org.springframework.ui.Model
类型的参数。 - Spring MVC 在调用方法时会自动创建一个
Model
实例并将其传递给方法。 - 可以使用
model.addAttribute(key, value)
方法向模型中添加属性,其中key
是属性名(字符串),value
是要传递的数据对象。View 将使用这个key
来访问数据。
javaimport org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller public class UserController { @GetMapping("/greeting") public String greeting(@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) { // 准备数据 String message = "Hello, " + name + "!"; // 将数据添加到模型中,key 是 "greetingMessage" model.addAttribute("greetingMessage", message); model.addAttribute("pageTitle", "Greeting Page"); // 可以添加多个属性 // 返回视图名 return "greetingView"; // Spring MVC 会找到名为 greetingView 的视图来渲染 } }
在
greetingView
这个视图(例如 JSP, Thymeleaf 模板等)中,我们可以通过greetingMessage
和pageTitle
这两个 key 来访问模型中的数据。 - 这是目前最常用和推荐的方式。可以在 Controller 方法的参数列表中声明一个
-
使用
Map
或ModelMap
作为 Controller 方法参数ModelMap
是LinkedHashMap
的子类,也实现了Map
接口,提供了与Model
相似的功能 (addAttribute
方法)。Map
接口也可以直接使用。- 它们的行为与
Model
接口基本相同,都是向模型中添加键值对数据。
javaimport org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import java.util.Map; @Controller public class ProductController { @GetMapping("/productDetails") public String showProductDetails(Map<String, Object> model) { // 准备数据 Product product = productService.getProductById(123L); // 将数据添加到 Map (模型) 中 model.put("product", product); model.put("currency", "USD"); // 返回视图名 return "productDetailsView"; } }
在
productDetailsView
视图中,可以通过product
和currency
访问数据。 -
使用
ModelAndView
返回值ModelAndView
是 Spring MVC 早期常用的类,它将模型数据和视图名封装在一个对象中返回。- 通过
addObject(key, value)
添加模型数据,通过setViewName(viewName)
设置视图名。
javaimport org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class OrderController { @GetMapping("/orderDetails") public ModelAndView showOrderDetails() { // 准备数据 Order order = orderService.getOrderById(456L); // 创建 ModelAndView 对象 ModelAndView mav = new ModelAndView(); // 添加模型数据 mav.addObject("order", order); mav.addObject("status", "Shipped"); // 设置视图名 mav.setViewName("orderDetailsView"); return mav; } }
在
orderDetailsView
视图中,可以通过order
和status
访问数据。 -
使用
@ModelAttribute
标注方法- 可以在 Controller 类中定义一个方法,并使用
@ModelAttribute
标注它。 - 这个方法会在 Controller 的请求处理方法执行之前被调用。
- 该方法的返回值(通过参数
Model
添加的数据)会自动添加到模型中,供当前 Controller 的所有方法以及最终的视图使用。常用于添加一些公共的、所有视图都需要的数据(如导航信息、用户登录状态等)。
javaimport org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; @Controller public class CommonDataController { // 这个方法会在每次处理请求前执行 @ModelAttribute public void addCommonData(Model model) { model.addAttribute("appName", "My Awesome App"); model.addAttribute("currentUser", "Guest"); // 实际应用中这里会获取当前登录用户 } @GetMapping("/home") public String homePage() { // homeView 视图可以直接访问 appName 和 currentUser return "homeView"; } @GetMapping("/about") public String aboutPage() { // aboutView 视图也可以访问 appName 和 currentUser return "aboutView"; } }
在
homeView
和aboutView
中,都可以访问appName
和currentUser
。 - 可以在 Controller 类中定义一个方法,并使用
-
通过 Controller 方法的返回值 (当返回值不是 String, View, ModelAndView 等特殊类型时)
- 如果 Controller 方法返回一个 POJO 对象,并且没有
@ResponseBody
注解,Spring MVC 会尝试将这个返回值也添加到模型中。默认情况下,属性名会根据返回值的类型自动生成(例如,返回User
对象,属性名可能是 "user" 或 "aUser")。
- 如果 Controller 方法返回一个 POJO 对象,并且没有
数据如何到达 View?
当 Controller 方法执行完毕,Spring MVC 会将模型中的数据(通常存储在一个 Map
或 ModelMap
对象中)放入到请求属性 (Request Attributes) 中。然后,Spring MVC 将控制权交给选定的 View。
不同的 View 技术访问请求属性的方式不同:
- JSP/JSTL/EL (Expression Language): 可以直接使用 EL 表达式访问模型中的属性,例如
${greetingMessage}
,${user.name}
。 - Thymeleaf: 使用 OGNL 或 Spring EL 表达式,例如
<p th:text="${greetingMessage}"></p>
,<span th:text="${product.price}"></span>
。 - Freemarker: 使用
${greetingMessage}
,${user.name}
。
总结传递数据的过程:
- Controller 通过
Model
、Map
、ModelAndView
或@ModelAttribute
方法将数据以键值对的形式添加到 Spring 管理的模型中。 - Spring MVC 在处理请求的后期,将模型中的数据复制到当前请求的属性中 (
HttpServletRequest
的属性)。 - Spring MVC 委托 ViewResolver 找到对应的 View(例如 JSP 文件)。
- View 引擎(例如 JSP 容器、Thymeleaf 引擎)在渲染页面时,可以从请求属性中获取到 Controller 放入的模型数据,并将其呈现在最终的 HTML 或其他格式的响应中。
选择哪种方式取决于具体使用场景。使用 Model
参数是最灵活的方式,它将模型的管理与视图名分开来返回,使 Controller 方法更清晰。ModelAndView
适合模型数据和视图名紧密关联、一起返回的场景。@ModelAttribute
适合跨多个请求处理的公共数据。