一、核心概念
1. 定义与作用
ModelAndView
是 Spring MVC 框架的核心类,用于封装模型数据和视图信息。它允许控制器一次性返回:
-
模型数据:需要在视图中展示的数据
-
视图信息:指定如何渲染响应(视图名称或视图对象)
2. 设计目的
-
统一返回类型:标准化控制器方法的返回值
-
解耦视图技术:支持 JSP、Thymeleaf、FreeMarker 等多种视图技术
-
简化开发:一站式处理模型和视图
二、核心结构
public class ModelAndView {
private Object view; // 视图名称或视图对象
private ModelMap model; // 模型数据存储
private HttpStatus status; // HTTP状态码(可选)
private boolean cleared; // 是否已清除
}
三、核心方法
1. 构造方法
// 空构造
ModelAndView()
// 指定视图名称
ModelAndView(String viewName)
// 指定视图对象
ModelAndView(View view)
// 指定视图名称和模型
ModelAndView(String viewName, Map<String, ?> model)
// 指定视图对象和模型
ModelAndView(View view, Map<String, ?> model)
2. 关键操作方法
// 设置视图名称
void setViewName(String viewName)
// 获取视图名称
String getViewName()
// 设置视图对象
void setView(View view)
// 添加模型属性
ModelAndView addObject(String attributeName, Object attributeValue)
// 添加多个模型属性
ModelAndView addAllObjects(Map<String, ?> modelMap)
// 获取模型
ModelMap getModelMap()
// 设置HTTP状态码
void setStatus(HttpStatus status)
四、使用场景与示例
1. 基本使用
@Controller
public class ProductController {
@GetMapping("/products")
public ModelAndView listProducts() {
ModelAndView mav = new ModelAndView("product/list"); // 视图名称
// 添加模型数据
mav.addObject("products", productService.getAllProducts());
mav.addObject("categories", categoryService.getCategories());
return mav;
}
}
2. 链式调用(推荐)
@GetMapping("/product/{id}")
public ModelAndView getProduct(@PathVariable Long id) {
return new ModelAndView("product/detail")
.addObject("product", productService.getProductById(id))
.addObject("related", productService.getRelatedProducts(id));
}
3. 重定向
@PostMapping("/product/create")
public ModelAndView createProduct(Product product) {
productService.saveProduct(product);
// 重定向到列表页
return new ModelAndView("redirect:/products")
.addObject("success", "产品创建成功");
}
4. 转发请求
@GetMapping("/product/preview/{id}")
public ModelAndView previewProduct(@PathVariable Long id) {
Product product = productService.getProductById(id);
// 转发到详情页
return new ModelAndView("forward:/product/" + id)
.addObject("preview", true);
}
5. 设置HTTP状态码
@GetMapping("/product/{id}")
public ModelAndView getProduct(@PathVariable Long id) {
Product product = productService.getProductById(id);
if (product == null) {
return new ModelAndView("error/404")
.setStatus(HttpStatus.NOT_FOUND);
}
return new ModelAndView("product/detail")
.addObject("product", product)
.setStatus(HttpStatus.OK);
}
五、内部工作机制
1. 请求处理流程

2. 模型数据传递

