全部笔记All notes

Spring MVC 框架完整指南

阅读 12m 46s12m 46s read

概述

Spring MVC 是 Spring Framework 的一个模块,是一个基于 MVC 架构模式的 Web 框架。它提供了一个灵活且功能强大的 Web 应用程序开发解决方案。

核心特性

  • MVC 架构:清晰的模型-视图-控制器分离
  • 灵活的配置:支持 XML 和注解配置
  • 强大的数据绑定:自动的请求参数绑定
  • 视图技术无关:支持多种视图技术
  • RESTful 支持:完整的 REST 风格API 支持

应用场景

场景描述示例
Web 应用开发传统的 Web 应用程序企业管理系统、电商平台
RESTful API构建 REST 风格的 API微服务接口、移动端 API
前后端分离作为后端 API 服务Vue/React + Spring MVC
企业级应用大型企业应用开发ERP、CRM 系统

💡 提示: Spring MVC 是 Spring Boot 的基础,掌握 Spring MVC 对理解 Spring Boot 至关重要。

环境准备

系统要求

工具版本说明
JDK1.8+Java 开发环境
Maven3.6+项目管理工具
Tomcat9.0+Servlet 容器
IDEA2020+集成开发环境

依赖配置

<dependencies>
    <!-- Spring MVC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.21</version>
    </dependency>
    
    <!-- Servlet API -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    
    <!-- JSP API -->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.3.3</version>
        <scope>provided</scope>
    </dependency>
    
    <!-- JSTL -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

核心组件

MVC 架构图

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│    View     │    │ Controller  │    │    Model    │
│   (视图)     │◀───│   (控制器)   │───▶│   (模型)     │
└─────────────┘    └─────────────┘    └─────────────┘
                          │
                          ▼
                   ┌─────────────┐
                   │   Service   │
                   │   (业务层)   │
                   └─────────────┘

核心组件说明

组件作用示例
DispatcherServlet前端控制器,统一处理请求请求分发和响应处理
HandlerMapping处理器映射器,找到对应的处理器URL 到 Controller 的映射
HandlerAdapter处理器适配器,调用具体处理器适配不同类型的 Controller
Controller控制器,处理业务逻辑用户请求处理
ViewResolver视图解析器,解析视图名称JSP、Thymeleaf 视图解析
View视图,展示数据JSP 页面、JSON 响应

请求处理流程

1. 用户请求 → 2. DispatcherServlet → 3. HandlerMapping → 4. HandlerAdapter
                     ↓                        ↓                    ↓
                5. Controller → 6. ModelAndView → 7. ViewResolver → 8. View
                     ↓                        ↓                    ↓
                9. 响应用户 ← 10. 渲染视图 ← 11. 视图对象 ← 12. 视图解析

项目搭建

1. 创建 Maven 项目

项目结构:

spring-mvc-demo/
├── pom.xml
├── src/
│   └── main/
│       ├── java/
│       │   └── com/
│       │       └── example/
│       │           └── controller/
│       │               └── FirstController.java
│       ├── resources/
│       │   └── springmvc-config.xml
│       └── webapp/
│           ├── WEB-INF/
│           │   ├── web.xml
│           │   └── jsp/
│           │       └── first.jsp
│           └── index.jsp
└── target/

2. 配置前端控制器

web.xml 配置:

<?xml version="1.0" encoding="UTF-8"?>
<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">
    
    <display-name>Spring MVC Demo</display-name>
    
    <!-- 配置前端控制器 -->
    <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>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <!-- 字符编码过滤器 -->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
</web-app>

🔧 配置说明:

  • DispatcherServlet: Spring MVC 的前端控制器
  • contextConfigLocation: 指定 Spring MVC 配置文件位置
  • load-on-startup: 设置为 1 表示启动时加载
  • url-pattern: 拦截所有请求(除了 JSP)

3. 创建控制器

FirstController.java:

package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
public class FirstController {
    
    @RequestMapping("/first")
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        
        // 添加数据到模型
        modelAndView.addObject("msg", "这是我的第一个 Spring MVC 程序");
        modelAndView.addObject("currentTime", new java.util.Date());
        
        // 设置视图名称
        modelAndView.setViewName("first");
        
        return modelAndView;
    }
}

传统 Controller 接口方式:

package com.example.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FirstController implements Controller {
    
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        
        // 添加数据
        modelAndView.addObject("msg", "这是我的第一个 Spring MVC 程序");
        
        // 设置视图路径
        modelAndView.setViewName("/WEB-INF/jsp/first.jsp");
        
        return modelAndView;
    }
}

💡 发展说明:

  • 传统方式: 实现 Controller 接口,需要在 XML 中配置
  • 注解方式: 使用 @Controller 注解,更加灵活和现代化

4. 创建视图文件

