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 的使用门槛,让开发者可以更专注于业务逻辑,而非繁琐的配置。

相关推荐
秋邱2 小时前
Java基础语法核心:程序结构、注释规范、变量常量与数据类型
java·开发语言·spring cloud·tomcat·hibernate
故渊ZY2 小时前
SpringBoot与Redis实战:企业级缓存进阶指南
java·spring boot
廋到被风吹走2 小时前
【Spring】核心类研究价值排行榜
java·后端·spring
wanghowie2 小时前
01.05 Java基础篇|I/O、NIO 与序列化实战
java·开发语言·nio
孔明兴汉2 小时前
springboot4 项目从零搭建
java·java-ee·springboot
APIshop2 小时前
Java 爬虫 1688 评论 API 接口实战解析
java·开发语言·爬虫
编程乐学(Arfan开发工程师)2 小时前
信息收集与分析详解:渗透测试的侦察兵 (CISP-PTE 核心技能)
java·开发语言·javascript·python
Filotimo_2 小时前
在java开发中:JSON序列化和JSON反序列化
java·microsoft·json
czlczl200209252 小时前
SpringBoot实践:从验证码到业务接口的完整交互生命周期
java·spring boot·redis·后端·mysql·spring