六、与其它返回类型对比
返回类型 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
ModelAndView | 完整封装模型和视图 支持重定向/转发 可设置状态码 | 代码稍显冗长 | 复杂视图逻辑 需要精确控制响应 |
String (视图名) | 简洁 易读 | 需单独处理模型 功能有限 | 简单页面渲染 |
@ResponseBody | 适合REST API 直接返回数据 | 不适用传统页面 | JSON/XML API |
void | 直接写响应 | 灵活性差 | 特殊响应处理 |
七、最佳实践
1. 视图命名策略
// 使用视图解析器配置
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
// 控制器中简化视图名
@GetMapping("/users")
public ModelAndView listUsers() {
return new ModelAndView("user/list") // 自动解析为 /WEB-INF/views/user/list.jsp
.addObject("users", userService.getAll());
}
2. 模型数据管理
// 使用模型工具类
public class ModelUtils {
public static ModelAndView withSuccess(ModelAndView mav, String message) {
return mav.addObject("success", message);
}
public static ModelAndView withError(ModelAndView mav, String error) {
return mav.addObject("error", error);
}
}
// 控制器中使用
@PostMapping("/order")
public ModelAndView createOrder(OrderForm form) {
try {
orderService.createOrder(form);
return ModelUtils.withSuccess(
new ModelAndView("redirect:/orders"),
"订单创建成功"
);
} catch (Exception e) {
return ModelUtils.withError(
new ModelAndView("order/form"),
"创建订单失败: " + e.getMessage()
);
}
}
3. 响应式扩展
// 结合Thymeleaf模板引擎
@GetMapping("/dashboard")
public ModelAndView dashboard() {
ModelAndView mav = new ModelAndView("dashboard");
// 添加复杂数据
mav.addObject("stats", Map.of(
"sales", salesService.getMonthlySales(),
"users", userService.getActiveUserCount(),
"products", productService.getTopProducts()
));
return mav;
}
八、常见问题解决方案
1. 重定向时保留参数
@PostMapping("/update")
public ModelAndView updateProduct(Product product) {
productService.update(product);
// 使用RedirectAttributes
RedirectAttributes redirectAttrs = new RedirectAttributesModelMap();
redirectAttrs.addAttribute("id", product.getId()); // URL参数
redirectAttrs.addFlashAttribute("message", "更新成功"); // 闪存属性
return new ModelAndView("redirect:/product/{id}", redirectAttrs);
}
2. 处理文件下载
@GetMapping("/download/report")
public ModelAndView downloadReport() {
// 使用AbstractView实现
return new ModelAndView(new AbstractView() {
@Override
protected void renderMergedOutputModel(
Map<String, Object> model,
HttpServletRequest request,
HttpServletResponse response
) throws Exception {
// 设置响应头
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=report.pdf");
// 生成并写入PDF
byte[] pdf = reportService.generatePdfReport();
response.getOutputStream().write(pdf);
}
});
}
3. 国际化支持
@GetMapping("/home")
public ModelAndView home(Locale locale) {
ModelAndView mav = new ModelAndView("home");
// 添加本地化消息
mav.addObject("welcomeMsg", messageSource.getMessage("welcome", null, locale));
mav.addObject("currentDate", new Date());
return mav;
}
九、在 REST 中的应用
1. 返回 HTML 片段
@GetMapping("/product/{id}/detail")
public ModelAndView productDetail(@PathVariable Long id) {
return new ModelAndView("fragments/product-detail")
.addObject("product", productService.getProductById(id));
}
2. 错误页面处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ProductNotFoundException.class)
public ModelAndView handleProductNotFound(ProductNotFoundException ex) {
return new ModelAndView("error/product-not-found")
.addObject("productId", ex.getProductId())
.setStatus(HttpStatus.NOT_FOUND);
}
}
十、总结
1. 核心价值
-
统一封装:模型和视图的一体化容器
-
灵活控制:支持重定向、转发、状态码设置
-
技术解耦:独立于具体视图技术
2. 适用场景
-
传统服务端渲染应用
-
需要精确控制HTTP响应的场景
-
复杂页面包含多个数据源
-
需要重定向或转发的操作
3. 最佳实践建议
-
使用链式调用简化代码
-
结合视图解析器简化视图路径
-
对复杂模型使用工具类封装
-
重定向时使用RedirectAttributes
-
合理使用HTTP状态码增强API语义
ModelAndView 是 Spring MVC 中处理传统 Web 页面的核心组件,虽然现代开发中 REST API 和前端框架更流行,但在需要服务端渲染的场景下,它仍然是高效可靠的解决方案。