first.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<html>
<head>
    <title>Spring MVC 示例</title>
    <meta charset="UTF-8">
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 50px;
            background-color: #f5f5f5;
        }
        .container {
            background-color: white;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .message {
            color: #2c3e50;
            font-size: 18px;
            margin-bottom: 10px;
        }
        .time {
            color: #7f8c8d;
            font-size: 14px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Spring MVC 成功运行</h1>
        <div class="message">${msg}</div>
        <div class="time">
            当前时间:<fmt:formatDate value="${currentTime}" pattern="yyyy-MM-dd HH:mm:ss"/>
        </div>
    </div>
</body>
</html>

配置详解

1. Spring MVC 配置文件

springmvc-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
    <!-- 开启注解扫描 -->
    <context:component-scan base-package="com.example.controller"/>
    
    <!-- 开启 Spring MVC 注解支持 -->
    <mvc:annotation-driven/>
    
    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    
    <!-- 配置静态资源处理 -->
    <mvc:resources mapping="/static/**" location="/static/"/>
    
    <!-- 配置默认 servlet 处理器 -->
    <mvc:default-servlet-handler/>
    
</beans>

传统方式配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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">
    
    <!-- 配置控制器 -->
    <bean name="/firstController" class="com.example.controller.FirstController"/>
    
    <!-- 处理器映射器 -->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    
    <!-- 处理器适配器 -->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>
    
</beans>

2. 组件配置详解

组件作用配置方式
HandlerMappingURL 到 Controller 的映射BeanNameUrlHandlerMapping, RequestMappingHandlerMapping
HandlerAdapter适配不同类型的 ControllerSimpleControllerHandlerAdapter, RequestMappingHandlerAdapter
ViewResolver视图解析InternalResourceViewResolver, ThymeleafViewResolver
MultipartResolver文件上传处理CommonsMultipartResolver

3. 注解配置方式

JavaConfig 配置:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.controller")
public class WebConfig implements WebMvcConfigurer {
    
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/jsp/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("/static/");
    }
    
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

控制器开发

1. 注解式控制器

@Controller
@RequestMapping("/user")
public class UserController {
    
    // 处理 GET 请求
    @GetMapping("/list")
    public ModelAndView getUserList() {
        ModelAndView mv = new ModelAndView();
        
        // 模拟数据
        List<User> users = Arrays.asList(
            new User(1, "张三", "zhangsan@example.com"),
            new User(2, "李四", "lisi@example.com")
        );
        
        mv.addObject("users", users);
        mv.setViewName("user/list");
        
        return mv;
    }
    
    // 处理 POST 请求
    @PostMapping("/add")
    public String addUser(@ModelAttribute User user, Model model) {
        // 保存用户逻辑
        System.out.println("添加用户:" + user.getName());
        
        model.addAttribute("message", "用户添加成功");
        return "redirect:/user/list";
    }
    
    // 处理路径参数
    @GetMapping("/detail/{id}")
    public ModelAndView getUserDetail(@PathVariable Integer id) {
        ModelAndView mv = new ModelAndView();
        
        // 模拟查询用户
        User user = new User(id, "用户" + id, "user" + id + "@example.com");
        
        mv.addObject("user", user);
        mv.setViewName("user/detail");
        
        return mv;
    }
    
    // 处理请求参数
    @GetMapping("/search")
    public ModelAndView searchUsers(@RequestParam(value = "keyword", defaultValue = "") String keyword) {
        ModelAndView mv = new ModelAndView();
        
        // 模拟搜索逻辑
        List<User> users = keyword.isEmpty() ? 
            Collections.emptyList() : 
            Arrays.asList(new User(1, keyword, keyword + "@example.com"));
        
        mv.addObject("users", users);
        mv.addObject("keyword", keyword);
        mv.setViewName("user/search");
        
        return mv;
    }
}

2. RESTful 风格控制器

@RestController
@RequestMapping("/api/users")
public class UserRestController {
    
    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        List<User> users = Arrays.asList(
            new User(1, "张三", "zhangsan@example.com"),
            new User(2, "李四", "lisi@example.com")
        );
        
        return ResponseEntity.ok(users);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Integer id) {
        User user = new User(id, "用户" + id, "user" + id + "@example.com");
        return ResponseEntity.ok(user);
    }
    
    @PostMapping
    public ResponseEntity<String> createUser(@RequestBody User user) {
        // 保存用户逻辑
        System.out.println("创建用户:" + user.getName());
        
        return ResponseEntity.status(HttpStatus.CREATED)
                           .body("用户创建成功");
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<String> updateUser(@PathVariable Integer id, @RequestBody User user) {
        // 更新用户逻辑
        System.out.println("更新用户 " + id + ": " + user.getName());
        
        return ResponseEntity.ok("用户更新成功");
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<String> deleteUser(@PathVariable Integer id) {
        // 删除用户逻辑
        System.out.println("删除用户: " + id);
        
        return ResponseEntity.ok("用户删除成功");
    }
}

视图解析

1. JSP 视图解析器

<!-- JSP 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
</bean>

2. Thymeleaf 视图解析器

<!-- Thymeleaf 模板引擎 -->
<bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
    <property name="prefix" value="/WEB-INF/templates/"/>
    <property name="suffix" value=".html"/>
    <property name="templateMode" value="HTML"/>
    <property name="characterEncoding" value="UTF-8"/>
</bean>

<bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
    <property name="templateResolver" ref="templateResolver"/>
</bean>

<bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="templateEngine" ref="templateEngine"/>
    <property name="characterEncoding" value="UTF-8"/>
</bean>

3. JSON 视图解析器

@Controller
public class JsonController {
    
    @RequestMapping(value = "/api/user", produces = "application/json")
    @ResponseBody
    public User getUser() {
        return new User(1, "张三", "zhangsan@example.com");
    }
    
    // 或者使用 @RestController
    @GetMapping("/api/users")
    public List<User> getUsers() {
        return Arrays.asList(
            new User(1, "张三", "zhangsan@example.com"),
            new User(2, "李四", "lisi@example.com")
        );
    }
}

请求处理

1. 请求参数处理

@Controller
public class ParamController {
    
    // 单个参数
    @GetMapping("/param")
    public String handleParam(@RequestParam String name, Model model) {
        model.addAttribute("name", name);
        return "param";
    }
    
    // 可选参数
    @GetMapping("/optional")
    public String handleOptional(@RequestParam(value = "age", defaultValue = "18") Integer age, Model model) {
        model.addAttribute("age", age);
        return "optional";
    }
    
    // 多个参数
    @GetMapping("/multiple")
    public String handleMultiple(
            @RequestParam String name,
            @RequestParam Integer age,
            @RequestParam(required = false) String email,
            Model model) {
        
        model.addAttribute("name", name);
        model.addAttribute("age", age);
        model.addAttribute("email", email);
        
        return "multiple";
    }
    
    // 数组参数
    @GetMapping("/array")
    public String handleArray(@RequestParam String[] hobbies, Model model) {
        model.addAttribute("hobbies", Arrays.asList(hobbies));
        return "array";
    }
}

2. 路径参数处理

@Controller
@RequestMapping("/path")
public class PathController {
    
    // 单个路径参数
    @GetMapping("/user/{id}")
    public String getUser(@PathVariable Long id, Model model) {
        model.addAttribute("userId", id);
        return "user";
    }
    
    // 多个路径参数
    @GetMapping("/user/{id}/post/{postId}")
    public String getUserPost(
            @PathVariable Long id,
            @PathVariable Long postId,
            Model model) {
        
        model.addAttribute("userId", id);
        model.addAttribute("postId", postId);
        
        return "userPost";
    }
    
    // 正则表达式限制
    @GetMapping("/article/{id:\\d+}")
    public String getArticle(@PathVariable Integer id, Model model) {
        model.addAttribute("articleId", id);
        return "article";
    }
}

3. 请求头处理

@Controller
public class HeaderController {
    
    // 获取单个请求头
    @GetMapping("/header")
    public String handleHeader(@RequestHeader("User-Agent") String userAgent, Model model) {
        model.addAttribute("userAgent", userAgent);
        return "header";
    }
    
    // 获取所有请求头
    @GetMapping("/headers")
    public String handleHeaders(@RequestHeader Map<String, String> headers, Model model) {
        model.addAttribute("headers", headers);
        return "headers";
    }
    
    // Cookie 处理
    @GetMapping("/cookie")
    public String handleCookie(@CookieValue(value = "sessionId", defaultValue = "none") String sessionId, Model model) {
        model.addAttribute("sessionId", sessionId);
        return "cookie";
    }
}

数据绑定

1. 表单数据绑定

// 用户实体类
public class User {
    private Long id;
    private String name;
    private String email;
    private Integer age;
    private Date birthday;
    
    // 构造函数、getter 和 setter
}

@Controller
@RequestMapping("/form")
public class FormController {
    
    // 显示表单
    @GetMapping("/user")
    public String showForm(Model model) {
        model.addAttribute("user", new User());
        return "form/user";
    }
    
    // 处理表单提交
    @PostMapping("/user")
    public String submitForm(@ModelAttribute User user, BindingResult result, Model model) {
        
        // 验证数据
        if (user.getName() == null || user.getName().trim().isEmpty()) {
            result.rejectValue("name", "error.user.name.empty", "用户名不能为空");
        }
        
        if (result.hasErrors()) {
            return "form/user";
        }
        
        // 处理业务逻辑
        System.out.println("保存用户:" + user.getName());
        
        model.addAttribute("message", "用户保存成功");
        return "redirect:/form/user";
    }
}

2. JSON 数据绑定

@RestController
@RequestMapping("/api")
public class ApiController {
    
    // 接收 JSON 数据
    @PostMapping("/user")
    public ResponseEntity<String> createUser(@RequestBody User user) {
        
        // 数据验证
        if (user.getName() == null || user.getName().trim().isEmpty()) {
            return ResponseEntity.badRequest().body("用户名不能为空");
        }
        
        // 业务处理
        System.out.println("创建用户:" + user.getName());
        
        return ResponseEntity.ok("用户创建成功");
    }
    
    // 返回 JSON 数据
    @GetMapping("/user/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = new User();
        user.setId(id);
        user.setName("用户" + id);
        user.setEmail("user" + id + "@example.com");
        
        return ResponseEntity.ok(user);
    }
}

异常处理

1. 全局异常处理器

@ControllerAdvice
public class GlobalExceptionHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
    // 处理验证异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
        BindingResult result = ex.getBindingResult();
        
        Map<String, String> errors = new HashMap<>();
        result.getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage())
        );
        
        ErrorResponse errorResponse = new ErrorResponse("参数验证失败", errors);
        return ResponseEntity.badRequest().body(errorResponse);
    }
    
    // 处理业务异常
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
        logger.warn("业务异常: {}", ex.getMessage());
        
        ErrorResponse errorResponse = new ErrorResponse(ex.getMessage());
        return ResponseEntity.badRequest().body(errorResponse);
    }
    
    // 处理系统异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
        logger.error("系统异常", ex);
        
        ErrorResponse errorResponse = new ErrorResponse("系统内部错误");
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
    }
    
    // 处理 404 错误
    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFoundException(NoHandlerFoundException ex) {
        logger.warn("请求路径不存在: {}", ex.getRequestURL());
        
        ErrorResponse errorResponse = new ErrorResponse("请求的资源不存在");
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
    }
}

