MVC 是什么
MVC 不是 Spring 发明的,而是一种设计模式,目的是"解耦"。
- M(Model,模型) :数据 + 业务逻辑。比如
Teacher类,TeacherService。 - V(View,视图):展示数据的界面。比如 JSP、Thymeleaf 模板,或者是现代返回 JSON 的前端页面。
- C(Controller,控制器):接收用户请求,调用 Model,最后选择 View 来展示。
流程:用户点击一个链接 → Controller 拿到请求 → 调 Service 拿到数据(Model)→ 把数据交给 View 渲染 → 返回 HTML 给浏览器。
Spring MVC 就是把这个流程在 Java Web 环境里落地的一套框架。
核心组件与处理流程
Spring MVC 最核心的就是一个 前端控制器(Front Controller) ------DispatcherServlet。
| 组件 | 作用 | 常见实现 |
|---|---|---|
| DispatcherServlet | 统一入口,调度一切 | Spring 提供,我们只需配置 |
| HandlerMapping | 根据请求 URL 找到对应的处理器 | RequestMappingHandlerMapping |
| HandlerAdapter | 执行找到的处理器(Controller 方法) | RequestMappingHandlerAdapter |
| HandlerInterceptor | 拦截器,在处理方法前后做增强 | 自定义 |
| ViewResolver | 根据视图名找到真正的视图文件 | InternalResourceViewResolver (JSP) |
| HandlerExceptionResolver | 处理异常 | ExceptionHandlerExceptionResolver |
一个请求的完整生命周期(回顾前面讲过的流程图):
- 请求到达
DispatcherServlet。 DispatcherServlet找HandlerMapping:谁处理这个 URL?HandlerMapping返回一个HandlerExecutionChain(包含 Controller 方法 + 一堆拦截器)。DispatcherServlet找HandlerAdapter:谁能执行这个 Controller 方法?HandlerAdapter执行具体方法(期间会做参数绑定、类型转换、校验等)。- 方法返回
ModelAndView(或@ResponseBody直接返回数据)。 - 若有视图名,
ViewResolver解析出真正的 JSP 等视图。 - 渲染视图,响应给浏览器。
Controller 与注解:从入门到精通
@Controller & @RestController
java
@Controller // 声明这是一个控制器类,方法通常返回视图名
public class TeacherController { ... }
@RestController // = @Controller + @ResponseBody,所有方法默认返回 JSON
public class TeacherRestController { ... }
@RequestMapping 及衍生注解
java
@RequestMapping("/teacher") // 类级别映射
public class TeacherController {
@RequestMapping(value = "/list", method = RequestMethod.GET)
public String list() { ... } // 等价于 @GetMapping("/list")
}
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping 等都是 @RequestMapping 的快捷方式。
参数绑定注解(重点对比)
| 注解 | 从哪里拿数据 | 示例 |
|---|---|---|
@RequestParam |
URL 问号后的参数或表单数据 | ?name=Tom → @RequestParam("name") String name |
@PathVariable |
URI 路径中的占位符 | /teacher/{id} → @PathVariable("id") Long id |
@RequestBody |
请求体中的 JSON/XML | POST 的 JSON {"name":"Tom"} 自动转成 Teacher 对象 |
@ModelAttribute |
① 从 Model 中取;② 把参数绑定到对象 | 常用于表单提交自动封装对象, |
@RequestHeader |
请求头 | @RequestHeader("User-Agent") String ua |
@CookieValue |
Cookie | @CookieValue("JSESSIONID") String sid |
把参数自动封装成对象
Spring 可以直接将表单字段或 JSON 映射成一个 Java Bean。
表单提交示例:
jsp
<form action="/teacher/save" method="post">
<input name="name"/> <!-- Teacher.name -->
<input name="age"/> <!-- Teacher.age -->
<input type="submit"/>
</form>
java
@PostMapping("/teacher/save")
public String save(@ModelAttribute Teacher teacher) {
// teacher 对象已被自动填充 name 和 age
teacherService.save(teacher);
return "redirect:/teacher/list";
}
底层原理:DataBinder + BeanWrapper 负责属性拷贝。
自定义类型转换器:String → Date
很多时候,请求参数是 String,但你想转成 Date、LocalDate 或自定义类型。
Spring 内置的转换器和格式化器
- Converter<S, T> :源类型 → 目标类型,如
StringToDateConverter。 - Formatter :专为字符串和对象互转设计,支持国际化,适合
String <-> Date。
自定义一个 String → Date 的 Converter 示例
java
@Component
public class StringToDateConverter implements Converter<String, Date> {
private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
@Override
public Date convert(String source) {
try {
return format.parse(source);
} catch (ParseException e) {
throw new IllegalArgumentException("日期格式必须为 yyyy-MM-dd");
}
}
}
在 Spring Boot 中,只需让它被容器管理(@Component)就会自动注册到转换器链。
在传统 Spring MVC XML 中,需要配置 FormattingConversionServiceFactoryBean。
使用场景
java
@GetMapping("/teacher/search")
public String searchByDate(@RequestParam("date") Date date) {
// /teacher/search?date=2025-01-01 → date 直接转换好
}
如果转换失败,会抛出 TypeMismatchException,我们可以用异常处理来统一返回友好信息。
拦截器(Interceptor)和过滤器(Filter)
两者都可以在请求前后做手脚,但层级不同。
| 对比维度 | Filter(过滤器) | Interceptor(拦截器) |
|---|---|---|
| 归属 | Servlet 规范,Java EE | Spring MVC 框架 |
| 作用范围 | 能拦截所有进入 Servlet 的请求(包括静态资源) | 只能拦截进到 Spring MVC 的请求(DispatcherServlet 处理) |
| 是否能用 Spring Bean | 不能直接注入(可以迂回) | 可以正常注入其他 Bean |
| 执行顺序 | 先经过 Filter,再进 DispatcherServlet | 在 DispatcherServlet 之后,Controller 方法之前 |
| 典型场景 | 字符编码、跨域、权限安全检查 | 登录检查、日志记录、性能监控、用户权限补充 |
定义拦截器示例
java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 检查 session 中是否有用户
if (request.getSession().getAttribute("user") == null) {
response.sendRedirect("/login");
return false; // 不放行
}
return true;
}
}
注册拦截器
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/teacher/**") // 要拦截的
.excludePathPatterns("/teacher/login"); // 排除的
}
}
异常处理:优雅地给前端报错
还记得我们之前聊过的"异常和错误"吗?常用的处理方式是:
局部异常处理
java
@Controller
public class TeacherController {
@ExceptionHandler(TeacherNotFoundException.class)
public String handleNotFound(TeacherNotFoundException ex, Model model) {
model.addAttribute("msg", ex.getMessage());
return "error/404"; // 返回错误视图
}
}
全局异常处理(推荐)
java
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public Map<String, String> handleValidation(MethodArgumentNotValidException ex) {
// 返回校验失败信息,如 {"name":"不能为空"}
...
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public Map<String, String> handleGeneral(Exception ex) {
return Map.of("error", "服务器内部错误");
}
}