SpringMVC核心原理与实战全解析

目录

[Spring MVC 深度解析:从核心原理到企业级实战](#Spring MVC 深度解析:从核心原理到企业级实战)

[一、Spring MVC 的核心定位:为什么选择它?](#一、Spring MVC 的核心定位:为什么选择它?)

[1. 传统 Servlet 开发的痛点](#1. 传统 Servlet 开发的痛点)

[2. Spring MVC 的核心优势](#2. Spring MVC 的核心优势)

[3. Spring MVC 与 Spring Boot 的关系](#3. Spring MVC 与 Spring Boot 的关系)

[二、Spring MVC 核心原理:请求处理全流程](#二、Spring MVC 核心原理:请求处理全流程)

[1. 核心组件](#1. 核心组件)

[2. 请求处理完整流程](#2. 请求处理完整流程)

[3. 核心配置(非 Spring Boot 环境)](#3. 核心配置(非 Spring Boot 环境))

[三、Spring MVC 实战:从 0 到 1 开发 RESTful 接口](#三、Spring MVC 实战:从 0 到 1 开发 RESTful 接口)

[1. 环境搭建](#1. 环境搭建)

[(1)创建 Spring Boot 项目](#(1)创建 Spring Boot 项目)

[(2)Maven 依赖(pom.xml)](#(2)Maven 依赖(pom.xml))

[2. 核心开发](#2. 核心开发)

(1)定义实体类(User)

[(2)定义 Service 层(模拟业务逻辑)](#(2)定义 Service 层(模拟业务逻辑))

[(3)定义 Controller 层(核心请求处理)](#(3)定义 Controller 层(核心请求处理))

(4)全局异常处理(可选)

[3. 测试接口](#3. 测试接口)

[四、Spring MVC 高级特性](#四、Spring MVC 高级特性)

[1. 请求参数绑定的多种方式](#1. 请求参数绑定的多种方式)

[2. 拦截器(HandlerInterceptor)](#2. 拦截器(HandlerInterceptor))

(1)自定义拦截器

[(2)注册拦截器(Spring Boot 环境)](#(2)注册拦截器(Spring Boot 环境))

[3. 文件上传](#3. 文件上传)

(1)配置文件上传(application.yml)

(2)编写文件上传接口

[4. 跨域处理(CORS)](#4. 跨域处理(CORS))

(1)局部跨域(@CrossOrigin)

(2)全局跨域配置

五、常见问题与性能优化

[1. 常见问题解决方案](#1. 常见问题解决方案)

[(1)404 错误(请求路径找不到)](#(1)404 错误(请求路径找不到))

[(2)参数绑定失败(参数为 null)](#(2)参数绑定失败(参数为 null))

(3)跨域请求被拒绝

[2. 性能优化技巧](#2. 性能优化技巧)

(1)开启请求压缩

[(2)优化 DispatcherServlet 线程池](#(2)优化 DispatcherServlet 线程池)

(3)减少不必要的拦截器

(4)使用异步请求(@Async)

六、总结


Spring MVC 深度解析:从核心原理到企业级实战

Spring MVC 作为 Spring 框架的核心 Web 模块,是基于 MVC(Model-View-Controller)设计模式的轻量级 Web 开发框架,自诞生以来便成为 Java EE 领域主流的 Web 开发方案。它通过松耦合的组件设计、灵活的请求映射、强大的视图处理能力,完美适配从简单 REST 接口到复杂企业级 Web 应用的开发需求。本文将从核心定位、底层原理、核心组件、实战开发到高级特性,全面拆解这一框架的设计与使用,帮助开发者从 "会用" 到 "精通"。

一、Spring MVC 的核心定位:为什么选择它?

在深入原理前,首先明确 Spring MVC 的核心价值 ------ 它解决了传统 Servlet 开发的痛点,同时与 Spring 生态深度融合,成为 Java Web 开发的事实标准。

1. 传统 Servlet 开发的痛点

  • 配置繁琐:每个 Servlet 需在 web.xml 中注册,URL 映射、初始化参数等配置分散,维护成本高;
  • 代码耦合:Servlet 既处理请求参数解析,又处理业务逻辑,还需手动管理响应输出,职责混乱;
  • 功能单一:无内置参数绑定、数据校验、视图解析等能力,需手动封装工具类;
  • 扩展困难:难以集成 Spring 容器(如依赖注入、事务管理),无法复用 Spring 生态的核心能力。

2. Spring MVC 的核心优势

  • 松耦合设计:基于 MVC 分层思想,将请求处理、业务逻辑、视图渲染解耦,符合 "单一职责原则";
  • Spring 生态无缝集成:天然融入 Spring 容器,可直接使用 Spring 的依赖注入、AOP、事务管理等特性;
  • 强大的请求处理能力:支持灵活的 URL 映射、参数自动绑定、数据校验、文件上传等;
  • 多样化视图支持:不仅支持 JSP、Thymeleaf 等服务器端视图,也适配 RESTful 接口的 JSON/XML 响应;
  • 可扩展的拦截器机制:通过拦截器实现登录校验、日志记录、性能监控等通用功能;
  • 轻量级:核心依赖少,启动快,无侵入式设计,可与其他框架(如 MyBatis、Redis)灵活整合。

3. Spring MVC 与 Spring Boot 的关系

很多开发者易混淆两者的定位:

  • Spring MVC:是 "Web 开发框架",专注于请求处理、视图解析等 Web 层能力;
  • Spring Boot:是 "Spring 应用的快速开发脚手架",通过自动配置简化 Spring MVC 的配置(如无需手动配置 DispatcherServlet、视图解析器等)。

简言之:Spring Boot 是 "加速器",Spring MVC 是 "核心引擎"------Spring Boot 整合 Spring MVC 时,仅需引入 spring-boot-starter-web 依赖,即可自动完成 Spring MVC 的核心组件配置。

二、Spring MVC 核心原理:请求处理全流程

Spring MVC 的核心是 "前端控制器(DispatcherServlet)+ 组件化请求处理",理解请求从进入应用到响应返回的全流程,是掌握 Spring MVC 的关键。

1. 核心组件

Spring MVC 的请求处理依赖以下核心组件,各组件各司其职,通过 Spring 容器管理,可灵活替换:

组件名称 核心作用 关键实现
DispatcherServlet(前端控制器) 统一接收请求、分发请求、处理响应,是整个流程的 "中枢" 继承自 HttpServlet,由 Spring MVC 核心包提供
HandlerMapping(处理器映射器) 根据请求 URL 匹配对应的处理器(Controller 方法),返回 HandlerExecutionChain(包含处理器 + 拦截器) 常用实现:RequestMappingHandlerMapping(适配 @RequestMapping 注解)
HandlerAdapter(处理器适配器) 适配不同类型的处理器,调用处理器方法并返回 ModelAndView 常用实现:RequestMappingHandlerAdapter(适配注解式 Controller)
Handler(处理器) 即 Controller 中的请求处理方法,负责处理业务逻辑,返回数据 / 视图 通常由 @Controller + @RequestMapping 注解定义
ViewResolver(视图解析器) 将逻辑视图名解析为物理视图(如 JSP 路径、Thymeleaf 模板) 常用实现:InternalResourceViewResolver(JSP)、ThymeleafViewResolver(Thymeleaf)
HandlerInterceptor(处理器拦截器) 拦截请求处理流程,实现前置 / 后置处理、完成后处理 自定义实现 HandlerInterceptor 接口

2. 请求处理完整流程

以 "用户访问 http://localhost:8080/api/users/1 查询用户信息" 为例,拆解请求处理的 10 个核心步骤:

  1. 请求进入 DispatcherServlet:客户端请求被 Tomcat 等 Servlet 容器接收后,转发给 DispatcherServlet(所有请求的统一入口);
  2. DispatcherServlet 调用 HandlerMapping:DispatcherServlet 向 HandlerMapping 发起请求,根据 URL(/api/users/1)匹配对应的 Controller 方法;
  3. HandlerMapping 返回处理器链:HandlerMapping(如 RequestMappingHandlerMapping)解析 @RequestMapping("/api/users/{id}") 注解,匹配到 UserController.getById(Integer id) 方法,返回包含该方法 + 拦截器的 HandlerExecutionChain;
  4. DispatcherServlet 调用 HandlerAdapter:DispatcherServlet 根据处理器类型,选择对应的 HandlerAdapter(如 RequestMappingHandlerAdapter);
  5. HandlerAdapter 预处理请求:HandlerAdapter 完成参数绑定(如将 URL 中的 id=1 绑定到方法参数)、数据校验、文件解析等;
  6. HandlerAdapter 调用处理器方法:执行 UserController.getById(1) 方法,处理业务逻辑(如调用 Service 查询用户),返回 ModelAndView(或直接返回 JSON 数据);
  7. HandlerAdapter 返回结果给 DispatcherServlet:若返回 ModelAndView(包含逻辑视图名 + 数据模型),则进入视图解析阶段;若返回 JSON(如 @ResponseBody 注解),则直接处理响应;
  8. DispatcherServlet 调用 ViewResolver:ViewResolver 将逻辑视图名(如 userDetail)解析为物理视图(如 /WEB-INF/views/userDetail.jsp);
  9. View 渲染视图:View 对象(如 JstlView)结合数据模型渲染视图,生成 HTML 响应;
  10. DispatcherServlet 返回响应:将渲染后的响应(或 JSON 数据)返回给客户端,完成请求处理。

关键细节

  • 若 Controller 方法添加 @ResponseBody 注解(或使用 @RestController),则步骤 7-9 会跳过,HandlerAdapter 直接将返回值转换为 JSON/XML,通过 DispatcherServlet 返回;
  • 拦截器会在 "HandlerAdapter 调用处理器方法前"(preHandle)、"处理器方法执行后"(postHandle)、"响应返回后"(afterCompletion)触发,实现通用逻辑拦截。

3. 核心配置(非 Spring Boot 环境)

在传统 Spring MVC 项目(非 Spring Boot)中,需手动配置核心组件,核心配置文件 spring-mvc.xml 示例:

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 1. 扫描 Controller 所在包,注册 @Controller 注解的 Bean -->
    <context:component-scan base-package="com.example.controller"/>

    <!-- 2. 开启 MVC 注解驱动(自动注册 RequestMappingHandlerMapping、RequestMappingHandlerAdapter) -->
    <mvc:annotation-driven/>

    <!-- 3. 配置视图解析器(JSP) -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/> <!-- 视图前缀 -->
        <property name="suffix" value=".jsp"/> <!-- 视图后缀 -->
    </bean>

    <!-- 4. 配置静态资源映射(如 CSS/JS/图片),避免 DispatcherServlet 拦截 -->
    <mvc:resources mapping="/static/**" location="/static/"/>

    <!-- 5. 配置拦截器 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/api/**"/> <!-- 拦截 /api 下的所有请求 -->
            <bean class="com.example.interceptor.LogInterceptor"/> <!-- 自定义拦截器 -->
        </mvc:interceptor>
    </mvc:interceptors>
</beans>

同时需在 web.xml 中注册 DispatcherServlet:

XML 复制代码
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- 注册 DispatcherServlet -->
    <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 指定 Spring MVC 配置文件路径 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <!-- 启动时加载 DispatcherServlet -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- 映射所有请求到 DispatcherServlet -->
    <servlet-mapping>
        <servlet-name>spring-mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

而在 Spring Boot 环境中,上述配置全部由 spring-boot-starter-web 的自动配置类(如 WebMvcAutoConfiguration)完成,开发者无需手动编写 XML,仅需通过注解和配置文件微调即可。

三、Spring MVC 实战:从 0 到 1 开发 RESTful 接口

本节基于 Spring Boot 2.7.x(简化配置),开发一个完整的用户管理 RESTful 接口,覆盖请求映射、参数绑定、数据校验、响应处理等核心场景。

1. 环境搭建

(1)创建 Spring Boot 项目

通过 Spring Initializr(start.spring.io)创建项目,引入核心依赖:

  • Spring Web(整合 Spring MVC);
  • Lombok(简化 POJO 代码);
  • Validation(数据校验)。
(2)Maven 依赖(pom.xml)
XML 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.10</version>
    <relativePath/>
</parent>

<dependencies>
    <!-- Spring MVC 核心依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 数据校验 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <!-- 单元测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2. 核心开发

(1)定义实体类(User)

使用 Lombok 简化代码,添加 JSR 380 数据校验注解(如 @NotBlank@Min):

java 复制代码
package com.example.springmvc.entity;

import lombok.Data;
import javax.validation.constraints.*;
import java.time.LocalDateTime;

/**
 * 用户实体类
 */
@Data
public class User {
    private Integer id; // 主键ID

    @NotBlank(message = "用户名不能为空") // 非空校验
    @Size(min = 2, max = 20, message = "用户名长度必须在2-20之间")
    private String name; // 用户名

    @Min(value = 1, message = "年龄不能小于1") // 最小值校验
    @Max(value = 150, message = "年龄不能大于150")
    private Integer age; // 年龄

    @Email(message = "邮箱格式不正确") // 邮箱格式校验
    private String email; // 邮箱

    private LocalDateTime createTime; // 创建时间
}
(2)定义 Service 层(模拟业务逻辑)
java 复制代码
package com.example.springmvc.service;

import com.example.springmvc.entity.User;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 用户业务层
 */
@Service
public class UserService {
    // 模拟数据库存储
    private final ConcurrentHashMap<Integer, User> userMap = new ConcurrentHashMap<>();
    private final AtomicInteger idGenerator = new AtomicInteger(1);

    /**
     * 新增用户
     */
    public User save(User user) {
        int id = idGenerator.getAndIncrement();
        user.setId(id);
        user.setCreateTime(LocalDateTime.now());
        userMap.put(id, user);
        return user;
    }

    /**
     * 根据ID查询用户
     */
    public User getById(Integer id) {
        return userMap.get(id);
    }

    /**
     * 查询所有用户
     */
    public List<User> listAll() {
        return new ArrayList<>(userMap.values());
    }

    /**
     * 根据ID更新用户
     */
    public boolean update(Integer id, User user) {
        if (!userMap.containsKey(id)) {
            return false;
        }
        user.setId(id);
        user.setCreateTime(userMap.get(id).getCreateTime()); // 保留创建时间
        userMap.put(id, user);
        return true;
    }

    /**
     * 根据ID删除用户
     */
    public boolean delete(Integer id) {
        return userMap.remove(id) != null;
    }
}
(3)定义 Controller 层(核心请求处理)

使用 @RestController(= @Controller + @ResponseBody)定义 REST 接口,覆盖 GET/POST/PUT/DELETE 请求,添加参数校验、异常处理:

java 复制代码
package com.example.springmvc.controller;

import com.example.springmvc.entity.User;
import com.example.springmvc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 用户管理REST接口
 * 接口前缀:/api/users
 */
@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 新增用户:POST /api/users
     * @Valid:触发参数校验,BindingResult:接收校验结果
     */
    @PostMapping
    public ResponseEntity<?> save(@Valid @RequestBody User user, BindingResult bindingResult) {
        // 校验失败:返回错误信息
        if (bindingResult.hasErrors()) {
            String errorMsg = bindingResult.getFieldErrors().stream()
                    .map(error -> error.getField() + ": " + error.getDefaultMessage())
                    .collect(Collectors.joining("; "));
            return new ResponseEntity<>(errorMsg, HttpStatus.BAD_REQUEST);
        }
        // 校验成功:新增用户
        User savedUser = userService.save(user);
        return new ResponseEntity<>(savedUser, HttpStatus.CREATED);
    }

    /**
     * 根据ID查询用户:GET /api/users/{id}
     * @PathVariable:绑定URL路径参数
     */
    @GetMapping("/{id}")
    public ResponseEntity<User> getById(@PathVariable Integer id) {
        User user = userService.getById(id);
        if (user == null) {
            return ResponseEntity.notFound().build(); // 用户不存在,返回404
        }
        return ResponseEntity.ok(user);
    }

    /**
     * 查询所有用户:GET /api/users
     */
    @GetMapping
    public ResponseEntity<List<User>> listAll() {
        List<User> users = userService.listAll();
        return ResponseEntity.ok(users);
    }

    /**
     * 根据ID更新用户:PUT /api/users/{id}
     */
    @PutMapping("/{id}")
    public ResponseEntity<?> update(
            @PathVariable Integer id,
            @Valid @RequestBody User user,
            BindingResult bindingResult) {
        // 校验失败
        if (bindingResult.hasErrors()) {
            String errorMsg = bindingResult.getFieldErrors().stream()
                    .map(error -> error.getField() + ": " + error.getDefaultMessage())
                    .collect(Collectors.joining("; "));
            return new ResponseEntity<>(errorMsg, HttpStatus.BAD_REQUEST);
        }
        // 更新用户
        boolean success = userService.update(id, user);
        if (!success) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(userService.getById(id));
    }

    /**
     * 根据ID删除用户:DELETE /api/users/{id}
     */
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Integer id) {
        boolean success = userService.delete(id);
        if (!success) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.noContent().build(); // 删除成功,返回204
    }
}
(4)全局异常处理(可选)

上述代码中,参数校验的错误处理逻辑可通过 @ControllerAdvice+@ExceptionHandler 实现全局异常处理,简化 Controller 代码:

java 复制代码
package com.example.springmvc.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.stream.Collectors;

/**
 * 全局异常处理器
 */
@ControllerAdvice // 拦截所有@Controller/@RestController
public class GlobalExceptionHandler {

    /**
     * 处理参数校验异常
     */
    @ExceptionHandler(BindException.class)
    public ResponseEntity<String> handleBindException(BindException e) {
        String errorMsg = e.getFieldErrors().stream()
                .map(error -> error.getField() + ": " + error.getDefaultMessage())
                .collect(Collectors.joining("; "));
        return new ResponseEntity<>(errorMsg, HttpStatus.BAD_REQUEST);
    }

    /**
     * 处理其他异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
        return new ResponseEntity<>("服务器内部错误:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

添加全局异常处理器后,Controller 中的参数校验代码可简化为:

java 复制代码
@PostMapping
public ResponseEntity<User> save(@Valid @RequestBody User user) {
    User savedUser = userService.save(user);
    return new ResponseEntity<>(savedUser, HttpStatus.CREATED);
}

3. 测试接口

启动 Spring Boot 应用,使用 Postman/curl 测试接口:

  • 新增用户:POST http://localhost:8080/api/users,请求体 {"name":"张三","age":25,"email":"zhangsan@example.com"},返回新增用户信息;
  • 新增用户(参数校验失败):请求体 {"name":"张","age":0,"email":"zhangsan"},返回错误信息 name: 用户名长度必须在2-20之间; age: 年龄不能小于1; email: 邮箱格式不正确
  • 查询用户:GET http://localhost:8080/api/users/1,返回用户信息;
  • 其他接口测试逻辑类似。

四、Spring MVC 高级特性

1. 请求参数绑定的多种方式

Spring MVC 支持灵活的参数绑定,除了 @PathVariable@RequestBody,还有以下常用方式:

注解 / 方式 适用场景 示例
@RequestParam 绑定 URL 查询参数 @GetMapping("/search") public List<User> search(@RequestParam String name) → 访问 /api/users/search?name=张三
@RequestHeader 绑定请求头参数 @GetMapping("/header") public String getHeader(@RequestHeader("User-Agent") String userAgent)
@CookieValue 绑定 Cookie 参数 @GetMapping("/cookie") public String getCookie(@CookieValue("JSESSIONID") String jsessionId)
@ModelAttribute 绑定表单参数(POST 表单) @PostMapping("/form") public User saveForm(@ModelAttribute User user)
自动绑定 POJO 多个参数自动绑定到 POJO 若请求参数为 name=张三&age=25,方法参数为 User user,则自动绑定 user.setName("张三")user.setAge(25)

2. 拦截器(HandlerInterceptor)

拦截器可拦截请求处理流程,实现登录校验、日志记录、性能监控等通用功能。自定义拦截器需实现 HandlerInterceptor 接口:

(1)自定义拦截器
java 复制代码
package com.example.springmvc.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 日志拦截器:记录请求URL、处理时间
 */
@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    // 请求处理前执行(返回true:放行,false:拦截)
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 记录请求开始时间
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        // 记录请求信息
        log.info("请求开始:URL={}, Method={}", request.getRequestURL(), request.getMethod());
        return true; // 放行
    }

    // 请求处理后、视图渲染前执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("请求处理完成,准备渲染视图");
    }

    // 视图渲染后执行(响应返回后)
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 计算处理时间
        long startTime = (long) request.getAttribute("startTime");
        long costTime = System.currentTimeMillis() - startTime;
        // 记录响应信息
        log.info("请求结束:URL={}, Status={}, 耗时={}ms",
                request.getRequestURL(), response.getStatus(), costTime);
    }
}
(2)注册拦截器(Spring Boot 环境)

创建配置类,实现 WebMvcConfigurer 接口,注册拦截器:

java 复制代码
package com.example.springmvc.config;

import com.example.springmvc.interceptor.LogInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Spring MVC 配置类
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 注册拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册日志拦截器,拦截所有请求,排除静态资源
        registry.addInterceptor(new LogInterceptor())
                .addPathPatterns("/**") // 拦截所有路径
                .excludePathPatterns("/static/**"); // 排除静态资源路径
    }
}

启动应用后,访问任意接口,控制台会打印请求日志:

复制代码
请求开始:URL=http://localhost:8080/api/users/1, Method=GET
请求处理完成,准备渲染视图
请求结束:URL=http://localhost:8080/api/users/1, Status=200, 耗时=10ms

3. 文件上传

Spring MVC 支持单文件 / 多文件上传,只需配置文件上传解析器(Spring Boot 自动配置),即可快速实现:

(1)配置文件上传(application.yml)
复制代码
spring:
  servlet:
    multipart:
      max-file-size: 10MB # 单个文件最大大小
      max-request-size: 50MB # 单次请求最大文件大小
      enabled: true # 开启文件上传(默认开启)
(2)编写文件上传接口
java 复制代码
/**
 * 单文件上传:POST /api/upload
 */
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(
        @RequestParam("file") MultipartFile file, // 绑定上传文件
        @RequestParam("desc") String desc) { // 绑定额外参数
    // 判断文件是否为空
    if (file.isEmpty()) {
        return new ResponseEntity<>("文件不能为空", HttpStatus.BAD_REQUEST);
    }
    // 保存文件(示例:保存到项目根目录的 upload 目录)
    try {
        String uploadPath = System.getProperty("user.dir") + "/upload/";
        File dir = new File(uploadPath);
        if (!dir.exists()) {
            dir.mkdirs(); // 创建目录
        }
        // 保存文件
        String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
        File destFile = new File(uploadPath + fileName);
        file.transferTo(destFile);
        return ResponseEntity.ok("文件上传成功:" + destFile.getAbsolutePath());
    } catch (Exception e) {
        return new ResponseEntity<>("文件上传失败:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

/**
 * 多文件上传:POST /api/uploads
 */
@PostMapping("/uploads")
public ResponseEntity<String> uploadFiles(@RequestParam("files") MultipartFile[] files) {
    if (files == null || files.length == 0) {
        return new ResponseEntity<>("文件不能为空", HttpStatus.BAD_REQUEST);
    }
    // 遍历保存文件
    for (MultipartFile file : files) {
        if (file.isEmpty()) {
            continue;
        }
        try {
            String uploadPath = System.getProperty("user.dir") + "/upload/";
            File dir = new File(uploadPath);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
            File destFile = new File(uploadPath + fileName);
            file.transferTo(destFile);
        } catch (Exception e) {
            return new ResponseEntity<>("文件上传失败:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    return ResponseEntity.ok("多文件上传成功,共上传" + files.length + "个文件");
}

4. 跨域处理(CORS)

前后端分离项目中,前端页面与后端接口部署在不同域名下,会触发跨域问题,Spring MVC 提供 @CrossOrigin 注解或全局 CORS 配置解决:

(1)局部跨域(@CrossOrigin)

在 Controller / 方法上添加 @CrossOrigin 注解:

java 复制代码
// 整个Controller允许跨域
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:3000", maxAge = 3600) // 允许前端域名 http://localhost:3000 跨域
public class UserController {
    // ...
}

// 单个方法允许跨域
@GetMapping("/{id}")
@CrossOrigin(origins = "*") // 允许所有域名跨域
public ResponseEntity<User> getById(@PathVariable Integer id) {
    // ...
}
(2)全局跨域配置

在 WebMvcConfig 中配置全局 CORS 规则,替代局部注解:

java 复制代码
@Override
public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/api/**") // 对 /api 下的所有路径生效
            .allowedOrigins("http://localhost:3000") // 允许的前端域名
            .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方法
            .allowedHeaders("*") // 允许的请求头
            .allowCredentials(true) // 允许携带 Cookie
            .maxAge(3600); // 预检请求缓存时间(秒)
}

五、常见问题与性能优化

1. 常见问题解决方案

(1)404 错误(请求路径找不到)
  • 原因 1:Controller 的 @RequestMapping 路径配置错误(如拼写错误);
  • 原因 2:请求方法不匹配(如前端用 POST 请求,后端用 @GetMapping);
  • 原因 3:Spring Boot 的上下文路径配置错误(如配置 server.servlet.context-path=/demo,但请求未加 /demo 前缀);
  • 解决方案:检查路径拼写、请求方法、上下文路径,开启 Spring MVC 日志(logging.level.org.springframework.web=DEBUG)排查映射匹配情况。
(2)参数绑定失败(参数为 null)
  • 原因 1:请求参数名与方法参数名不匹配(如前端传 userName,后端参数为 name);
  • 原因 2:JSON 请求的 Content-Type 不是 application/json
  • 原因 3:POJO 类无无参构造器(JSON 解析器无法实例化);
  • 解决方案:使用 @RequestParam(name = "userName") 指定参数名,确保请求头 Content-Type 正确,POJO 类添加无参构造器(Lombok 的 @Data 默认包含)。
(3)跨域请求被拒绝
  • 原因:未配置 CORS 规则,或配置的允许域名 / 方法不匹配;
  • 解决方案:使用 @CrossOrigin 或全局 CORS 配置,确保 allowedOrigins 包含前端域名,allowedMethods 包含请求方法。

2. 性能优化技巧

(1)开启请求压缩

Spring MVC 支持 Gzip 压缩响应数据,减少网络传输量:

复制代码
server:
  compression:
    enabled: true # 开启压缩
    mime-types: application/json,text/html,text/css,application/javascript # 压缩的 MIME 类型
    min-response-size: 1024 # 响应数据最小大小(字节),超过才压缩
(2)优化 DispatcherServlet 线程池

Tomcat 的线程池参数直接影响请求处理能力,可通过以下配置优化:

复制代码
server:
  tomcat:
    threads:
      max: 200 # 最大线程数(根据 CPU 核心数调整,建议 CPU 核心数*2)
      min-spare: 50 # 最小空闲线程数
    max-connections: 10000 # 最大连接数
    accept-count: 100 # 连接队列长度
(3)减少不必要的拦截器

拦截器会增加请求处理耗时,需按需配置:

  • 仅对核心路径(如 /api/**)添加拦截器;
  • 排除静态资源、健康检查等无需拦截的路径;
  • 拦截器中避免执行耗时操作(如数据库查询)。
(4)使用异步请求(@Async)

对于耗时的请求(如调用第三方 API),可使用异步请求避免阻塞 Tomcat 线程:

java 复制代码
// 1. 开启异步支持
@SpringBootApplication
@EnableAsync
public class SpringMvcDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringMvcDemoApplication.class, args);
    }
}

// 2. Service 方法添加 @Async
@Service
public class UserService {
    @Async
    public CompletableFuture<User> getByIdAsync(Integer id) {
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return CompletableFuture.completedFuture(userMap.get(id));
    }
}

// 3. Controller 调用异步方法
@GetMapping("/async/{id}")
public CompletableFuture<ResponseEntity<User>> getByIdAsync(@PathVariable Integer id) {
    return userService.getByIdAsync(id)
            .thenApply(user -> {
                if (user == null) {
                    return ResponseEntity.notFound().build();
                }
                return ResponseEntity.ok(user);
            });
}

六、总结

Spring MVC 的核心是 "前端控制器 + 组件化请求处理",通过 DispatcherServlet 统一接收请求,再由 HandlerMapping、HandlerAdapter 等组件分工协作,实现灵活、松耦合的请求处理流程。它不仅解决了传统 Servlet 开发的痛点,还与 Spring 生态深度融合,成为 Java Web 开发的主流选择。

掌握 Spring MVC 的关键在于:

  1. 理解请求处理的全流程,熟悉核心组件的职责;
  2. 熟练使用 @RequestMapping@PathVariable@RequestBody 等注解实现参数绑定与请求映射;
  3. 掌握数据校验、拦截器、文件上传、跨域处理等高级特性;
  4. 结合性能优化技巧,确保应用在高并发场景下的稳定性。

无论是开发简单的 REST 接口,还是构建复杂的企业级 Web 应用,Spring MVC 都能通过灵活的配置和扩展能力,满足多样化的开发需求。而 Spring Boot 的自动配置进一步降低了 Spring MVC 的使用门槛,让开发者可以更专注于业务逻辑,而非繁琐的配置。

相关推荐
侠客行031710 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪10 小时前
深入浅出LangChain4J
java·langchain·llm
老毛肚12 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎13 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
Yvonne爱编码13 小时前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python
Re.不晚13 小时前
JAVA进阶之路——无奖问答挑战1
java·开发语言
你这个代码我看不懂13 小时前
@ConditionalOnProperty不直接使用松绑定规则
java·开发语言
fuquxiaoguang13 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
琹箐13 小时前
最大堆和最小堆 实现思路
java·开发语言·算法
__WanG13 小时前
JavaTuples 库分析
java