// 错误响应类
public class ErrorResponse {
    private String message;
    private Map<String, String> errors;
    private long timestamp;
    
    public ErrorResponse(String message) {
        this.message = message;
        this.timestamp = System.currentTimeMillis();
    }
    
    public ErrorResponse(String message, Map<String, String> errors) {
        this.message = message;
        this.errors = errors;
        this.timestamp = System.currentTimeMillis();
    }
    
    // getter 和 setter
}

RESTful API

1. RESTful 风格设计

@RestController
@RequestMapping("/api/v1/users")
public class UserRestController {
    
    @Autowired
    private UserService userService;
    
    // GET /api/v1/users - 获取所有用户
    @GetMapping
    public ResponseEntity<PageResult<User>> getUsers(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String keyword) {
        
        PageResult<User> result = userService.findUsers(page, size, keyword);
        return ResponseEntity.ok(result);
    }
    
    // GET /api/v1/users/{id} - 获取单个用户
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        return ResponseEntity.ok(user);
    }
    
    // POST /api/v1/users - 创建用户
    @PostMapping
    public ResponseEntity<User> createUser(@Valid @RequestBody CreateUserRequest request) {
        User user = userService.createUser(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(user);
    }
    
    // PUT /api/v1/users/{id} - 更新用户
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(
            @PathVariable Long id, 
            @Valid @RequestBody UpdateUserRequest request) {
        
        User user = userService.updateUser(id, request);
        return ResponseEntity.ok(user);
    }
    
    // DELETE /api/v1/users/{id} - 删除用户
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
    
    // GET /api/v1/users/{id}/posts - 获取用户的所有文章
    @GetMapping("/{id}/posts")
    public ResponseEntity<List<Post>> getUserPosts(@PathVariable Long id) {
        List<Post> posts = postService.findByUserId(id);
        return ResponseEntity.ok(posts);
    }
}

