SpringMVC处理流程完全指南:从请求到响应的完整旅程

掌握MVC架构 | 💡 精通处理流程 | 🔥 面试必备知识


📖 前言

💭 「理解SpringMVC的处理流程,就像理解一场精心编排的交响乐」

在Web开发中,SpringMVC作为最流行的MVC框架之一,其处理流程是每个Java开发者必须掌握的核心知识。本文将用最通俗易懂的方式,带你完整理解从用户发起请求到返回响应的整个过程。

想象一下:用户在浏览器输入网址按下回车,到页面显示出内容,这中间到底发生了什么?SpringMVC如何像一位高效的指挥家,协调各个组件完成这场演出?

🎯 学习目标

  • ✅ 深入理解SpringMVC的核心组件
  • ✅ 掌握完整的请求处理流程
  • ✅ 学会各种Controller的编写方式
  • ✅ 理解数据绑定和参数传递
  • ✅ 掌握视图解析机制
  • ✅ 应对面试高频考点

一、🎯 什么是SpringMVC?

1.1 MVC架构模式

💡 通俗解释:MVC就像餐厅的运作模式

想象你去餐厅吃饭:

  • Model(模型) = 厨房:负责准备数据(做菜)
  • View(视图) = 服务员端上来的菜品:负责展示数据(呈现给顾客)
  • Controller(控制器) = 服务员:负责接收请求并协调(接单、传达、上菜)

发起请求/点菜
调用
返回数据/做好的菜
选择视图
响应/上菜
用户/顾客
Controller/服务员
Model/厨房
View/菜品呈现

🌟 MVC的好处:

  1. 职责分离:各司其职,代码更清晰
  2. 易于维护:修改界面不影响业务逻辑
  3. 可重用性:模型和视图可以被多个控制器使用
  4. 并行开发:前端和后端可以同时开发

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>

注意事项:

  1. 表单必须是POST方式
  2. enctype必须是multipart/form-data
  3. 配置上传文件大小限制
  4. 验证文件类型和大小
  5. 生成唯一文件名避免冲突
  6. 处理上传异常

面试题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道高频面试题
  • 详细的答案解析
  • 代码示例说明

学习建议

  1. 理解核心流程:重点掌握9步处理流程
  2. 动手实践:创建项目,测试各种功能
  3. 阅读源码:深入理解SpringMVC实现原理
  4. 总结归纳:整理常用注解和配置

🎉 恭喜你完成学习!

现在你已经掌握了SpringMVC的核心知识!

💪 继续实践,不断精进!

📧 有问题欢迎交流 | ⭐ 觉得有用请点赞分享

相关推荐
瑶山2 小时前
Spring Cloud微服务搭建三、分布式任务调度XXL-JOB
java·spring cloud·微服务·xxljob
Re.不晚2 小时前
深入底层理解HashMap——妙哉妙哉的结构!!
java·哈希算法
Serene_Dream2 小时前
Java 内存区域
java·jvm
BYSJMG2 小时前
计算机毕设推荐:基于大数据的共享单车数据可视化分析
大数据·后端·python·信息可视化·数据分析·课程设计
爱吃山竹的大肚肚2 小时前
文件上传大小超过服务器限制
java·数据库·spring boot·mysql·spring
黄昏恋慕黎明2 小时前
测试模型讲解
java
瑞雪兆丰年兮2 小时前
[从0开始学Java|第十二天]学生管理系统升级
java·开发语言
弹简特2 小时前
【JavaSE-网络部分03】网络原理-泛泛介绍各个层次
java·开发语言·网络
周杰伦的稻香2 小时前
Hexo搭建教程
java·node.js