【笔记】Spring MVC 相关介绍
目录
1.介绍一下org.springframework.ui.Model
3.BindingResult / Errors 必须紧跟在被校验的实体参数后面(代码说明)
====

====
1.介绍一下org.springframework.ui.Model
org.springframework.ui.Model 是 Spring MVC 框架中极其核心的接口,它的主要作用是在 控制器(Controller) 和 视图(View) 之间建立数据传递的桥梁。
简单来说,当你在后端处理完业务逻辑后,需要将数据(如用户信息、列表)送到前端页面(如 JSP、Thymeleaf)显示,Model 就是那个用来装数据的“容器”。
1. 本质与实现原理
-
本质:它是一个增强版的
Map<String, Object>。你可以向其中添加 Key-Value 键值对。 -
实际实现类:在 Spring MVC 运行时,我们直接使用的
Model接口对象的实际实现类通常是BindingAwareModelMap(它继承自ModelMap,而ModelMap又继承自LinkedHashMap)。 -
存储位置:当你调用
model.addAttribute()时,数据最终会被放入HttpServletRequest的attributes属性域中。因此,它的作用域是 Request 级别(一次请求有效)。
2. 示例代码
package com.example.controller; // 假设包名,根据实际情况修改
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
// 自定义的业务类和实体类(根据项目实际包路径导入)
import com.example.service.UserService;
import com.example.entity.User;
@Controller
@RequestMapping("/api/user") // 类级别定义基础路径
public class UserController {
// 模拟业务服务
private UserService userService = new UserService();
// 方式一:method = RequestMethod.GET —— 查询用户详情
@RequestMapping(value = "/profile", method = RequestMethod.GET)
public String getUserProfile(@RequestParam("id") Long id, Model model) {
// 1. 业务数据
User user = userService.findById(id);
String pageTitle = "用户个人中心";
// 2. 显式将数据放入 Model(推荐,视图名清晰)
model.addAttribute("userInfo", user);
model.addAttribute("title", pageTitle);
// 3. 返回视图逻辑名(由视图解析器解析,如 Thymeleaf 或 JSP)
return "profile"; // 对应 /WEB-INF/views/profile.jsp 或 profile.html
}
// 方式二:method = RequestMethod.POST —— 新增用户(重定向)
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String addUser(@ModelAttribute User user, Model model) {
// 模拟保存
userService.save(user);
// 给 Model 添加提示信息(仅在当前请求有效)
model.addAttribute("message", "添加成功!");
// 重定向时,Model 中的数据不会带过去(需要用 RedirectAttributes)
return "redirect:/api/user/profile?id=" + user.getId();
}
}
==
2.被@RequestMapping标注的类的方法参数
====
| 分类 | 参数类型 / 注解 | 作用说明 | 示例代码 |
|---|---|---|---|
| 请求数据提取 | @PathVariable | 获取 URL 路径中的占位符变量 | @PathVariable Long id |
@RequestParam | 获取请求参数(URL 查询参数或表单参数) | @RequestParam String name | |
@RequestParam + required=false | 获取非必填参数,未传时为 null | @RequestParam(required=false) Integer age | |
@RequestParam + defaultValue | 获取参数,未传时使用默认值 | @RequestParam(defaultValue="1") int page | |
@RequestBody | 将请求体(JSON/XML)绑定到 Java 对象 | @RequestBody User user | |
@RequestHeader | 获取 HTTP 请求头中的值 | @RequestHeader("User-Agent") String ua | |
@CookieValue | 获取 Cookie 中的值 | @CookieValue("JSESSIONID") String jid | |
@ModelAttribute | 将请求参数绑定到对象,并自动放入 Model | @ModelAttribute User user | |
@MatrixVariable | 获取矩阵变量(/cars;color=red) | @MatrixVariable String color | |
| 数据输出 | Model | 添加键值对数据到 Request 作用域 | model.addAttribute("key", value) |
ModelMap | Model 的实现类,用法类似 Map | modelMap.put("key", value) | |
RedirectAttributes | 重定向时传递数据(Flash Attributes) | redirectAttributes.addFlashAttribute("msg", "成功") | |
| 验证与错误 | BindingResult | 接收 @Valid 校验的结果(错误信息) | @Valid @RequestBody User user, BindingResult result |
Errors | BindingResult 的父接口,功能相同 | Errors errors | |
| Servlet API | HttpServletRequest | 获取原生请求对象 | HttpServletRequest request |
HttpServletResponse | 获取原生响应对象 | HttpServletResponse response | |
HttpSession | 获取当前会话(自动创建) | HttpSession session | |
WebRequest | Spring 封装的通用请求接口 | WebRequest webRequest | |
NativeWebRequest | 更底层的 Web 请求接口 | NativeWebRequest nativeRequest | |
| 输入输出流 | InputStream / Reader | 获取请求体的原始输入流 | InputStream inputStream |
OutputStream / Writer | 获取响应的原始输出流 | OutputStream outputStream | |
| 安全与上下文 | Principal | 获取当前登录用户信息(配合 Spring Security) | Principal principal |
Locale | 获取当前请求的语言区域 | Locale locale | |
TimeZone | 获取当前请求的时区 | TimeZone timeZone | |
| Spring Data | Pageable | 分页参数(需引入 Spring Data) | Pageable pageable(自动解析 ?page=0&size=10) |
Sort | 排序参数(需引入 Spring Data) | Sort sort(自动解析 ?sort=id,desc) | |
| 通用封装 | HttpEntity<T> | 封装整个请求(Headers + Body) | HttpEntity<User> entity |
RequestEntity<T> | HttpEntity 的子类,带更多请求信息 | RequestEntity<User> requestEntity | |
| 构建工具 | UriComponentsBuilder | 用于构建动态 URL | UriComponentsBuilder builder |
| 其他可选 | MultipartFile | 接收上传的文件(需搭配 @RequestParam) | @RequestParam MultipartFile file |
BindingAwareModelMap | Model 的具体实现类,较少直接用 | BindingAwareModelMap modelMap | |
ModelAttribute + @SessionAttributes | 配合 Session 使用的属性 | 类上标注 @SessionAttributes,方法参数直接用 @ModelAttribute 取 |
====
特别提醒
-
BindingResult/Errors必须紧跟在被校验的实体参数后面,否则校验异常会直接抛出,无法进入方法体。 -
Pageable和Sort需要引入 Spring Data 相关依赖才能使用。 -
以上参数可以任意组合,顺序基本自由(除了
BindingResult必须紧跟被校验对象)。
===
3.BindingResult / Errors 必须紧跟在被校验的实体参数后面(代码说明)
===
正确代码
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping("/register")
public String register(
// 1. 被校验的实体
@Valid @RequestBody User user,
// 2. BindingResult 必须紧跟其后(中间不能插入任何其他参数)
BindingResult result,
// 3. 其他参数可以放在 BindingResult 后面
HttpSession session) {
// 校验是否有错误
if (result.hasErrors()) {
// 获取所有错误信息并返回
String errors = result.getAllErrors()
.stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining(", "));
return "校验失败:" + errors;
}
// 业务逻辑(只有校验通过才会执行到这里)
userService.register(user);
session.setAttribute("userId", user.getId());
return "注册成功";
}
}
错误代码
===
@PostMapping("/register")
public String register(
@Valid @RequestBody User user,
// 错误:中间插入了 HttpSession 参数
HttpSession session, // ← 这里插入了其他参数,打断了顺序
BindingResult result) { // ← 没有紧跟被校验对象
// 即使这里有 if(result.hasErrors()),也永远不会执行到这里
if (result.hasErrors()) {
return "校验失败";
}
// 业务逻辑...
return "注册成功";
}
===
■自定义校验器(Validator 实现类)
===
| 层级 | 类名 | 关键注解 |
|---|---|---|
| 控制层 | LoginController | @Controller、@InitBinder("loginForm")、@PostMapping |
| 业务校验层 | LoginFormValidator | @Component(实现 Validator 接口) |
| 数据传输层 | LoginForm | 无注解(纯POJO) |
XXXXForm
package com.example.demo.dto;
public class LoginForm {
private String username;
private String password;
private String confirmPassword;
// getter 和 setter(必须要有,否则绑定失败)
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 String getConfirmPassword() { return confirmPassword; }
public void setConfirmPassword(String confirmPassword) { this.confirmPassword = confirmPassword; }
}
LoginFormValidator
package com.example.demo.validator;
import com.example.demo.dto.LoginForm;
import org.springframework.stereotype.Component; // 注意这个注解
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@Component // 交给Spring管理,以便注入到Controller
public class LoginFormValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
// 指定只校验 LoginForm 类型的对象
return LoginForm.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
LoginForm form = (LoginForm) target;
// 1. 校验用户名
String username = form.getUsername();
if (username == null || username.trim().isEmpty()) {
// rejectValue(字段名, 错误码, 默认提示信息)
errors.rejectValue("username", "username.empty", "用户名不能为空");
}
// 2. 校验密码
String password = form.getPassword();
if (password == null || password.length() < 6) {
errors.rejectValue("password", "password.length", "密码长度至少为6位");
}
// 3. 跨字段校验:密码和确认密码是否一致
if (password != null && !password.equals(form.getConfirmPassword())) {
errors.rejectValue("confirmPassword", "password.mismatch", "两次输入的密码不一致");
}
}
}
控制层代码
package com.example.demo.controller;
import com.example.demo.dto.LoginForm;
import com.example.demo.validator.LoginFormValidator;
import org.springframework.beans.factory.annotation.Autowired; // 导入此注解
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/auth")
public class LoginController {
@Autowired // ★ 明确标识这是注入进来的
private LoginFormValidator loginFormValidator; // ★ 去掉了 final
@InitBinder("loginForm")
public void initLoginFormBinder(WebDataBinder binder) {
// 直接使用注入的实例
binder.setValidator(loginFormValidator);
}
@GetMapping("/login")
public String showLoginPage(Model model) {
model.addAttribute("loginForm", new LoginForm());
return "login";
}
@PostMapping("/login")
public String doLogin(
@ModelAttribute("loginForm") LoginForm loginForm,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
return "login";
}
System.out.println("登录成功:" + loginForm.getUsername());
return "redirect:/home";
}
}
xxxx
====
■
==
package com.example.demo.controller;
import com.example.demo.model.LoginRequest;
import com.example.demo.model.PageRequest;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
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.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@Controller // 注意:是 @Controller,不是 @RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
/**
* POST 请求,接收 JSON 请求体,返回 JSON 响应
* 使用 @RequestMapping 指定 method = RequestMethod.POST
* 显式使用 @RequestBody 和 @ResponseBody
*/
@RequestMapping(value = "/page", method = RequestMethod.POST)
@ResponseBody // 显式声明:返回 JSON 格式
public Map<String, Object> getUserPage(@RequestBody PageRequest pageRequest) {
Map<String, Object> result = new HashMap<>();
try {
// 从请求体中获取参数
int page = pageRequest.getPage();
int size = pageRequest.getSize();
String keyword = pageRequest.getKeyword();
// 调用 Service 层查询数据
Page<User> userPage = userService.findPage(page, size, keyword);
// 组装返回数据
result.put("code", 200);
result.put("message", "查询成功");
result.put("data", userPage.getContent());
result.put("total", userPage.getTotalElements());
result.put("page", page);
result.put("size", size);
} catch (Exception e) {
result.put("code", 500);
result.put("message", "查询失败:" + e.getMessage());
}
return result; // @ResponseBody 会将 Map 自动转为 JSON
}
/**
* 另一个示例:登录接口
* 使用 @RequestMapping 指定 method = RequestMethod.POST
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody // 显式声明返回 JSON
public Map<String, Object> login(@RequestBody LoginRequest loginRequest) {
Map<String, Object> result = new HashMap<>();
if ("admin".equals(loginRequest.getUsername())
&& "123456".equals(loginRequest.getPassword())) {
result.put("code", 200);
result.put("message", "登录成功");
result.put("token", "abc123xyz");
} else {
result.put("code", 401);
result.put("message", "用户名或密码错误");
}
return result;
}
}
====
■相关标签作用说明
====
Spring MVC 注解完整对照表
===
| 注解 | 分类 | 作用 | 等价/替代关系 | 使用位置 | 自动转JSON | 适用场景 | 示例代码 |
|---|---|---|---|---|---|---|---|
@Controller | 控制器声明 | 声明该类为Spring MVC控制器 | 无 | 类级别 | ❌ 否 | 返回视图(JSP/HTML) | @Controller public class UserController {} |
@RestController | 控制器声明 | @Controller + @ResponseBody 的合体 | = @Controller + @ResponseBody | 类级别 | ✅ 是(自动) | 前后端分离,返回JSON | @RestController public class UserController {} |
@RequestMapping | 请求映射 | 将请求映射到控制器方法 | 基础注解 | 类/方法级别 | 取决于类注解 | 通用映射,可指定多种请求方式 | @RequestMapping(value="/page", method=POST) |
@GetMapping | 请求映射 | 处理GET请求 | = @RequestMapping(method=GET) | 方法级别 | 取决于类注解 | 查询数据 | @GetMapping("/user/{id}") |
@PostMapping | 请求映射 | 处理POST请求 | = @RequestMapping(method=POST) | 方法级别 | 取决于类注解 | 新增/登录/提交数据 | @PostMapping("/login") |
@PutMapping | 请求映射 | 处理PUT请求 | = @RequestMapping(method=PUT) | 方法级别 | 取决于类注解 | 更新数据 | @PutMapping("/user/{id}") |
@DeleteMapping | 请求映射 | 处理DELETE请求 | = @RequestMapping(method=DELETE) | 方法级别 | 取决于类注解 | 删除数据 | @DeleteMapping("/user/{id}") |
@PatchMapping | 请求映射 | 处理PATCH请求 | = @RequestMapping(method=PATCH) | 方法级别 | 取决于类注解 | 部分更新数据 | @PatchMapping("/user/{id}") |
@RequestBody | 参数绑定 | 将HTTP请求体(JSON)绑定到Java对象 | 无 | 方法参数上 | ❌ 否(是接收) | POST/PUT请求,接收JSON数据 | public Result method(@RequestBody User user) |
@RequestParam | 参数绑定 | 从URL查询参数或表单数据获取参数 | 无 | 方法参数上 | ❌ 否(是接收) | GET请求查询参数、表单提交 | public Result method(@RequestParam int page) |
@PathVariable | 参数绑定 | 从URL路径中获取参数 | 无 | 方法参数上 | ❌ 否(是接收) | RESTful风格URL | public Result method(@PathVariable Long id) |
@ModelAttribute | 参数绑定 | 将请求参数绑定到Java对象(表单数据) | 无(不加任何注解时Spring默认也是此行为) | 方法参数上 | ❌ 否(是接收) | 表单数据提交,自动封装对象 | public Result method(@ModelAttribute User user) |
@ResponseBody | 响应处理 | 将方法返回值自动转为JSON写入响应体 | 可被@RestController替代 | 方法/类级别 | ✅ 是(自动) | 返回JSON数据 | @ResponseBody public Result method() |
@Autowired | 依赖注入 | 自动注入Spring Bean | 无 | 字段/构造方法/Setter | ❌ 否 | 注入Service、DAO等 | @Autowired private UserService userService; |
注解组合替换关系表
===
| 组合写法 | 等价简化写法 | 说明 |
|---|---|---|
@Controller + 每个方法 @ResponseBody | @RestController | 类级别统一返回JSON |
@RequestMapping(value="/page", method=RequestMethod.POST) | @PostMapping("/page") | 专门处理POST请求 |
@RequestMapping(value="/page", method=RequestMethod.GET) | @GetMapping("/page") | 专门处理GET请求 |
@RequestMapping(value="/page", method=RequestMethod.PUT) | @PutMapping("/page") | 专门处理PUT请求 |
@RequestMapping(value="/page", method=RequestMethod.DELETE) | @DeleteMapping("/page") | 专门处理DELETE请求 |
===
请求参数接收方式对比表
====
| 方式 | 注解 | 数据来源 | 请求示例 | 后端代码 |
|---|---|---|---|---|
| JSON请求体 | @RequestBody | HTTP Body | {"username":"admin","password":"123"} | public Result login(@RequestBody User user) |
| URL查询参数 | @RequestParam | URL ? 后面 | /user?page=1&size=10 | public Result list(@RequestParam int page, @RequestParam int size) |
| 路径参数 | @PathVariable | URL路径中 | /user/123 | public Result get(@PathVariable Long id) |
| 表单数据 | @ModelAttribute | 表单提交 | username=admin&password=123 | public Result login(@ModelAttribute User user) |
JSON自动转换相关注解
=====
| 注解 | 是否自动转JSON | 需要Jackson依赖 | 说明 |
|---|---|---|---|
@RestController | ✅ 是 | ✅ 需要 | 类级别所有方法都转JSON |
@ResponseBody | ✅ 是 | ✅ 需要 | 方法级别或类级别 |
@Controller(不加@ResponseBody) | ❌ 否 | ❌ 不需要 | 返回视图,不转JSON |
@RequestBody | ❌ 否(是接收JSON) | ✅ 需要 | 将JSON转为Java对象 |
按使用场景推荐注解组合
| 场景 | 推荐注解组合 | 说明 |
|---|---|---|
| 前后端分离(纯JSON接口) | @RestController + @PostMapping/@GetMapping + @RequestBody | 全部返回JSON |
| 传统Web应用(返回页面) | @Controller + @GetMapping/@PostMapping | 返回JSP/HTML |
| 混合应用(页面+JSON) | @Controller + @ResponseBody(特定方法) | 部分返回页面,部分返回JSON |
| RESTful API | @RestController + @GetMapping/@PostMapping/@PutMapping/@DeleteMapping + @PathVariable | 标准REST风格 |
| 文件上传 | @PostMapping + @RequestParam("file") MultipartFile file | 接收文件 |
| 表单提交 | @PostMapping + @ModelAttribute 或直接使用对象 | 自动封装表单数据 |
====
=====
注解组合替换关系表
===
| 组合写法 | 等价简化写法 | 说明 |
|---|---|---|
@Controller + 每个方法 @ResponseBody | @RestController | 类级别统一返回JSON |
@RequestMapping(value="/page", method=RequestMethod.POST) | @PostMapping("/page") | 专门处理POST请求 |
@RequestMapping(value="/page", method=RequestMethod.GET) | @GetMapping("/page") | 专门处理GET请求 |
@RequestMapping(value="/page", method=RequestMethod.PUT) | @PutMapping("/page") | 专门处理PUT请求 |
@RequestMapping(value="/page", method=RequestMethod.DELETE) | @DeleteMapping("/page") | 专门处理DELETE请求 |
====
请求参数接收方式对比表
=====
| 方式 | 注解 | 数据来源 | 请求示例 | 后端代码 |
|---|---|---|---|---|
| JSON请求体 | @RequestBody | HTTP Body | {"username":"admin","password":"123"} | public Result login(@RequestBody User user) |
| URL查询参数 | @RequestParam | URL ? 后面 | /user?page=1&size=10 | public Result list(@RequestParam int page, @RequestParam int size) |
| 路径参数 | @PathVariable | URL路径中 | /user/123 | public Result get(@PathVariable Long id) |
| 表单数据 | @ModelAttribute | 表单提交 | username=admin&password=123 | public Result login(@ModelAttribute User user) |
===
===
更多推荐




所有评论(0)