2. HTTP 状态码使用

@RestController
public class StatusCodeController {
    
    // 200 OK - 成功返回数据
    @GetMapping("/success")
    public ResponseEntity<String> success() {
        return ResponseEntity.ok("操作成功");
    }
    
    // 201 Created - 资源创建成功
    @PostMapping("/resource")
    public ResponseEntity<String> create() {
        return ResponseEntity.status(HttpStatus.CREATED).body("资源创建成功");
    }
    
    // 204 No Content - 成功但无内容返回
    @DeleteMapping("/resource/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        return ResponseEntity.noContent().build();
    }
    
    // 400 Bad Request - 请求参数错误
    @PostMapping("/validate")
    public ResponseEntity<String> validate(@RequestParam String param) {
        if (param == null || param.trim().isEmpty()) {
            return ResponseEntity.badRequest().body("参数不能为空");
        }
        return ResponseEntity.ok("验证成功");
    }
    
    // 404 Not Found - 资源不存在
    @GetMapping("/resource/{id}")
    public ResponseEntity<String> getResource(@PathVariable Long id) {
        if (id > 1000) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok("资源数据");
    }
    
    // 500 Internal Server Error - 服务器内部错误
    @GetMapping("/error")
    public ResponseEntity<String> error() {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                           .body("服务器内部错误");
    }
}

