⭐ 掌握MVC架构 | 💡 精通处理流程 | 🔥 面试必备知识
📖 前言
💭 「理解SpringMVC的处理流程,就像理解一场精心编排的交响乐」
在Web开发中,SpringMVC作为最流行的MVC框架之一,其处理流程是每个Java开发者必须掌握的核心知识。本文将用最通俗易懂的方式,带你完整理解从用户发起请求到返回响应的整个过程。
想象一下:用户在浏览器输入网址按下回车,到页面显示出内容,这中间到底发生了什么?SpringMVC如何像一位高效的指挥家,协调各个组件完成这场演出?
🎯 学习目标:
- ✅ 深入理解SpringMVC的核心组件
- ✅ 掌握完整的请求处理流程
- ✅ 学会各种Controller的编写方式
- ✅ 理解数据绑定和参数传递
- ✅ 掌握视图解析机制
- ✅ 应对面试高频考点
一、🎯 什么是SpringMVC?
1.1 MVC架构模式
💡 通俗解释:MVC就像餐厅的运作模式
想象你去餐厅吃饭:
- Model(模型) = 厨房:负责准备数据(做菜)
- View(视图) = 服务员端上来的菜品:负责展示数据(呈现给顾客)
- Controller(控制器) = 服务员:负责接收请求并协调(接单、传达、上菜)
发起请求/点菜
调用
返回数据/做好的菜
选择视图
响应/上菜
用户/顾客
Controller/服务员
Model/厨房
View/菜品呈现
🌟 MVC的好处:
- 职责分离:各司其职,代码更清晰
- 易于维护:修改界面不影响业务逻辑
- 可重用性:模型和视图可以被多个控制器使用
- 并行开发:前端和后端可以同时开发
1.2 SpringMVC是什么?
🔍 定义:SpringMVC是Spring框架的一部分,是一个基于MVC架构模式的Web框架。
java
/**
* SpringMVC的核心特点
*/
public class SpringMVCFeatures {
// 1. 基于Servlet API构建
// 核心是DispatcherServlet(前端控制器)
// 2. 遵循MVC设计模式
// Model: 业务数据和逻辑
// View: 展示层(JSP、Thymeleaf等)
// Controller: 控制器(处理请求)
// 3. 与Spring无缝集成
// 可以使用Spring的依赖注入、AOP等功能
// 4. 灵活的配置方式
// 支持XML配置和注解配置
// 5. 强大的数据绑定
// 自动将请求参数绑定到Java对象
}
二、🏗️ SpringMVC核心组件
在理解处理流程之前,我们先认识一下SpringMVC的核心组件。就像认识乐团的各个乐器一样!
2.1 DispatcherServlet(前端控制器)🎯
💡 通俗解释:DispatcherServlet就像一个交通警察,负责指挥所有的请求
作用:
- 接收所有的HTTP请求
- 将请求分发给对应的Handler(控制器)
- 协调各个组件完成请求处理
java
/**
* DispatcherServlet工作示意(简化版)
*/
public class DispatcherServletDemo extends HttpServlet {
// 处理器映射器:找到处理请求的Controller
private HandlerMapping handlerMapping;
// 处理器适配器:执行Controller方法
private HandlerAdapter handlerAdapter;
// 视图解析器:解析视图名称
private ViewResolver viewResolver;
@Override
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) {
try {
// 第1步:根据请求URL找到对应的Handler
HandlerExecutionChain handler = handlerMapping.getHandler(request);
// 第2步:找到能执行这个Handler的适配器
HandlerAdapter adapter = handlerAdapter;
// 第3步:执行Handler(Controller的方法)
ModelAndView mv = adapter.handle(request, response, handler);
// 第4步:解析视图并渲染
View view = viewResolver.resolveViewName(mv.getViewName());
view.render(mv.getModel(), request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
配置DispatcherServlet:
xml
<!-- web.xml配置 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定Spring配置文件位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-config.xml</param-value>
</init-param>
<!-- 启动时加载 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 拦截所有请求 -->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2.2 HandlerMapping(处理器映射器)🗺️
💡 通俗解释:HandlerMapping就像GPS导航,根据URL找到对应的Controller
作用:根据请求的URL,找到对应的处理器(Controller)
java
/**
* HandlerMapping示例
* 维护URL和Controller方法的映射关系
*/
@Configuration
public class HandlerMappingDemo {
/**
* URL映射表(简化示例)
* 实际上SpringMVC会自动扫描@RequestMapping注解建立映射
*/
private Map<String, HandlerMethod> urlMapping = new HashMap<>();
public void initMapping() {
// URL -> Controller方法
urlMapping.put("/user/list",
new HandlerMethod(UserController.class, "getUserList"));
urlMapping.put("/user/add",
new HandlerMethod(UserController.class, "addUser"));
urlMapping.put("/user/{id}",
new HandlerMethod(UserController.class, "getUser"));
}
/**
* 根据请求URL查找Handler
*/
public HandlerMethod getHandler(String url) {
// 1. 精确匹配
if (urlMapping.containsKey(url)) {
return urlMapping.get(url);
}
// 2. 路径变量匹配(如/user/123)
for (Map.Entry<String, HandlerMethod> entry : urlMapping.entrySet()) {
if (matchPathVariable(entry.getKey(), url)) {
return entry.getValue();
}
}
return null;
}
}
/**
* 实际使用:通过注解建立映射
*/
@Controller
@RequestMapping("/user")
public class UserController {
// URL: /user/list
@RequestMapping("/list")
public String getUserList() {
return "userList";
}
// URL: /user/add
@RequestMapping("/add")
public String addUser() {
return "addUser";
}
// URL: /user/123(路径变量)
@RequestMapping("/{id}")
public String getUser(@PathVariable Long id) {
return "userDetail";
}
}
2.3 HandlerAdapter(处理器适配器)🔌
💡 通俗解释:HandlerAdapter就像电源适配器,让不同类型的Controller都能工作
作用:执行Handler(Controller方法),并返回ModelAndView
java
/**
* HandlerAdapter示例
* 适配不同类型的Controller
*/
public interface HandlerAdapter {
/**
* 判断是否支持该Handler
*/
boolean supports(Object handler);
/**
* 执行Handler并返回ModelAndView
*/
ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception;
}
/**
* 注解方式的Controller适配器
*/
public class RequestMappingHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
// 支持@RequestMapping注解的方法
return handler instanceof HandlerMethod;
}
@Override
public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 1. 准备方法参数(从request中提取)
Object[] args = prepareArguments(request, handlerMethod);
// 2. 反射调用Controller方法
Method method = handlerMethod.getMethod();
Object controller = handlerMethod.getBean();
Object result = method.invoke(controller, args);
// 3. 处理返回值,构建ModelAndView
ModelAndView mv = new ModelAndView();
if (result instanceof String) {
mv.setViewName((String) result); // 视图名称
} else if (result instanceof ModelAndView) {
mv = (ModelAndView) result;
}
return mv;
}
/**
* 准备方法参数
*/
private Object[] prepareArguments(HttpServletRequest request,
HandlerMethod handlerMethod) {
// 根据参数注解(@RequestParam、@PathVariable等)从request中提取值
// 这里简化处理
return new Object[]{};
}
}
2.4 ViewResolver(视图解析器)👀
💡 通俗解释:ViewResolver就像翻译官,把逻辑视图名翻译成实际的页面文件
作用:根据逻辑视图名(如"userList"),解析成实际的视图对象(如"/WEB-INF/views/userList.jsp")
java
/**
* ViewResolver示例
*/
public interface ViewResolver {
/**
* 解析视图名称
* @param viewName 逻辑视图名
* @return View对象
*/
View resolveViewName(String viewName, Locale locale) throws Exception;
}
/**
* 常用的InternalResourceViewResolver
*/
public class InternalResourceViewResolver implements ViewResolver {
private String prefix = "/WEB-INF/views/"; // 前缀
private String suffix = ".jsp"; // 后缀
@Override
public View resolveViewName(String viewName, Locale locale) {
// 拼接完整路径
// viewName="userList" -> "/WEB-INF/views/userList.jsp"
String url = prefix + viewName + suffix;
return new InternalResourceView(url);
}
}
配置ViewResolver:
xml
<!-- Spring配置文件 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/views/"/>
<!-- 后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
java
/**
* 或者使用Java配置
*/
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver =
new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}
2.5 核心组件总结
返回Handler
执行Controller
返回ModelAndView
返回View
渲染响应
用户请求
DispatcherServlet
前端控制器
HandlerMapping
处理器映射器
HandlerAdapter
处理器适配器
Controller
控制器
ViewResolver
视图解析器
View
视图
| 组件 | 作用 | 比喻 |
|---|---|---|
| DispatcherServlet | 前端控制器,统一处理请求 | 交通警察 |
| HandlerMapping | 根据URL找到Controller | GPS导航 |
| HandlerAdapter | 执行Controller方法 | 电源适配器 |
| Controller | 处理业务逻辑 | 厨师 |
| ViewResolver | 解析视图名称 | 翻译官 |
| View | 渲染页面 | 最终的菜品 |
三、🎬 SpringMVC完整处理流程
💡 核心流程:请求 → DispatcherServlet → Handler → 返回 → 视图渲染 → 响应
3.1 流程图解
View ViewResolver Controller HandlerAdapter HandlerMapping DispatcherServlet 用户浏览器 View ViewResolver Controller HandlerAdapter HandlerMapping DispatcherServlet 用户浏览器 ①发送请求 GET /user/list ②查找Handler "谁能处理/user/list?" ③返回Handler UserController.getUserList() ④执行Handler "帮我调用这个方法" ⑤调用方法 getUserList() ⑥返回ModelAndView viewName="userList" model={users: [...]} ⑦返回ModelAndView ⑧解析视图 "userList是哪个页面?" ⑨返回View /WEB-INF/views/userList.jsp ⑩渲染视图 传入model数据 ⑪返回HTML响应 显示用户列表页面
3.2 详细步骤说明
步骤1️⃣:用户发起请求
java
/**
* 用户在浏览器输入URL并访问
* 例如:http://localhost:8080/app/user/list
*/
// 浏览器发送HTTP请求:
GET /app/user/list HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0...
Accept: text/html...
步骤2️⃣:DispatcherServlet接收请求
java
/**
* DispatcherServlet的doDispatch方法(核心方法)
*/
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
try {
// ========== 第2步:查找Handler ==========
// 根据请求URL找到对应的Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 没找到Handler,返回404
noHandlerFound(processedRequest, response);
return;
}
// ========== 第3步:获取HandlerAdapter ==========
// 找到能执行这个Handler的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// ========== 第4步:执行拦截器的preHandle方法 ==========
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return; // 拦截器返回false,中断请求
}
// ========== 第5步:执行Handler(Controller方法) ==========
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// ========== 第6步:执行拦截器的postHandle方法 ==========
mappedHandler.applyPostHandle(processedRequest, response, mv);
// ========== 第7步:处理返回结果(视图渲染) ==========
processDispatchResult(processedRequest, response, mappedHandler, mv, null);
} catch (Exception ex) {
// 异常处理
processDispatchResult(processedRequest, response, mappedHandler, mv, ex);
}
}
步骤3️⃣:HandlerMapping查找Handler
java
/**
* 根据请求URL查找对应的Handler
*/
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) {
// 遍历所有的HandlerMapping
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
// 尝试获取Handler
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler; // 找到了就返回
}
}
}
return null; // 没找到
}
/**
* RequestMappingHandlerMapping的查找逻辑
*/
public class RequestMappingHandlerMapping extends HandlerMapping {
// 存储URL和Controller方法的映射
// Key: RequestMappingInfo(包含URL、请求方法等)
// Value: HandlerMethod(Controller方法)
private final Map<RequestMappingInfo, HandlerMethod> mappingRegistry
= new HashMap<>();
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) {
// 1. 获取请求路径:/user/list
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
// 2. 查找匹配的HandlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
// 3. 创建Handler执行链(包含拦截器)
return getHandlerExecutionChain(handlerMethod, request);
}
}
步骤4️⃣:HandlerAdapter执行Handler
java
/**
* RequestMappingHandlerAdapter执行Controller方法
*/
public class RequestMappingHandlerAdapter extends HandlerAdapter {
@Override
public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// handler就是HandlerMethod(Controller的方法)
return handleInternal(request, response, (HandlerMethod) handler);
}
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) {
ModelAndView mv = null;
// ===== 1. 准备方法参数 =====
Object[] args = getMethodArgumentValues(request, handlerMethod);
// ===== 2. 反射调用Controller方法 =====
Object returnValue = invokeHandlerMethod(handlerMethod, args);
// ===== 3. 处理返回值 =====
mv = handleReturnValue(returnValue, handlerMethod);
return mv;
}
/**
* 获取方法参数值
*/
private Object[] getMethodArgumentValues(HttpServletRequest request,
HandlerMethod handlerMethod) {
MethodParameter[] parameters = handlerMethod.getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
// 根据参数注解解析参数值
if (parameter.hasParameterAnnotation(RequestParam.class)) {
// @RequestParam:从request.getParameter()获取
RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
args[i] = request.getParameter(ann.value());
} else if (parameter.hasParameterAnnotation(PathVariable.class)) {
// @PathVariable:从URL路径中提取
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
args[i] = extractPathVariable(request, ann.value());
} else if (parameter.hasParameterAnnotation(RequestBody.class)) {
// @RequestBody:从request body中解析JSON
args[i] = parseRequestBody(request, parameter.getParameterType());
} else {
// 普通参数:尝试自动绑定
args[i] = bindParameter(request, parameter);
}
}
return args;
}
/**
* 反射调用方法
*/
private Object invokeHandlerMethod(HandlerMethod handlerMethod, Object[] args) {
Object controller = handlerMethod.getBean(); // Controller实例
Method method = handlerMethod.getMethod(); // 方法对象
try {
// 反射调用
return method.invoke(controller, args);
} catch (Exception e) {
throw new RuntimeException("执行Controller方法失败", e);
}
}
}
步骤5️⃣:Controller处理业务逻辑
java
/**
* Controller示例:用户管理
*/
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 查询用户列表
* URL: /user/list
*
* 处理流程:
* 1. HandlerAdapter调用这个方法
* 2. 执行业务逻辑(查询数据库)
* 3. 返回ModelAndView
*/
@RequestMapping("/list")
public ModelAndView getUserList() {
// ===== 1. 调用Service层查询数据 =====
List<User> users = userService.findAll();
// ===== 2. 创建ModelAndView =====
ModelAndView mv = new ModelAndView();
// ===== 3. 设置视图名称 =====
mv.setViewName("userList"); // 逻辑视图名
// ===== 4. 添加模型数据 =====
mv.addObject("users", users); // 传递给页面的数据
mv.addObject("totalCount", users.size());
// ===== 5. 返回 =====
return mv;
}
/**
* 简化写法:直接返回视图名,数据通过Model传递
*/
@RequestMapping("/list2")
public String getUserList2(Model model) {
List<User> users = userService.findAll();
// Model会自动包装成ModelAndView
model.addAttribute("users", users);
// 返回视图名
return "userList";
}
}
步骤6️⃣:ViewResolver解析视图
java
/**
* ViewResolver解析视图名称
*/
public class InternalResourceViewResolver implements ViewResolver {
private String prefix = "/WEB-INF/views/";
private String suffix = ".jsp";
@Override
public View resolveViewName(String viewName, Locale locale) {
// ===== 1. 拼接完整路径 =====
// viewName = "userList"
// 完整路径 = "/WEB-INF/views/" + "userList" + ".jsp"
// = "/WEB-INF/views/userList.jsp"
String url = buildViewUrl(viewName);
// ===== 2. 创建View对象 =====
return new InternalResourceView(url);
}
private String buildViewUrl(String viewName) {
StringBuilder url = new StringBuilder();
// 添加前缀
if (prefix != null) {
url.append(prefix);
}
// 添加视图名
url.append(viewName);
// 添加后缀
if (suffix != null) {
url.append(suffix);
}
return url.toString();
}
}
步骤7️⃣:View渲染页面
java
/**
* View渲染视图
*/
public class InternalResourceView implements View {
private String url; // JSP文件路径
@Override
public void render(Map<String, ?> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
// ===== 1. 将Model数据放入request =====
exposeModelAsRequestAttributes(model, request);
// ===== 2. 转发到JSP页面 =====
RequestDispatcher rd = request.getRequestDispatcher(url);
rd.forward(request, response);
}
/**
* 将Model数据设置为request属性
*/
private void exposeModelAsRequestAttributes(Map<String, ?> model,
HttpServletRequest request) {
// model = {users: [...], totalCount: 10}
for (Map.Entry<String, ?> entry : model.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// request.setAttribute("users", [...])
// request.setAttribute("totalCount", 10)
request.setAttribute(key, value);
}
}
}
对应的JSP页面:
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<p>总数:${totalCount}</p>
<table border="1">
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
</tr>
<!-- 遍历users集合 -->
<c:forEach items="${users}" var="user">
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.age}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
步骤8️⃣:返回响应给用户
java
/**
* 最终的HTML响应
*/
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 1234
<html>
<head><title>用户列表</title></head>
<body>
<h1>用户列表</h1>
<p>总数:10</p>
<table border="1">
<tr><th>ID</th><th>姓名</th><th>年龄</th></tr>
<tr><td>1</td><td>张三</td><td>25</td></tr>
<tr><td>2</td><td>李四</td><td>30</td></tr>
...
</table>
</body>
</html>
3.3 完整流程总结
java
/**
* SpringMVC处理流程总结(9个步骤)
*/
public class SpringMVCProcessFlow {
/**
* 第1步:用户发起请求
* 浏览器输入URL: http://localhost:8080/app/user/list
*/
void step1_UserRequest() {
// GET /app/user/list HTTP/1.1
}
/**
* 第2步:DispatcherServlet接收请求
* 前端控制器统一处理所有请求
*/
void step2_DispatcherServletReceive() {
// doDispatch(request, response)
}
/**
* 第3步:HandlerMapping查找Handler
* 根据URL找到对应的Controller方法
*/
void step3_FindHandler() {
// /user/list -> UserController.getUserList()
}
/**
* 第4步:HandlerAdapter执行Handler
* 准备参数并反射调用Controller方法
*/
void step4_ExecuteHandler() {
// Object[] args = getArguments();
// Object result = method.invoke(controller, args);
}
/**
* 第5步:Controller处理业务
* 调用Service层,返回ModelAndView
*/
void step5_BusinessLogic() {
// List<User> users = userService.findAll();
// ModelAndView mv = new ModelAndView("userList");
// mv.addObject("users", users);
// return mv;
}
/**
* 第6步:返回ModelAndView
* Controller方法执行完成
*/
void step6_ReturnModelAndView() {
// ModelAndView(viewName="userList", model={users:[...]})
}
/**
* 第7步:ViewResolver解析视图
* 逻辑视图名 -> 物理视图路径
*/
void step7_ResolveView() {
// "userList" -> "/WEB-INF/views/userList.jsp"
}
/**
* 第8步:View渲染页面
* 将Model数据填充到页面
*/
void step8_RenderView() {
// request.setAttribute("users", users);
// forward to JSP
}
/**
* 第9步:返回响应
* 将渲染后的HTML返回给浏览器
*/
void step9_Response() {
// HTTP/1.1 200 OK
// Content-Type: text/html
// <html>...</html>
}
}
四、💻 Controller开发详解
4.1 Controller的多种写法
写法1:返回ModelAndView
java
/**
* 方式1:返回ModelAndView对象(最完整的方式)
*/
@Controller
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
/**
* 查询商品列表
* URL: /product/list
*
* 返回:ModelAndView对象
* - viewName: 视图名称
* - model: 模型数据
*/
@RequestMapping("/list")
public ModelAndView getProductList() {
// 1. 查询数据
List<Product> products = productService.findAll();
// 2. 创建ModelAndView
ModelAndView mv = new ModelAndView();
// 3. 设置视图名称
mv.setViewName("product/list"); // 对应/WEB-INF/views/product/list.jsp
// 4. 添加模型数据
mv.addObject("products", products);
mv.addObject("totalCount", products.size());
mv.addObject("pageTitle", "商品列表");
return mv;
}
}
写法2:返回String(视图名)
java
/**
* 方式2:返回String(最常用的方式)
*/
@Controller
@RequestMapping("/product")
public class ProductController2 {
@Autowired
private ProductService productService;
/**
* 查询商品列表
*
* 参数:Model用于存放数据
* 返回:视图名称(String)
*/
@RequestMapping("/list")
public String getProductList(Model model) {
// 1. 查询数据
List<Product> products = productService.findAll();
// 2. 将数据放入Model
model.addAttribute("products", products);
model.addAttribute("totalCount", products.size());
// 3. 返回视图名称
// SpringMVC会自动包装成ModelAndView
return "product/list";
}
/**
* 重定向示例
*/
@RequestMapping("/save")
public String saveProduct(Product product) {
productService.save(product);
// 重定向到列表页
return "redirect:/product/list";
}
/**
* 转发示例
*/
@RequestMapping("/forward")
public String forwardExample() {
// 转发到另一个Controller
return "forward:/product/list";
}
}
写法3:返回void(直接操作response)
java
/**
* 方式3:返回void(适合Ajax请求)
*/
@Controller
@RequestMapping("/api")
public class ApiController {
/**
* 直接写响应
* 适合返回JSON数据
*/
@RequestMapping("/getData")
public void getData(HttpServletResponse response) throws IOException {
// 设置响应类型
response.setContentType("application/json;charset=UTF-8");
// 构造JSON数据
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "success");
result.put("data", Arrays.asList("item1", "item2"));
// 转换为JSON字符串
String json = new ObjectMapper().writeValueAsString(result);
// 写入响应
response.getWriter().write(json);
}
}
写法4:返回对象(@ResponseBody)
java
/**
* 方式4:返回对象,自动转JSON(RESTful API常用)
*/
@Controller
@RequestMapping("/api/user")
public class UserApiController {
@Autowired
private UserService userService;
/**
* @ResponseBody:将返回值转换为JSON
* 不经过ViewResolver,直接写入响应体
*/
@RequestMapping("/list")
@ResponseBody
public List<User> getUserList() {
// 返回的List会自动转换为JSON数组
return userService.findAll();
}
/**
* 返回自定义对象
*/
@RequestMapping("/{id}")
@ResponseBody
public User getUser(@PathVariable Long id) {
// 返回的User对象会自动转换为JSON
return userService.findById(id);
}
/**
* 返回统一响应格式
*/
@RequestMapping("/save")
@ResponseBody
public Result saveUser(@RequestBody User user) {
userService.save(user);
return Result.success("保存成功");
}
}
/**
* 统一响应类
*/
class Result {
private int code;
private String message;
private Object data;
public static Result success(String message) {
Result result = new Result();
result.code = 200;
result.message = message;
return result;
}
// getters and setters...
}
写法5:使用@RestController
java
/**
* 方式5:@RestController(相当于@Controller + @ResponseBody)
* RESTful API的标准写法
*/
@RestController // 等价于 @Controller + @ResponseBody
@RequestMapping("/api/product")
public class ProductRestController {
@Autowired
private ProductService productService;
/**
* 所有方法默认都返回JSON
* 不需要每个方法都加@ResponseBody
*/
@GetMapping("/list")
public List<Product> getList() {
return productService.findAll();
}
@GetMapping("/{id}")
public Product getById(@PathVariable Long id) {
return productService.findById(id);
}
@PostMapping
public Result save(@RequestBody Product product) {
productService.save(product);
return Result.success("保存成功");
}
@PutMapping("/{id}")
public Result update(@PathVariable Long id, @RequestBody Product product) {
product.setId(id);
productService.update(product);
return Result.success("更新成功");
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Long id) {
productService.deleteById(id);
return Result.success("删除成功");
}
}
4.2 参数接收详解
java
/**
* Controller参数接收的各种方式
*/
@Controller
@RequestMapping("/demo")
public class ParameterController {
/**
* 1. 普通参数:自动绑定
* URL: /demo/test1?name=张三&age=25
*/
@RequestMapping("/test1")
public String test1(String name, Integer age, Model model) {
// SpringMVC自动将请求参数name和age绑定到方法参数
model.addAttribute("name", name);
model.addAttribute("age", age);
return "result";
}
/**
* 2. @RequestParam:指定参数名和默认值
* URL: /demo/test2?username=李四&page=1
*/
@RequestMapping("/test2")
public String test2(
@RequestParam("username") String name, // 参数名不同时指定
@RequestParam(value = "page", defaultValue = "1") int page, // 默认值
@RequestParam(required = false) String keyword, // 可选参数
Model model) {
model.addAttribute("name", name);
model.addAttribute("page", page);
model.addAttribute("keyword", keyword);
return "result";
}
/**
* 3. @PathVariable:路径变量
* URL: /demo/user/123
*/
@RequestMapping("/user/{id}")
public String test3(@PathVariable Long id, Model model) {
// 从URL路径中提取参数
model.addAttribute("userId", id);
return "result";
}
/**
* 4. 对象绑定:自动封装到对象
* URL: /demo/test4?name=王五&age=30&email=wang@example.com
*/
@RequestMapping("/test4")
public String test4(User user, Model model) {
// SpringMVC自动将请求参数封装到User对象
// user.name = "王五"
// user.age = 30
// user.email = "wang@example.com"
model.addAttribute("user", user);
return "result";
}
/**
* 5. @RequestBody:接收JSON数据
* Content-Type: application/json
* Body: {"name":"赵六","age":35,"email":"zhao@example.com"}
*/
@RequestMapping("/test5")
@ResponseBody
public User test5(@RequestBody User user) {
// JSON自动反序列化为User对象
System.out.println("接收到:" + user);
return user;
}
/**
* 6. 数组参数
* URL: /demo/test6?ids=1&ids=2&ids=3
*/
@RequestMapping("/test6")
public String test6(Long[] ids, Model model) {
// ids = [1, 2, 3]
model.addAttribute("ids", ids);
return "result";
}
/**
* 7. List参数(需要包装类)
* URL: /demo/test7?userIds=1&userIds=2&userIds=3
*/
@RequestMapping("/test7")
public String test7(@RequestParam List<Long> userIds, Model model) {
// userIds = [1, 2, 3]
model.addAttribute("userIds", userIds);
return "result";
}
/**
* 8. Map参数
* URL: /demo/test8?key1=value1&key2=value2
*/
@RequestMapping("/test8")
public String test8(@RequestParam Map<String, String> params, Model model) {
// params = {key1=value1, key2=value2}
model.addAttribute("params", params);
return "result";
}
/**
* 9. 日期参数(需要格式化)
* URL: /demo/test9?birthday=2000-01-01
*/
@RequestMapping("/test9")
public String test9(
@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday,
Model model) {
model.addAttribute("birthday", birthday);
return "result";
}
/**
* 10. 文件上传
*/
@RequestMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file, Model model) {
if (!file.isEmpty()) {
String filename = file.getOriginalFilename();
// 保存文件...
model.addAttribute("filename", filename);
}
return "result";
}
}
五、🎯 RESTful风格API开发
5.1 什么是RESTful?
💡 通俗解释:RESTful就是一种设计Web API的风格,让URL更简洁、更有意义
传统风格 vs RESTful风格:
| 操作 | 传统风格 | RESTful风格 |
|---|---|---|
| 查询所有用户 | /user/list |
GET /users |
| 查询单个用户 | /user/get?id=1 |
GET /users/1 |
| 添加用户 | /user/add |
POST /users |
| 更新用户 | /user/update?id=1 |
PUT /users/1 |
| 删除用户 | /user/delete?id=1 |
DELETE /users/1 |
5.2 RESTful API完整示例
java
/**
* RESTful风格的用户管理API
*
* 核心思想:
* 1. URL代表资源(名词)
* 2. HTTP方法代表操作(动词)
* 3. 返回JSON数据
*/
@RestController
@RequestMapping("/api/users")
public class UserRestController {
@Autowired
private UserService userService;
/**
* 查询所有用户
* GET /api/users
*
* 返回:用户列表JSON
*/
@GetMapping
public Result<List<User>> getAllUsers() {
List<User> users = userService.findAll();
return Result.success(users);
}
/**
* 根据ID查询用户
* GET /api/users/123
*
* 路径变量:{id}
* 返回:单个用户JSON
*/
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return Result.error("用户不存在");
}
return Result.success(user);
}
/**
* 创建用户
* POST /api/users
* Content-Type: application/json
* Body: {"name":"张三","age":25,"email":"zhang@example.com"}
*
* @RequestBody:接收JSON数据
* 返回:创建的用户JSON
*/
@PostMapping
public Result<User> createUser(@RequestBody @Valid User user) {
// @Valid:参数校验
User created = userService.save(user);
return Result.success(created);
}
/**
* 更新用户(完整更新)
* PUT /api/users/123
* Content-Type: application/json
* Body: {"name":"李四","age":30,"email":"li@example.com"}
*
* PUT:完整替换资源
*/
@PutMapping("/{id}")
public Result<User> updateUser(
@PathVariable Long id,
@RequestBody @Valid User user) {
user.setId(id);
User updated = userService.update(user);
return Result.success(updated);
}
/**
* 部分更新用户
* PATCH /api/users/123
* Content-Type: application/json
* Body: {"name":"王五"} // 只更新name字段
*
* PATCH:部分更新资源
*/
@PatchMapping("/{id}")
public Result<User> patchUser(
@PathVariable Long id,
@RequestBody Map<String, Object> updates) {
User user = userService.partialUpdate(id, updates);
return Result.success(user);
}
/**
* 删除用户
* DELETE /api/users/123
*
* 返回:删除结果
*/
@DeleteMapping("/{id}")
public Result<Void> deleteUser(@PathVariable Long id) {
userService.deleteById(id);
return Result.success(null, "删除成功");
}
/**
* 条件查询
* GET /api/users/search?name=张&age=25&page=1&size=10
*
* 查询参数:筛选条件
*/
@GetMapping("/search")
public Result<PageResult<User>> searchUsers(
@RequestParam(required = false) String name,
@RequestParam(required = false) Integer age,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
PageResult<User> result = userService.search(name, age, page, size);
return Result.success(result);
}
}
/**
* 统一响应格式
*/
@Data
public class Result<T> {
private int code; // 状态码:200成功,其他失败
private String message; // 提示信息
private T data; // 数据
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.code = 200;
result.message = "success";
result.data = data;
return result;
}
public static <T> Result<T> success(T data, String message) {
Result<T> result = new Result<>();
result.code = 200;
result.message = message;
result.data = data;
return result;
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.code = 500;
result.message = message;
return result;
}
}
/**
* 分页结果
*/
@Data
public class PageResult<T> {
private List<T> list; // 当前页数据
private long total; // 总记录数
private int page; // 当前页码
private int size; // 每页大小
private int totalPages; // 总页数
}
5.3 参数校验
java
/**
* 使用JSR-303进行参数校验
*/
@Data
public class User {
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度必须在2-20之间")
private String name;
@Min(value = 0, message = "年龄不能小于0")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
/**
* Controller中使用@Valid触发校验
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public Result<User> createUser(@RequestBody @Valid User user) {
// 如果校验失败,会抛出MethodArgumentNotValidException
// 可以通过全局异常处理器统一处理
return Result.success(userService.save(user));
}
}
/**
* 全局异常处理器
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidationException(MethodArgumentNotValidException ex) {
// 获取所有校验错误
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
return Result.error("参数校验失败:" + String.join(", ", errors));
}
}
六、🛡️ 拦截器(Interceptor)
6.1 什么是拦截器?
💡 通俗解释:拦截器就像机场安检,在请求到达Controller之前和之后进行检查和处理
用户请求
拦截器1-preHandle
拦截器2-preHandle
Controller
拦截器2-postHandle
拦截器1-postHandle
视图渲染
拦截器2-afterCompletion
拦截器1-afterCompletion
响应
6.2 拦截器的实现
java
/**
* 自定义拦截器
* 实现HandlerInterceptor接口
*/
public class LoginInterceptor implements HandlerInterceptor {
/**
* 前置处理:在Controller执行之前调用
*
* 返回值:
* - true:继续执行后续流程
* - false:中断请求,不执行Controller
*
* 使用场景:
* - 登录检查
* - 权限验证
* - 日志记录
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("========== preHandle ==========");
System.out.println("请求URL: " + request.getRequestURI());
// 1. 从session中获取用户信息
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
// 2. 判断是否登录
if (user == null) {
// 未登录,重定向到登录页
System.out.println("用户未登录,跳转登录页");
response.sendRedirect(request.getContextPath() + "/login");
return false; // 中断请求
}
// 3. 已登录,继续执行
System.out.println("用户已登录:" + user);
return true; // 继续执行
}
/**
* 后置处理:在Controller执行之后、视图渲染之前调用
*
* 参数:
* - modelAndView:Controller返回的ModelAndView
*
* 使用场景:
* - 修改ModelAndView
* - 添加公共数据
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("========== postHandle ==========");
if (modelAndView != null) {
// 可以修改ModelAndView
String viewName = modelAndView.getViewName();
System.out.println("视图名称: " + viewName);
// 添加公共数据(如当前时间)
modelAndView.addObject("currentTime", new Date());
modelAndView.addObject("serverName", "MyServer");
}
}
/**
* 完成处理:在视图渲染之后调用
*
* 特点:
* - 无论是否发生异常都会调用
* - 最后执行的方法
*
* 使用场景:
* - 资源清理
* - 日志记录
* - 性能统计
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("========== afterCompletion ==========");
if (ex != null) {
System.out.println("发生异常: " + ex.getMessage());
}
System.out.println("请求处理完成");
}
}
6.3 拦截器配置
java
/**
* 配置拦截器
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 添加拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// ===== 登录拦截器 =====
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns( // 排除以下路径
"/login", // 登录页面
"/register", // 注册页面
"/static/**", // 静态资源
"/error" // 错误页面
)
.order(1); // 执行顺序(数字越小越先执行)
// ===== 权限拦截器 =====
registry.addInterceptor(new PermissionInterceptor())
.addPathPatterns("/admin/**") // 只拦截/admin/**路径
.order(2);
// ===== 日志拦截器 =====
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/**")
.order(3);
}
}
6.4 多个拦截器的执行顺序
java
/**
* 多个拦截器的执行流程
*
* 假设有3个拦截器:A、B、C(按order顺序)
*/
public class InterceptorExecutionOrder {
/**
* 正常执行流程:
*
* 1. A.preHandle()
* 2. B.preHandle()
* 3. C.preHandle()
* 4. Controller执行
* 5. C.postHandle()
* 6. B.postHandle()
* 7. A.postHandle()
* 8. 视图渲染
* 9. C.afterCompletion()
* 10. B.afterCompletion()
* 11. A.afterCompletion()
*/
/**
* 如果B.preHandle()返回false:
*
* 1. A.preHandle() ✅ 执行
* 2. B.preHandle() ✅ 执行,返回false
* 3. A.afterCompletion() ✅ 执行(已经preHandle的拦截器会执行afterCompletion)
*
* C.preHandle()、Controller、所有postHandle都不会执行
*/
}
6.5 实战示例:性能监控拦截器
java
/**
* 性能监控拦截器
* 记录每个请求的执行时间
*/
public class PerformanceInterceptor implements HandlerInterceptor {
// 使用ThreadLocal存储每个请求的开始时间
private ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 记录开始时间
long startTime = System.currentTimeMillis();
startTimeThreadLocal.set(startTime);
System.out.println("====== 请求开始 ======");
System.out.println("URL: " + request.getRequestURI());
System.out.println("Method: " + request.getMethod());
System.out.println("开始时间: " + new Date(startTime));
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
// 计算执行时间
long startTime = startTimeThreadLocal.get();
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
System.out.println("====== 请求结束 ======");
System.out.println("URL: " + request.getRequestURI());
System.out.println("执行时间: " + executeTime + "ms");
// 如果执行时间过长,记录警告
if (executeTime > 1000) {
System.out.println("⚠️ 警告:请求执行时间过长!");
}
// 清除ThreadLocal,避免内存泄漏
startTimeThreadLocal.remove();
}
}
七、⚠️ 异常处理
7.1 异常处理的三种方式
方式1:SimpleMappingExceptionResolver
java
/**
* 简单的异常映射解析器
* 配置异常类型和视图的映射关系
*/
@Configuration
public class ExceptionConfig {
@Bean
public SimpleMappingExceptionResolver exceptionResolver() {
SimpleMappingExceptionResolver resolver =
new SimpleMappingExceptionResolver();
// 设置异常映射
Properties properties = new Properties();
properties.setProperty("java.lang.NullPointerException", "error/null");
properties.setProperty("java.lang.ArithmeticException", "error/arithmetic");
properties.setProperty("java.lang.Exception", "error/common");
resolver.setExceptionMappings(properties);
// 设置默认错误视图
resolver.setDefaultErrorView("error/default");
// 设置异常属性名(在页面中获取异常信息)
resolver.setExceptionAttribute("exception");
return resolver;
}
}
方式2:实现HandlerExceptionResolver接口
java
/**
* 自定义异常解析器
*/
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
ModelAndView mv = new ModelAndView();
// 根据异常类型跳转不同页面
if (ex instanceof NullPointerException) {
mv.setViewName("error/null");
mv.addObject("message", "空指针异常");
} else if (ex instanceof ArithmeticException) {
mv.setViewName("error/arithmetic");
mv.addObject("message", "算术异常");
} else {
mv.setViewName("error/common");
mv.addObject("message", "系统异常");
}
// 添加异常信息
mv.addObject("exception", ex);
mv.addObject("url", request.getRequestURI());
return mv;
}
}
方式3:@ControllerAdvice(推荐)
java
/**
* 全局异常处理器
* 使用@ControllerAdvice注解
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理空指针异常
*/
@ExceptionHandler(NullPointerException.class)
public ModelAndView handleNullPointerException(NullPointerException ex,
HttpServletRequest request) {
logger.error("空指针异常: {}", ex.getMessage(), ex);
ModelAndView mv = new ModelAndView();
mv.setViewName("error/null");
mv.addObject("message", "空指针异常");
mv.addObject("url", request.getRequestURI());
return mv;
}
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
public ModelAndView handleBusinessException(BusinessException ex,
HttpServletRequest request) {
logger.warn("业务异常: {}", ex.getMessage());
ModelAndView mv = new ModelAndView();
mv.setViewName("error/business");
mv.addObject("message", ex.getMessage());
mv.addObject("code", ex.getCode());
return mv;
}
/**
* 处理所有其他异常
*/
@ExceptionHandler(Exception.class)
public ModelAndView handleException(Exception ex, HttpServletRequest request) {
logger.error("系统异常: {}", ex.getMessage(), ex);
ModelAndView mv = new ModelAndView();
mv.setViewName("error/common");
mv.addObject("message", "系统繁忙,请稍后再试");
mv.addObject("url", request.getRequestURI());
return mv;
}
}
7.2 RESTful API的异常处理
java
/**
* RESTful API全局异常处理
* 返回JSON格式
*/
@RestControllerAdvice
public class RestExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(RestExceptionHandler.class);
/**
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleValidationException(MethodArgumentNotValidException ex) {
// 获取所有校验错误
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
logger.warn("参数校验失败: {}", errors);
return Result.error(400, "参数校验失败", errors);
}
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleBusinessException(BusinessException ex) {
logger.warn("业务异常: {}", ex.getMessage());
return Result.error(ex.getCode(), ex.getMessage(), null);
}
/**
* 处理404异常
*/
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Result handleNotFoundException(NoHandlerFoundException ex) {
logger.warn("404异常: {}", ex.getRequestURL());
return Result.error(404, "请求的资源不存在", ex.getRequestURL());
}
/**
* 处理系统异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result handleException(Exception ex) {
logger.error("系统异常: {}", ex.getMessage(), ex);
return Result.error(500, "系统繁忙,请稍后再试", null);
}
}
/**
* 统一响应格式
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
private int code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
return new Result<>(200, "success", data);
}
public static <T> Result<T> error(int code, String message, T data) {
return new Result<>(code, message, data);
}
}
/**
* 自定义业务异常
*/
public class BusinessException extends RuntimeException {
private int code;
public BusinessException(String message) {
super(message);
this.code = 400;
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
八、📚 SpringMVC面试高频题
面试题1:说说SpringMVC的处理流程?
点击查看答案
答案:
SpringMVC的处理流程可以分为9个步骤:
1. 用户发起请求
- 用户在浏览器输入URL,发送HTTP请求
2. DispatcherServlet接收请求
- 前端控制器接收所有请求,统一处理
3. HandlerMapping查找Handler
- 根据请求URL,查找对应的Controller方法
4. 返回HandlerExecutionChain
- 包含Handler和拦截器列表
5. HandlerAdapter执行Handler
- 执行拦截器的preHandle方法
- 反射调用Controller方法
- 执行拦截器的postHandle方法
6. Controller处理业务
- 调用Service层处理业务逻辑
- 返回ModelAndView
7. ViewResolver解析视图
- 将逻辑视图名解析为物理视图
8. View渲染页面
- 将Model数据填充到页面
- 执行拦截器的afterCompletion方法
9. 返回响应
- 将渲染后的HTML返回给浏览器
核心组件:
- DispatcherServlet:前端控制器
- HandlerMapping:处理器映射器
- HandlerAdapter:处理器适配器
- Controller:控制器
- ViewResolver:视图解析器
- View:视图
面试题2:SpringMVC和Spring的关系?
点击查看答案
答案:
1. 包含关系
- SpringMVC是Spring框架的一个模块
- Spring是一个完整的生态系统,包括Core、AOP、MVC、Data等
2. 依赖关系
- SpringMVC依赖Spring Core
- 使用Spring的IoC容器管理Bean
- 使用Spring的AOP功能
3. 配置关系
- 通常有两个容器:
- Spring根容器:管理Service、DAO等
- SpringMVC子容器:管理Controller等
- 子容器可以访问父容器的Bean,反之不行
4. 协作关系
浏览器
↓
SpringMVC层(Controller)
↓
Spring层(Service、Transaction)
↓
持久层(DAO、MyBatis)
↓
数据库
配置示例:
xml
<!-- web.xml -->
<!-- Spring根容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- SpringMVC子容器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-config.xml</param-value>
</init-param>
</servlet>
面试题3:SpringMVC如何防止表单重复提交?
点击查看答案
答案:
方式1:重定向(Post-Redirect-Get模式)
java
@Controller
public class FormController {
/**
* 显示表单
*/
@GetMapping("/form")
public String showForm() {
return "form";
}
/**
* 处理表单提交
* 提交后重定向,防止刷新重复提交
*/
@PostMapping("/submit")
public String submitForm(User user) {
userService.save(user);
// 重定向到成功页面
// 用户刷新只会刷新GET请求,不会重复提交
return "redirect:/success";
}
@GetMapping("/success")
public String success() {
return "success";
}
}
方式2:Token令牌机制
java
/**
* 自定义注解:需要Token验证
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireToken {
}
/**
* Token拦截器
*/
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
// 检查是否需要Token验证
if (method.hasMethodAnnotation(RequireToken.class)) {
// 获取表单提交的token
String formToken = request.getParameter("token");
// 获取session中的token
String sessionToken = (String) request.getSession()
.getAttribute("token");
// 验证token
if (formToken == null || !formToken.equals(sessionToken)) {
// token无效,拒绝请求
response.getWriter().write("重复提交!");
return false;
}
// token有效,移除session中的token(一次性)
request.getSession().removeAttribute("token");
}
}
return true;
}
}
/**
* Controller使用
*/
@Controller
public class FormController {
/**
* 显示表单时生成token
*/
@GetMapping("/form")
public String showForm(HttpSession session) {
// 生成token并存入session
String token = UUID.randomUUID().toString();
session.setAttribute("token", token);
return "form";
}
/**
* 提交表单时验证token
*/
@PostMapping("/submit")
@RequireToken // 需要token验证
public String submitForm(User user) {
userService.save(user);
return "redirect:/success";
}
}
JSP页面:
jsp
<form action="/submit" method="post">
<!-- 隐藏域:携带token -->
<input type="hidden" name="token" value="${sessionScope.token}"/>
<input type="text" name="name"/>
<button type="submit">提交</button>
</form>
方式3:前端防抖
javascript
// JavaScript防抖
let submitting = false;
function submitForm() {
if (submitting) {
alert('请勿重复提交!');
return false;
}
submitting = true;
// 提交表单
document.getElementById('myForm').submit();
// 禁用提交按钮
document.getElementById('submitBtn').disabled = true;
}
面试题4:@RequestMapping的参数有哪些?
点击查看答案
答案:
@RequestMapping注解有以下常用参数:
1. value/path:URL路径
java
@RequestMapping(value = "/user")
// 或
@RequestMapping(path = "/user")
2. method:HTTP请求方法
java
@RequestMapping(value = "/user", method = RequestMethod.GET)
// 或使用快捷注解
@GetMapping("/user") // GET请求
@PostMapping("/user") // POST请求
@PutMapping("/user") // PUT请求
@DeleteMapping("/user") // DELETE请求
3. params:请求参数条件
java
// 必须有name参数
@RequestMapping(value = "/user", params = "name")
// name参数值必须为admin
@RequestMapping(value = "/user", params = "name=admin")
// 必须有name和age参数
@RequestMapping(value = "/user", params = {"name", "age"})
// 不能有name参数
@RequestMapping(value = "/user", params = "!name")
4. headers:请求头条件
java
// 必须有Content-Type头
@RequestMapping(value = "/user", headers = "Content-Type")
// Content-Type必须为application/json
@RequestMapping(value = "/user",
headers = "Content-Type=application/json")
5. consumes:指定请求的Content-Type
java
// 只接受JSON请求
@RequestMapping(value = "/user",
consumes = "application/json")
// 只接受表单请求
@RequestMapping(value = "/user",
consumes = "application/x-www-form-urlencoded")
6. produces:指定响应的Content-Type
java
// 返回JSON
@RequestMapping(value = "/user",
produces = "application/json")
// 返回XML
@RequestMapping(value = "/user",
produces = "application/xml")
完整示例:
java
@RequestMapping(
value = "/user/save", // URL路径
method = RequestMethod.POST, // POST请求
params = "type=new", // 必须有type=new参数
headers = "Content-Type=application/json", // 请求头
consumes = "application/json", // 接受JSON
produces = "application/json" // 返回JSON
)
@ResponseBody
public Result saveUser(@RequestBody User user) {
return Result.success(userService.save(user));
}
面试题5:SpringMVC的拦截器和Filter的区别?
点击查看答案
答案:
| 对比项 | Filter(过滤器) | Interceptor(拦截器) |
|---|---|---|
| 所属 | Servlet规范 | Spring框架 |
| 拦截范围 | 所有请求(包括静态资源) | 只拦截Controller请求 |
| 执行时机 | Servlet前后 | Controller前后 |
| 依赖 | 不依赖Spring | 依赖Spring |
| Spring Bean | 不能直接注入 | 可以注入Bean |
| 配置 | web.xml或@WebFilter | implements WebMvcConfigurer |
| 粒度 | 粗粒度 | 细粒度 |
执行顺序:
Filter1 → Filter2 → DispatcherServlet → Interceptor1 → Interceptor2
→ Controller → Interceptor2 → Interceptor1 → DispatcherServlet
→ Filter2 → Filter1
Filter示例:
java
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) {
// 前置处理
System.out.println("Filter前置处理");
try {
chain.doFilter(request, response); // 继续执行
} catch (Exception e) {
e.printStackTrace();
}
// 后置处理
System.out.println("Filter后置处理");
}
}
Interceptor示例:
java
public class MyInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService; // 可以注入Bean
@Override
public boolean preHandle(...) {
System.out.println("Interceptor前置处理");
return true;
}
@Override
public void postHandle(...) {
System.out.println("Interceptor后置处理");
}
@Override
public void afterCompletion(...) {
System.out.println("Interceptor完成处理");
}
}
使用建议:
- Filter:字符编码、安全检查、日志记录
- Interceptor:登录验证、权限检查、性能监控
面试题6:SpringMVC如何实现文件上传?
点击查看答案
答案:
1. 添加依赖
xml
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
2. 配置MultipartResolver
java
@Configuration
public class WebConfig {
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
// 设置最大上传大小(字节)
resolver.setMaxUploadSize(10 * 1024 * 1024); // 10MB
// 设置单个文件最大大小
resolver.setMaxUploadSizePerFile(5 * 1024 * 1024); // 5MB
// 设置字符编码
resolver.setDefaultEncoding("UTF-8");
return resolver;
}
}
3. 单文件上传
java
@Controller
public class UploadController {
/**
* 单文件上传
*/
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file,
Model model) {
// 1. 检查文件是否为空
if (file.isEmpty()) {
model.addAttribute("message", "请选择文件!");
return "upload";
}
try {
// 2. 获取文件信息
String originalFilename = file.getOriginalFilename(); // 原始文件名
String contentType = file.getContentType(); // 文件类型
long size = file.getSize(); // 文件大小
System.out.println("文件名: " + originalFilename);
System.out.println("类型: " + contentType);
System.out.println("大小: " + size);
// 3. 生成新文件名(避免重名)
String extension = originalFilename.substring(
originalFilename.lastIndexOf("."));
String newFilename = UUID.randomUUID().toString() + extension;
// 4. 保存文件
String uploadPath = "D:/uploads/"; // 上传目录
File dest = new File(uploadPath + newFilename);
// 创建目录
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
// 保存
file.transferTo(dest);
// 5. 返回结果
model.addAttribute("message", "上传成功!");
model.addAttribute("filename", newFilename);
} catch (IOException e) {
e.printStackTrace();
model.addAttribute("message", "上传失败:" + e.getMessage());
}
return "uploadResult";
}
}
4. 多文件上传
java
/**
* 多文件上传
*/
@PostMapping("/batchUpload")
public String batchUpload(@RequestParam("files") MultipartFile[] files,
Model model) {
List<String> uploadedFiles = new ArrayList<>();
for (MultipartFile file : files) {
if (!file.isEmpty()) {
try {
String filename = saveFile(file); // 保存文件
uploadedFiles.add(filename);
} catch (IOException e) {
e.printStackTrace();
}
}
}
model.addAttribute("files", uploadedFiles);
return "uploadResult";
}
/**
* 保存文件的公共方法
*/
private String saveFile(MultipartFile file) throws IOException {
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(
originalFilename.lastIndexOf("."));
String newFilename = UUID.randomUUID().toString() + extension;
String uploadPath = "D:/uploads/";
File dest = new File(uploadPath + newFilename);
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
file.transferTo(dest);
return newFilename;
}
5. HTML表单
html
<!-- 单文件上传 -->
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file"/>
<button type="submit">上传</button>
</form>
<!-- 多文件上传 -->
<form action="/batchUpload" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple/>
<button type="submit">上传</button>
</form>
注意事项:
- 表单必须是POST方式
- enctype必须是multipart/form-data
- 配置上传文件大小限制
- 验证文件类型和大小
- 生成唯一文件名避免冲突
- 处理上传异常
面试题7:SpringMVC的返回值类型有哪些?
点击查看答案
答案:
SpringMVC Controller方法支持多种返回值类型:
1. String:返回视图名称
java
@RequestMapping("/user")
public String getUser() {
return "user"; // 逻辑视图名
}
@RequestMapping("/redirect")
public String redirect() {
return "redirect:/user"; // 重定向
}
@RequestMapping("/forward")
public String forward() {
return "forward:/user"; // 转发
}
2. ModelAndView:视图和数据
java
@RequestMapping("/user")
public ModelAndView getUser() {
ModelAndView mv = new ModelAndView();
mv.setViewName("user");
mv.addObject("name", "张三");
return mv;
}
3. void:直接操作response
java
@RequestMapping("/download")
public void download(HttpServletResponse response) throws IOException {
response.setContentType("application/octet-stream");
response.getOutputStream().write(data);
}
4. Object:@ResponseBody转JSON
java
@RequestMapping("/user")
@ResponseBody
public User getUser() {
return new User("张三", 25); // 自动转JSON
}
5. ResponseEntity:设置响应头和状态码
java
@RequestMapping("/user")
public ResponseEntity<User> getUser() {
User user = new User("张三", 25);
HttpHeaders headers = new HttpHeaders();
headers.add("Custom-Header", "value");
return new ResponseEntity<>(user, headers, HttpStatus.OK);
}
6. DeferredResult:异步处理
java
@RequestMapping("/async")
public DeferredResult<String> async() {
DeferredResult<String> result = new DeferredResult<>();
// 异步处理
new Thread(() -> {
try {
Thread.sleep(3000);
result.setResult("处理完成");
} catch (Exception e) {
result.setErrorResult("处理失败");
}
}).start();
return result;
}
7. Callable:异步处理
java
@RequestMapping("/callable")
public Callable<String> callable() {
return () -> {
Thread.sleep(3000);
return "处理完成";
};
}
九、📝 总结
核心知识点回顾
本文详细讲解了SpringMVC的核心内容:
1. 核心组件
- DispatcherServlet:前端控制器
- HandlerMapping:处理器映射器
- HandlerAdapter:处理器适配器
- ViewResolver:视图解析器
2. 处理流程
- 9个步骤的完整流程
- 每个步骤的详细说明
- 流程图和时序图展示
3. Controller开发
- 5种返回值类型
- 10种参数接收方式
- RESTful API开发
4. 高级特性
- 拦截器的使用
- 全局异常处理
- 文件上传下载
5. 面试题
- 7道高频面试题
- 详细的答案解析
- 代码示例说明
学习建议
- 理解核心流程:重点掌握9步处理流程
- 动手实践:创建项目,测试各种功能
- 阅读源码:深入理解SpringMVC实现原理
- 总结归纳:整理常用注解和配置
🎉 恭喜你完成学习!
现在你已经掌握了SpringMVC的核心知识!
💪 继续实践,不断精进!
📧 有问题欢迎交流 | ⭐ 觉得有用请点赞分享