最佳实践

1. 控制器设计原则

  • 单一职责: 每个控制器只处理一个业务领域
  • 薄控制器: 控制器只负责请求处理,业务逻辑交给服务层
  • 统一返回: 使用统一的响应格式
  • 参数验证: 在控制器层进行参数验证

2. RESTful API 设计规范

// 好的做法
@RestController
@RequestMapping("/api/v1/users")
public class UserRestController {
    
    @GetMapping                    // GET /api/v1/users
    @PostMapping                   // POST /api/v1/users  
    @GetMapping("/{id}")           // GET /api/v1/users/123
    @PutMapping("/{id}")           // PUT /api/v1/users/123
    @DeleteMapping("/{id}")        // DELETE /api/v1/users/123
}

// 不好的做法
@RestController
public class BadController {
    
    @GetMapping("/getUser")        // 不符合 RESTful 风格
    @PostMapping("/createUser")    // URL 中包含动词
    @GetMapping("/deleteUser")     // 使用 GET 删除数据
}

3. 统一响应格式

// 统一响应类
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;
    private long timestamp;
    
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "成功", data);
    }
    
    public static <T> ApiResponse<T> error(int code, String message) {
        return new ApiResponse<>(code, message, null);
    }
}

@RestController
public class ApiController {
    
    @GetMapping("/api/users")
    public ApiResponse<List<User>> getUsers() {
        List<User> users = userService.findAll();
        return ApiResponse.success(users);
    }
    
    @PostMapping("/api/users")
    public ApiResponse<User> createUser(@RequestBody User user) {
        User createdUser = userService.create(user);
        return ApiResponse.success(createdUser);
    }
}

拦截器机制

1. 拦截器概述

Spring MVC 拦截器(Interceptor)是对 Controller 请求进行拦截,在请求处理前后进行特定操作的组件。类似于 Servlet 的 Filter,但更加灵活和强大。

拦截器与过滤器的区别

特性拦截器(Interceptor)过滤器(Filter)
依赖Spring MVC 框架Servlet 规范
拦截范围仅拦截 Controller 请求拦截所有请求
配置方式Spring 配置web.xml 配置
执行时机DispatcherServlet 内部Servlet 容器层
访问对象可以访问 Spring 容器不能访问 Spring 容器

2. 自定义拦截器

实现 HandlerInterceptor 接口

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class LoginInterceptor implements HandlerInterceptor {
    
    /**
     * 预处理方法,在 Controller 方法执行之前调用
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
        System.out.println("LoginInterceptor: preHandle 执行");
        
        // 获取请求路径
        String requestURI = request.getRequestURI();
        System.out.println("请求路径: " + requestURI);
        
        // 白名单:登录、注册等页面不需要拦截
        String[] allowUrls = {"/login", "/register", "/static/", "/error"};
        for (String url : allowUrls) {
            if (requestURI.contains(url)) {
                return true;
            }
        }
        
        // 检查用户是否已登录
        Object user = request.getSession().getAttribute("user");
        if (user != null) {
            System.out.println("用户已登录: " + user);
            return true; // 继续执行
        } else {
            System.out.println("用户未登录,重定向到登录页面");
            response.sendRedirect(request.getContextPath() + "/login");
            return false; // 中断执行
        }
    }
    
    /**
     * 后处理方法,在 Controller 方法执行之后,视图渲染之前调用
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        
        System.out.println("LoginInterceptor: postHandle 执行");
        
        if (modelAndView != null) {
            // 可以修改模型数据或视图名称
            modelAndView.addObject("currentTime", new java.util.Date());
            System.out.println("视图名称: " + modelAndView.getViewName());
        }
    }
    
    /**
     * 完成处理方法,在视图渲染完成后调用
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        
        System.out.println("LoginInterceptor: afterCompletion 执行");
        
        if (ex != null) {
            System.err.println("请求处理过程中发生异常: " + ex.getMessage());
        }
        
        // 清理资源,记录日志等
        long startTime = System.currentTimeMillis();
        Object startTimeAttr = request.getAttribute("startTime");
        if (startTimeAttr != null) {
            long endTime = System.currentTimeMillis();
            long executeTime = endTime - (Long) startTimeAttr;
            System.out.println("请求执行时间: " + executeTime + "ms");
        }
    }
}

3. 拦截器配置

XML 配置方式

<!-- springmvc-config.xml -->
<mvc:interceptors>
    <!-- 全局拦截器 -->
    <bean class="com.example.interceptor.LoggingInterceptor"/>
    
    <!-- 指定路径拦截器 -->
    <mvc:interceptor>
        <mvc:mapping path="/admin/**"/>
        <mvc:exclude-mapping path="/admin/login"/>
        <bean class="com.example.interceptor.AdminInterceptor"/>
    </mvc:interceptor>
    
    <!-- 登录拦截器 -->
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/login"/>
        <mvc:exclude-mapping path="/register"/>
        <mvc:exclude-mapping path="/static/**"/>
        <bean class="com.example.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

Java 配置方式

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        
        // 添加登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login", "/register", "/static/**", "/error");
        
        // 添加管理员拦截器
        registry.addInterceptor(new AdminInterceptor())
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/login");
        
        // 添加日志拦截器
        registry.addInterceptor(new LoggingInterceptor())
                .addPathPatterns("/**");
    }
}

4. 高级拦截器应用

权限控制拦截器

@Component
public class AuthorizationInterceptor implements HandlerInterceptor {
    
    @Autowired
    private UserService userService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
        // 检查是否是控制器方法
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        
        // 检查方法或类上是否有权限注解
        RequireRole requireRole = handlerMethod.getMethodAnnotation(RequireRole.class);
        if (requireRole == null) {
            requireRole = handlerMethod.getBeanType().getAnnotation(RequireRole.class);
        }
        
        if (requireRole == null) {
            return true; // 没有权限要求,直接通过
        }
        
        // 获取当前用户
        Object userObj = request.getSession().getAttribute("user");
        if (userObj == null) {
            response.sendError(HttpStatus.UNAUTHORIZED.value(), "用户未登录");
            return false;
        }
        
        User user = (User) userObj;
        String[] requiredRoles = requireRole.value();
        
        // 检查用户权限
        boolean hasPermission = userService.hasAnyRole(user.getId(), requiredRoles);
        if (!hasPermission) {
            response.sendError(HttpStatus.FORBIDDEN.value(), "权限不足");
            return false;
        }
        
        return true;
    }
}

// 权限注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
    String[] value();
}

// 使用示例
@Controller
@RequestMapping("/admin")
@RequireRole({"ADMIN"})
public class AdminController {
    
    @GetMapping("/users")
    @RequireRole({"ADMIN", "USER_MANAGER"})
    public String userList() {
        return "admin/users";
    }
}

高级异常处理

1. 全局异常处理器

@ControllerAdvice 注解

@ControllerAdvice
public class GlobalExceptionHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
    /**
     * 处理参数验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorResponse handleValidationException(MethodArgumentNotValidException ex) {
        
        logger.warn("参数验证失败", ex);
        
        BindingResult result = ex.getBindingResult();
        Map<String, String> errors = new HashMap<>();
        
        result.getFieldErrors().forEach(error -> {
            errors.put(error.getField(), error.getDefaultMessage());
        });
        
        return new ErrorResponse("参数验证失败", errors);
    }
    
    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorResponse handleBusinessException(BusinessException ex) {
        
        logger.warn("业务异常: {}", ex.getMessage());
        
        return new ErrorResponse(ex.getMessage());
    }
    
    /**
     * 处理系统异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorResponse handleGenericException(Exception ex) {
        
        logger.error("系统异常", ex);
        
        return new ErrorResponse("系统内部错误,请稍后重试");
    }
    
    /**
     * 处理 404 错误
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ResponseBody
    public ErrorResponse handleNotFoundException(NoHandlerFoundException ex) {
        
        logger.warn("请求路径不存在: {}", ex.getRequestURL());
        
        return new ErrorResponse("请求的资源不存在");
    }
}

// 错误响应类
public class ErrorResponse {
    private String message;
    private Map<String, String> errors;
    private long timestamp;
    
    public ErrorResponse(String message) {
        this.message = message;
        this.timestamp = System.currentTimeMillis();
    }
    
    public ErrorResponse(String message, Map<String, String> errors) {
        this.message = message;
        this.errors = errors;
        this.timestamp = System.currentTimeMillis();
    }
    
    // getters and setters
}

// 自定义业务异常
public class BusinessException extends RuntimeException {
    
    private int errorCode;
    
    public BusinessException(String message) {
        super(message);
    }
    
    public BusinessException(int errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }
    
    public int getErrorCode() {
        return errorCode;
    }
}

2. 统一响应格式

// 统一响应类
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;
    private long timestamp;
    
    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(200);
        response.setMessage("成功");
        response.setData(data);
        response.setTimestamp(System.currentTimeMillis());
        return response;
    }
    
    public static <T> ApiResponse<T> error(int code, String message) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(code);
        response.setMessage(message);
        response.setTimestamp(System.currentTimeMillis());
        return response;
    }
    
    // getters and setters
}

@RestController
public class ApiController {
    
    @GetMapping("/api/users")
    public ApiResponse<List<User>> getUsers() {
        List<User> users = userService.findAll();
        return ApiResponse.success(users);
    }
    
    @PostMapping("/api/users")
    public ApiResponse<User> createUser(@Valid @RequestBody User user) {
        User createdUser = userService.create(user);
        return ApiResponse.success(createdUser);
    }
}

数据验证与转换

1. 数据验证

使用 Bean Validation

// 用户实体类
public class User {
    
    @NotNull(message = "用户ID不能为空")
    private Long id;
    
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
    private String username;
    
    @Email(message = "邮箱格式不正确")
    @NotBlank(message = "邮箱不能为空")
    private String email;
    
    @Range(min = 18, max = 100, message = "年龄必须在18-100之间")
    private Integer age;
    
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
    
    @Past(message = "生日必须是过去的日期")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;
    
    // constructors, getters and setters
}

// 控制器中使用验证
@Controller
public class UserController {
    
    @PostMapping("/users")
    public String createUser(@Valid @ModelAttribute User user, BindingResult result, Model model) {
        
        if (result.hasErrors()) {
            // 处理验证错误
            model.addAttribute("user", user);
            return "user/form";
        }
        
        // 保存用户
        userService.save(user);
        return "redirect:/users";
    }
    
    @PostMapping("/api/users")
    @ResponseBody
    public ApiResponse<User> createUserApi(@Valid @RequestBody User user) {
        User savedUser = userService.save(user);
        return ApiResponse.success(savedUser);
    }
}

2. 自定义验证器

// 自定义验证注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdCardValidator.class)
public @interface IdCard {
    String message() default "身份证号格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// 验证器实现
public class IdCardValidator implements ConstraintValidator<IdCard, String> {
    
    @Override
    public void initialize(IdCard constraintAnnotation) {
        // 初始化
    }
    
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null || value.trim().isEmpty()) {
            return true; // 由 @NotBlank 等注解处理空值
        }
        
        // 身份证号验证逻辑
        return isValidIdCard(value);
    }
    
    private boolean isValidIdCard(String idCard) {
        // 简化的身份证验证逻辑
        return idCard.matches("^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$");
    }
}

// 使用自定义验证
public class Person {
    
    @IdCard
    private String idCard;
    
    // getters and setters
}

3. 数据转换

自定义类型转换器

// 字符串转日期转换器
@Component
public class StringToDateConverter implements Converter<String, Date> {
    
    private static final String[] DATE_PATTERNS = {
        "yyyy-MM-dd",
        "yyyy/MM/dd",
        "yyyy-MM-dd HH:mm:ss"
    };
    
    @Override
    public Date convert(String source) {
        if (source == null || source.trim().isEmpty()) {
            return null;
        }
        
        for (String pattern : DATE_PATTERNS) {
            try {
                SimpleDateFormat sdf = new SimpleDateFormat(pattern);
                return sdf.parse(source);
            } catch (ParseException e) {
                // 继续尝试下一个格式
            }
        }
        
        throw new IllegalArgumentException("无法解析日期格式: " + source);
    }
}

// 配置转换器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToDateConverter());
    }
}

文件上传下载

1. 文件上传

配置文件上传

<!-- 配置文件上传解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 设置上传文件总大小上限,单位字节 -->
    <property name="maxUploadSize" value="10485760"/> <!-- 10MB -->
    <!-- 设置单个文件大小上限 -->
    <property name="maxUploadSizePerFile" value="5242880"/> <!-- 5MB -->
    <!-- 设置内存临界值 -->
    <property name="maxInMemorySize" value="4096"/>
    <!-- 设置默认编码 -->
    <property name="defaultEncoding" value="UTF-8"/>
</bean>

文件上传控制器

@Controller
@RequestMapping("/file")
public class FileController {
    
    private static final String UPLOAD_DIR = "/uploads/";
    
    @GetMapping("/upload")
    public String uploadForm() {
        return "file/upload";
    }
    
    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file, Model model) {
        
        if (file.isEmpty()) {
            model.addAttribute("error", "请选择要上传的文件");
            return "file/upload";
        }
        
        try {
            // 检查文件类型
            String contentType = file.getContentType();
            if (!isAllowedFileType(contentType)) {
                model.addAttribute("error", "不支持的文件类型");
                return "file/upload";
            }
            
            // 生成文件名
            String originalFilename = file.getOriginalFilename();
            String fileName = generateFileName(originalFilename);
            
            // 保存文件
            File uploadDir = new File(UPLOAD_DIR);
            if (!uploadDir.exists()) {
                uploadDir.mkdirs();
            }
            
            File dest = new File(uploadDir, fileName);
            file.transferTo(dest);
            
            model.addAttribute("message", "文件上传成功");
            model.addAttribute("fileName", fileName);
            
        } catch (IOException e) {
            logger.error("文件上传失败", e);
            model.addAttribute("error", "文件上传失败");
        }
        
        return "file/upload";
    }
    
    @PostMapping("/upload/multiple")
    public String uploadMultipleFiles(@RequestParam("files") MultipartFile[] files, Model model) {
        
        List<String> uploadedFiles = new ArrayList<>();
        List<String> errors = new ArrayList<>();
        
        for (MultipartFile file : files) {
            if (!file.isEmpty()) {
                try {
                    String fileName = generateFileName(file.getOriginalFilename());
                    File dest = new File(UPLOAD_DIR, fileName);
                    file.transferTo(dest);
                    uploadedFiles.add(fileName);
                } catch (IOException e) {
                    errors.add("文件 " + file.getOriginalFilename() + " 上传失败");
                }
            }
        }
        
        model.addAttribute("uploadedFiles", uploadedFiles);
        model.addAttribute("errors", errors);
        
        return "file/upload";
    }
    
    private boolean isAllowedFileType(String contentType) {
        String[] allowedTypes = {"image/jpeg", "image/png", "image/gif", "application/pdf", "text/plain"};
        return Arrays.asList(allowedTypes).contains(contentType);
    }
    
    private String generateFileName(String originalFilename) {
        String extension = "";
        int dotIndex = originalFilename.lastIndexOf('.');
        if (dotIndex > 0) {
            extension = originalFilename.substring(dotIndex);
        }
        return System.currentTimeMillis() + "_" + UUID.randomUUID().toString() + extension;
    }
}

2. 文件下载

@Controller
@RequestMapping("/file")
public class FileDownloadController {
    
    private static final String UPLOAD_DIR = "/uploads/";
    
    @GetMapping("/download/{fileName}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
        
        try {
            File file = new File(UPLOAD_DIR, fileName);
            if (!file.exists()) {
                return ResponseEntity.notFound().build();
            }
            
            Resource resource = new FileSystemResource(file);
            
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
                    .header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
                    .body(resource);
                    
        } catch (Exception e) {
            logger.error("文件下载失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
    
    @GetMapping("/view/{fileName}")
    public ResponseEntity<Resource> viewFile(@PathVariable String fileName) {
        
        try {
            File file = new File(UPLOAD_DIR, fileName);
            if (!file.exists()) {
                return ResponseEntity.notFound().build();
            }
            
            Resource resource = new FileSystemResource(file);
            String contentType = Files.probeContentType(file.toPath());
            
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_TYPE, contentType)
                    .body(resource);
                    
        } catch (Exception e) {
            logger.error("文件查看失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}

常见问题

问题1:404 错误

现象: 访问控制器时出现 404 错误 原因:

  • URL 映射配置错误
  • 控制器没有被扫描到
  • DispatcherServlet 配置错误

解决方案:

<!-- 检查 web.xml 配置 -->
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>  <!-- 正确配置 -->
</servlet-mapping>

<!-- 检查组件扫描 -->
<context:component-scan base-package="com.example.controller"/>

问题2:中文乱码

现象: 返回的中文内容出现乱码 解决方案:

<!-- 字符编码过滤器 -->
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

问题3:静态资源无法访问

现象: CSS、JS、图片等静态资源 404 解决方案:

<!-- 配置静态资源处理 -->
<mvc:resources mapping="/static/**" location="/static/"/>
<mvc:default-servlet-handler/>

问题4:@RequestBody 不生效

现象: 无法接收 JSON 数据 解决方案:

<!-- 开启 MVC 注解支持 -->
<mvc:annotation-driven/>

<!-- 或者手动配置 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>

问题5:跨域问题

现象: 前端调用 API 时出现跨域错误 解决方案:

// 全局跨域配置
@Configuration
public class CorsConfig {
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

// 或者使用注解
@CrossOrigin(origins = "http://localhost:3000")
@RestController
public class ApiController {
    // ...
}

⚠️ 注意: 在生产环境中,请使用 HTTPS,并配置合适的安全策略。

相关文章

前后章节导航

Spring 基础系列

SpringBoot 现代化开发

数据库技术

中间件技术

运维部署

开发工具

  • IDEA 使用技巧
  • Maven 项目管理

总结

Spring MVC 是 Java Web 开发的经典框架,本指南涵盖了:

🎯 核心内容

  • 环境搭建: Maven + Spring MVC + Tomcat 开发环境
  • 核心组件: DispatcherServlet、HandlerMapping、Controller、ViewResolver
  • 配置方式: XML 配置和注解配置
  • 请求处理: 参数绑定、数据验证、异常处理

🛠️ 实现方式

  • 传统方式: 实现 Controller 接口,XML 配置
  • 注解方式: @Controller、@RequestMapping 等注解
  • RESTful API: @RestController、HTTP 方法映射

📚 实践应用

  • Web 应用开发: 传统的 MVC 架构 Web 应用
  • API 服务: RESTful 风格的 API 接口开发
  • 前后端分离: 作为后端 API 服务提供者