Article

Spring MVC 文档

更新于:2026-07-15

第 1 章 Spring MVC 概述

1.1 什么是 MVC 设计模式

概念名称说明注意事项
Model(模型)负责封装应用程序的数据和业务逻辑,通常由 Java Bean 或 Service 层实现。应保持与视图和控制器的解耦,避免直接依赖 UI 逻辑。
View(视图)负责数据的展示,将模型中的数据以用户友好的方式呈现,如 JSP、Thymeleaf 页面等。视图不应包含复杂的业务逻辑,仅用于数据显示和用户交互界面渲染。
Controller(控制器)负责接收用户请求,调用模型处理业务,并选择合适的视图进行结果展示。控制器是 Model 与 View 之间的协调者,应尽量保持轻量,不包含核心业务逻辑。
MVC 优势实现关注点分离,提高代码可维护性、可测试性和可扩展性。初学者可能因过度拆分而导致类数量增多,需合理设计模块边界。

1.2 Spring MVC 框架简介

概念名称说明注意事项
Spring MVC基于 Java 的轻量级 Web 框架,是 Spring Framework 的一部分,用于构建 Web 应用程序。需要理解其与 Servlet API 的集成关系,底层依赖于 Java EE 的 Servlet 规范。
设计目标提供清晰的 MVC 实现,支持灵活的 URL 映射、数据绑定、视图解析和异常处理机制。框架功能丰富,初学者应循序渐进,避免一次性掌握所有特性。
松耦合性通过依赖注入(DI)实现组件间的解耦,便于单元测试和替换实现。推荐使用接口编程,增强系统的可扩展性和可维护性。
可扩展性支持自定义组件(如 Converter、Validator、Interceptor),易于扩展功能。自定义组件需遵循 Spring 的生命周期和配置方式,避免破坏框架结构。

1.3 Spring MVC 的核心组件

组件名称说明注意事项
DispatcherServlet前端控制器,接收所有 HTTP 请求,协调各组件完成请求处理。是整个流程的入口点,需在 web.xml 或通过 Java 配置正确注册。
HandlerMapping根据请求 URL 查找对应的处理器(Controller 方法)。支持多种映射策略(如注解、XML 配置),常用的是 RequestMappingHandlerMapping。
HandlerAdapter调用具体处理器方法,并处理参数绑定、返回值解析等。开发者通常无需直接操作,由框架自动选择适配器。
Controller处理具体业务逻辑的类,通常使用 @Controller 注解标记。方法通过 @RequestMapping 等注解映射请求,返回 ModelAndView 或响应数据。
ModelAndView封装模型数据和视图信息的对象,用于传递数据并指定跳转视图。在返回视图时常用,若使用 @ResponseBody 则不适用。
ViewResolver将逻辑视图名解析为实际的视图对象(如 JSP、Thymeleaf 模板)。需正确配置前缀和后缀,确保能找到对应页面文件。
View实际渲染页面的组件,如 JSP、Thymeleaf、FreeMarker 等。视图负责展示数据,不应包含业务逻辑。
HandlerExceptionResolver全局异常处理组件,捕获并处理请求处理过程中的异常。可自定义异常处理器,统一返回错误页面或 JSON 错误信息。

1.4 Spring MVC 的工作流程

步骤编号流程阶段说明注意事项
1用户发起请求用户在浏览器输入 URL 或提交表单,发送 HTTP 请求到服务器。请求包含 URL、请求方法(GET/POST)、参数等信息。
2DispatcherServlet 接收请求所有请求首先由 DispatcherServlet 接收,作为前端控制器统一入口。必须配置为拦截特定路径(如 /),否则无法接管请求。
3HandlerMapping 查找处理器DispatcherServlet 查询 HandlerMapping,根据 URL 找到对应的 Controller 方法。多个映射规则时,按优先级匹配最合适的处理器。
4HandlerAdapter 调用处理器HandlerAdapter 执行目标方法,完成参数绑定、数据转换、调用业务逻辑等。参数绑定失败会抛出异常,需配置合适的转换器或校验机制。
5Controller 返回结果处理完成后返回 ModelAndView 对象(或 @ResponseBody 数据)。若返回 void 或 String 且无 @ResponseBody,默认视为逻辑视图名。
6ViewResolver 解析视图根据逻辑视图名(如 “home”),ViewResolver 查找实际视图(如 “/WEB-INF/jsp/home.jsp”)。需正确配置视图解析器的前缀和后缀,避免视图找不到错误。
7View 渲染响应视图使用模型数据生成 HTML 页面,填充动态内容并输出到响应流。静态资源(CSS/JS)需单独配置路径,避免被 DispatcherServlet 拦截。
8返回响应给客户端DispatcherServlet 将渲染后的 HTML 页面返回给浏览器显示。若为 REST API,直接返回 JSON/XML 数据,无需视图渲染。

第 2 章 开发环境搭建与项目初始化

2.1 环境准备(JDK、Maven、IDE)

工具名称说明注意事项
JDKJava 开发工具包,Spring MVC 5.x 建议使用 JDK 8 或更高版本。需正确配置 JAVA_HOME 环境变量,并确保命令行可执行 java 和 javac 命令。
Maven项目构建与依赖管理工具,用于自动下载库文件、编译、打包 Web 应用。建议使用 Maven 3.6+ 版本;配置阿里云镜像可加速依赖下载。
IDE集成开发环境,推荐使用 IntelliJ IDEA 或 Eclipse。IDEA 对 Spring 支持更友好;Eclipse 需安装 Spring Tools Suite 插件增强功能。
Servlet 容器如 Tomcat 9+,用于运行 Java Web 应用程序。Tomcat 需与 Servlet API 版本匹配(如 Tomcat 9 支持 Servlet 4.0)。

2.2 创建 Maven Web 项目

方法/步骤说明注意事项
使用 Maven 原型创建执行 mvn archetype:generate 命令,选择 maven-archetype-webapp 模板。该模板生成基础 Web 结构,但默认无 src/main/java 目录,需手动创建。
目录结构标准src/main/java(Java 类)、src/main/resources(配置文件)、src/main/webapp(Web 资源)必须符合 Maven 标准目录结构,否则编译或部署会失败。
pom.xml 配置添加 Spring MVC、Servlet API 等依赖项。servlet-api 依赖应设置 provided,避免与服务器冲突。
导入 IDE在 IDE 中导入 Maven 项目,自动识别模块结构和依赖。导入后检查是否成功加载依赖库,避免 ClassNotFoundException。

2.3 配置 web.xml

元素/属性说明注意事项
<web-app>根元素,所有配置必须位于此标签内,声明为 Web 应用描述符。版本需与 Servlet 规范一致(如 version=“4.0” 对应 Servlet 4.0)。
<display-name>设置 Web 应用显示名称,用于管理界面识别。可选,不影响功能。
<servlet>声明一个 Servlet 组件,此处用于注册 DispatcherServlet。servlet-name 用于唯一标识,后续映射引用此名称。
<servlet-name>定义 Servlet 名称,如 spring-mvc-dispatcher。名称在当前应用中必须唯一。
<servlet-class>指定 Servlet 实现类,通常为 org.springframework.web.servlet.DispatcherServlet。必须完整类名,拼写错误将导致初始化失败。
<init-param>初始化参数,用于传递配置文件位置等信息。param-name 和 param-value 成对出现,常见参数为 contextConfigLocation。
<load-on-startup>设置 Servlet 启动时加载顺序,值为整数,越小越早加载。推荐设为 1,确保应用启动时初始化 Spring 容器。
<servlet-mapping>将 Servlet 映射到 URL 路径。url-pattern 通常设为 /,表示拦截所有请求(静态资源需特殊处理)。
<welcome-file-list>指定欢迎页列表,如 index.jsp。当访问根路径时,默认跳转到第一个存在的欢迎页面。

2.4 配置 Spring MVC 核心配置文件(spring-mvc.xml)

配置项/标签说明注意事项
<beans>根标签,所有 Spring 配置必须在此命名空间下定义。需包含必要的 XML 命名空间(如 mvc, context, beans)。
xmlns:mvc启用 Spring MVC 命名空间,支持 <mvc:annotation-driven/> 等快捷配置。必须引入:http://www.springframework.org/schema/mvc
xmlns:context启用上下文命名空间,支持组件扫描等功能。必须引入:http://www.springframework.org/schema/context
context:component-scan自动扫描指定包下的注解类(如 @Controller)。base-package 属性指定扫描范围,避免扫描过多包影响性能。
<bean>注册 Spring Bean,如视图解析器、消息转换器等。class 属性为全限定类名,可通过 id 或 name 引用。
mvc:annotation-driven/启用注解驱动支持,自动注册 HandlerMapping、HandlerAdapter 等组件。必须添加,否则 @RequestMapping 等注解不生效。
InternalResourceViewResolver配置内部资源视图解析器,用于 JSP 页面解析。需设置 prefix(前缀)和 suffix(后缀),如 /WEB-INF/jsp/ 和 .jsp。
prefix设置视图路径前缀,如 “/WEB-INF/jsp/“。路径必须以 / 开头,且目标目录存在于 webapp 下。
suffix设置视图路径后缀,如 “.jsp”。与 prefix 结合使用,最终视图为 prefix + 逻辑名 + suffix。

2.5 配置 DispatcherServlet

配置方式说明注意事项
web.xml 中声明使用 <servlet><servlet-mapping> 注册 DispatcherServlet 并映射路径。必须配置 init-param 指定 spring-mvc.xml 位置,否则无法加载 Spring 上下文。
初始化参数 contextConfigLocation指定 Spring MVC 配置文件路径,如 classpath:spring-mvc.xml 或 /WEB-INF/spring-mvc.xml。若未指定,默认查找 [servlet-name]-servlet.xml(如 dispatcher-servlet.xml)。
url-pattern 设置常见值为 ”/“,表示拦截所有请求(除 .jsp 外)。”/” 会覆盖默认 Servlet,静态资源需通过 mvc:resources 显式放行。
load-on-startup控制 DispatcherServlet 在 Web 应用启动时是否立即初始化。设为 1 表示随应用启动加载,避免首次请求延迟。
Java 类配置替代方案可通过实现 WebApplicationInitializer 接口编程式注册 DispatcherServlet。适用于完全基于 Java 配置的项目,无需 web.xml。
多 DispatcherServlet可配置多个 DispatcherServlet 处理不同模块(如前台/后台)。每个 Servlet 拥有独立的 Spring 上下文,需分别配置对应的配置文件。

第 3 章 控制器(Controller)基础

3.1 @Controller 注解的使用

概念/属性说明注意事项
@Controller标记一个类为 Spring MVC 的控制器,使其能处理 HTTP 请求。必须配合 @ComponentScan 或 context:component-scan 使用才能被 Spring 容器管理。
组件注册被 @Controller 标注的类会被 Spring 自动注册为 Bean。无需再添加 @Component,@Controller 本身带有 @Component 元注解。
作用范围通常应用于类级别,不用于方法或字段。方法级别的请求映射通过 @RequestMapping 等注解实现。
与 @Service 区别@Controller 专用于 Web 层控制器;@Service 用于业务逻辑层。分层清晰有助于维护和测试,避免在 Controller 中写复杂业务逻辑。
自定义 Bean 名称可指定名称,如 @Controller(“userController”),否则默认为类名首字母小写。若不指定,UserHomeController -> userHomeController。

3.2 @RequestMapping 注解详解

属性名称说明注意事项
value指定请求映射的 URL 路径,可为单个或多个。如 value = “/home” 或 value = {”/”, “/index”}。
path与 value 等价,用于指定映射路径。推荐使用 path,语义更清晰。
method限制请求的 HTTP 方法类型,如 RequestMethod.GET、POST 等。不设置则匹配所有方法,存在安全风险,建议明确指定。
params指定请求必须包含(或不包含)的参数条件。如 params = “role=admin” 表示只有 role=admin 时才匹配。
headers限制请求头必须满足的条件。如 headers = “Content-Type=application/json”。
consumes指定请求体内容类型(Content-Type),用于限制请求数据格式。如 consumes = “application/json”。
produces指定响应内容类型(Accept),用于内容协商。如 produces = “application/json;charset=UTF-8”。
name为映射指定一个名称,可用于程序化查找。较少使用,主要用于高级场景。

3.3 请求映射方法的参数绑定基础

参数类型说明注意事项
HttpServletRequest原生 Servlet 请求对象,可获取所有请求信息。可直接作为方法参数注入,Spring 自动提供实例。
HttpServletResponse原生 Servlet 响应对象,用于写入响应数据。可直接注入,但应优先使用 Model 或 @ResponseBody 简化开发。
HttpSession会话对象,用于存储用户会话数据。注入后可操作 session.setAttribute/getAttribute。
java.util.Locale获取当前请求的区域信息,用于国际化。需配置 LocaleResolver 才能正确获取。
InputStream / Reader获取请求体原始输入流。通常用于处理文件上传或自定义格式数据,读取后流将关闭。
OutputStream / Writer获取响应输出流,用于直接写入响应内容。如返回文件或自定义二进制数据时使用。
@PathVariable绑定 URL 路径中的变量,配合 @RequestMapping 中的占位符使用。需与路径中 {xxx} 对应,详见 4.3 小节。
@RequestParam绑定请求参数(query string 或 form data)。非 POJO 类型参数默认按此规则绑定,详见 4.4 小节。
@RequestBody将请求体 JSON/XML 数据绑定到 Java 对象。需配合 HttpMessageConverter 使用,常用于 REST API。
Model / ModelMap用于向视图传递数据。方法参数注入后调用 addAttribute 可传递数据至 JSP 等视图。

第 4 章 请求处理与映射

4.1 @RequestMapping 路径映射

语法/示例说明注意事项
@RequestMapping(“/home”)映射到单一路径 /home。大小写敏感,/Home 不匹配。
@RequestMapping({”/”, “/index”})映射到多个路径,任一匹配即可。使用数组形式定义多个路径。
@RequestMapping(“/user/info”)多级路径映射,需完整匹配。路径前后的 / 可省略,框架会自动处理。
类级别路径 + 方法级别路径类上 @RequestMapping(“/user”),方法上 @RequestMapping(“/list”) → 最终路径为 /user/list。支持路径组合,便于模块化设计。
Ant 风格路径:/user/*/create* 匹配一层路径,如 /user/123/create。不能跨层级,* 不匹配 /
Ant 风格路径:/user/**/create** 匹配多层路径,如 /user/role/admin/create。** 可匹配任意深度的子路径。
Ant 风格路径:/user/??.action? 匹配单个字符,如 /user/id.action。? 表示任意一个字符。

4.2 请求方法限制(GET、POST 等)

method 属性值说明注意事项
method = RequestMethod.GET仅处理 GET 请求,常用于查询操作。浏览器书签、链接默认为 GET,注意防止重复提交。
method = RequestMethod.POST仅处理 POST 请求,常用于表单提交或数据创建。可防止恶意刷新导致重复提交,需配合 CSRF 防护。
method = RequestMethod.PUT用于更新资源,对应 REST 中的更新操作。浏览器表单不支持 PUT,通常通过 HiddenHttpMethodFilter 转换 POST 为 PUT。
method = RequestMethod.DELETE用于删除资源,对应 REST 中的删除操作。同 PUT,需借助过滤器实现浏览器兼容。
method = {GET, POST}支持多种请求方法。适用于需兼容不同客户端的接口。
不设置 method 属性匹配所有 HTTP 方法(GET、POST、PUT、DELETE 等)。存在安全隐患,生产环境建议明确指定 method。

4.3 路径变量(@PathVariable)

方法定义示例说明注意事项
@RequestMapping(“/user/{id}“)定义路径变量 {id}。{id} 是占位符,实际请求如 /user/123。
public String getUser(@PathVariable String id)将路径变量 id 绑定到方法参数。参数名必须与占位符一致,否则需指定 value。
@PathVariable(“uid”) String id指定绑定的路径变量名称为 uid,即使参数名为 id。当参数名与占位符不一致时使用。
@PathVariable int id支持自动类型转换(String → int)。转换失败会抛出 TypeMismatchException,需处理异常。
@PathVariable(required = false) String name设置路径变量为非必填(较少用,路径通常为必填)。若路径中 {xxx} 存在,则 required 无效,必须提供。

4.4 请求参数绑定(@RequestParam)

方法定义示例说明注意事项
@RequestParam String name绑定请求参数 name,如 ?name=Tom。若参数不存在会抛出异常。
@RequestParam(value = “userName”) String name指定请求参数名为 userName,绑定到 name 变量。用于参数名与变量名不一致时。
@RequestParam(defaultValue = “未知”) String name设置默认值,若参数未提供则使用 “未知”。defaultValue 为 String 类型,可解析为基本类型(如 “18” → int)。
@RequestParam(required = false) String email指定参数为非必填,若无 email 参数则值为 null。与 defaultValue 结合使用更安全。
@RequestParam Map<String, String> params将所有请求参数绑定到 Map 中。key 为参数名,value 为参数值(仅取第一个值)。
@RequestParam MultiValueMap<String, String> params绑定多个同名参数(如 checkbox),保留所有值。适用于参数有多个值的情况。
public String search(@RequestParam List tags)将多个同名参数自动绑定为 List。如 ?tags=java&tags=spring → List 包含 “java”,“spring”。
注解与示例说明注意事项
@RequestHeader(“User-Agent”) String userAgent获取请求头 User-Agent 的值。头名称不区分大小写。
@RequestHeader Map<String, String> headers将所有请求头绑定到 Map 中。key 为头名称(小写),value 为对应值。
@RequestHeader(value=“Accept”, defaultValue=”/”) String accept设置默认 Accept 头值。防止头缺失导致空指针。
@CookieValue(“JSESSIONID”) String sessionId获取名为 JSESSIONID 的 Cookie 值。用于会话跟踪或身份识别。
@CookieValue(“_ga”) String googleAnalyticsId获取 Google Analytics 的追踪 ID。常用于埋点分析。
@CookieValue(value=“token”, required = false) String token获取 token Cookie,非必填。若 Cookie 不存在,值为 null,避免异常。
@CookieValue(“sessionId”) Cookie sessionId直接获取 Cookie 对象,可调用 getName()、getValue()、getPath() 等方法。适用于需访问 Cookie 元数据的场景。

第 5 章 数据绑定与类型转换

5.1 基本数据类型绑定

参数类型/示例说明注意事项
String name绑定请求参数 name 到 String 类型。若参数不存在且未设默认值,值为 null。
int age 或 Integer age绑定数值型参数,支持自动类型转换。基本类型 int 不能为 null,若参数缺失会抛 TypeMismatchException;建议使用包装类 Integer。
boolean active 或 Boolean active绑定布尔值,支持 true/false、on/off、1/0 等格式。注意表单复选框未勾选时不提交参数,需设置 required=false 或 defaultValue。
double price 或 Double price绑定浮点数参数。支持小数和科学计数法,转换失败抛异常。
@RequestParam(defaultValue = “0”) int count设置默认值避免类型转换异常。defaultValue 为 String 类型,需能正确解析为目标类型。
@RequestParam(required = false) Long id允许参数为空,使用包装类型接收。推荐对非必填参数使用包装类型,防止空值异常。

5.2 对象绑定(POJO)

绑定方式/示例说明注意事项
public String addUser(User user)将请求参数自动绑定到 User 对象的同名属性。User 类必须有 setter 方法,Spring 通过反射调用 setXxx() 赋值。
User 类属性:private String name; private int age;请求参数 name=Tom&age=25 → 自动填充 user 对象。属性名需与参数名完全匹配(忽略大小写前缀)。
内嵌对象:private Address address;支持级联绑定,如 address.city=Beijing。表单字段名为”对象名.属性名”,Address 需有默认构造函数。
List hobbies绑定集合属性,如 hobbies=reading&hobbies=music。需在 POJO 中初始化 List(如 =new ArrayList<>()),否则为 null。
List roles绑定对象集合,如 roles[0].name=admin&roles[1].name=user。使用索引语法绑定多个对象,roles 需初始化且 Role 类有 setter。
数组属性:String[] tags绑定多个同名参数为数组,如 tags=java&tags=spring。数组无需初始化,自动创建。

5.3 数组与集合绑定

绑定类型/示例说明注意事项
String[] hobbies方法参数直接接收同名多值参数为数组。如 ?hobbies=reading&hobbies=music → hobbies 数组含两个元素。
Integer[] scores接收数值数组,支持自动类型转换。若某值无法转换(如非数字),整个请求失败。
@RequestParam List tags显式使用 List 接收多值参数。List 比数组更灵活,推荐使用。
List numbers接收数值列表,自动转换 String→Integer。空值或无效值导致 TypeMismatchException。
Map<String, String> params绑定所有请求参数到 Map(键值对)。适用于参数动态变化的场景,仅获取第一个值。
MultiValueMap<String, String> params保留所有参数值,支持同名多值。需引入 org.springframework.util.MultiValueMap。

5.4 自定义类型转换器(Converter)

方法/配置说明注意事项
实现 Converter<S, T> 接口定义泛型转换器,如 Converter<String, Date>。必须实现 convert(S source) 方法,返回目标类型。
public Date convert(String source)将字符串转换为 Date 对象。源字符串格式需明确(如 yyyy-MM-dd),否则抛异常。
注册转换器:addConverter(new StringToDateConverter())在 WebMvcConfigurer 中注册自定义转换器。需实现 WebMvcConfigurer 并重写 addFormatters()。
转换器优先级多个转换器匹配时,按注册顺序执行。避免冲突,确保关键转换器优先注册。
异常处理转换失败应抛 IllegalArgumentException。框架会捕获并返回 400 Bad Request。

5.5 数据格式化(@DateTimeFormat、@NumberFormat)

注解/示例说明注意事项
@DateTimeFormat(pattern = “yyyy-MM-dd”)指定日期字符串的解析格式。用于 Date、LocalDate 等类型,配合表单提交使用。
private Date birthDate;POJO 属性上标注,自动格式化绑定。若格式不匹配,抛 ConversionFailedException。
@DateTimeFormat(iso = ISO.DATE)使用 ISO 标准日期格式(yyyy-MM-dd)。简化常用格式的配置。
@NumberFormat(pattern = ”#,###.00”)格式化数字显示,如 1234.5 → 1,234.50。用于金额、统计等场景。
@NumberFormat(style = Style.CURRENCY)按货币风格格式化(如 $1,234.50)。需结合 Locale 显示本地化货币符号。
应用场景主要用于表单提交的数据绑定和数据显示。显示时需配合 Formatter 注册,绑定时自动生效。

第 6 章 模型与视图(ModelAndView)

6.1 使用 ModelAndView 返回视图

方法/属性说明注意事项
ModelAndView mav = new ModelAndView();创建 ModelAndView 实例。需手动设置视图名和模型数据。
mav.setViewName(“home”);设置逻辑视图名,由 ViewResolver 解析。视图名对应实际页面路径(如 /WEB-INF/jsp/home.jsp)。
mav.addObject(“msg”, “Hello”);添加模型数据,键值对形式。相当于 request.setAttribute(“msg”, “Hello”)。
mav.addObject(user);添加对象,键为类名首字母小写(如 user)。若需自定义键名,使用 mav.addObject(“u”, user)。
return mav;控制器方法返回 ModelAndView 对象。最终由 DispatcherServlet 执行视图渲染。

6.2 使用 Model 接口传递数据

方法/示例说明注意事项
public String home(Model model)将 Model 作为方法参数注入。Spring 自动创建 Model 实例。
model.addAttribute(“title”, “首页”);添加单个属性到模型。等价于 request.setAttribute。
model.addAttribute(“user”, user);添加对象到模型。对象可在 JSP 中通过 ${user} 访问。
model.addAllAttributes(map);批量添加 Map 中的所有属性。已存在的键会被覆盖。
return “home”;返回逻辑视图名,不创建 ModelAndView。更简洁,推荐方式。

6.3 使用 ModelMap 与 ModelAndView 的区别

对比项ModelMapModelAndView
类型实现 Model 接口,仅封装模型数据。封装模型数据和视图信息。
用途仅用于传递数据到视图。同时指定跳转视图和传递数据。
创建方式自动注入或 new ModelMap()。new ModelAndView() 或返回值。
设置视图无法设置视图,需方法返回视图名。可通过 setViewName() 设置。
推荐场景大多数控制器方法,返回 String 视图名。需动态决定视图或复杂控制流程。
灵活性低,专注数据传递。高,可编程控制视图和模型。

6.4 视图解析器(ViewResolver)配置与原理

配置项/类型说明注意事项
InternalResourceViewResolver用于 JSP 页面解析,最常用。需设置 prefix 和 suffix。
prefix = “/WEB-INF/jsp/“视图路径前缀。确保 JSP 文件存放在此目录下。
suffix = “.jsp”视图路径后缀。与 prefix 结合生成完整路径。
order 属性多个 ViewResolver 时的解析优先级。值越小优先级越高。
ContentNegotiatingViewResolver支持根据请求格式(JSON/HTML)选择视图。用于 RESTful 混合响应。
解析流程逻辑视图名 → ViewResolver → 实际视图对象 → 渲染。若找不到视图抛 BeanNotFoundException。
配置方式XML 中或 Java 配置 @Bean。必须注册为 Spring Bean。

第 7 章 视图技术集成

7.1 JSP 视图集成

配置/使用方式说明注意事项
ViewResolver 配置使用 InternalResourceViewResolver 解析 JSP 页面。需在 spring-mvc.xml 中配置。
prefix = “/WEB-INF/jsp/“设置 JSP 文件存放目录,增强安全性(防止直接访问)。目录需存在于 webapp 下。
suffix = “.jsp”设置文件后缀,与逻辑视图名拼接成完整路径。如视图名 “user/list” → “/WEB-INF/jsp/user/list.jsp”。
JSP 中获取模型数据使用 EL 表达式 ${name} 或 JSTL <c:out> 输出。确保引入 JSTL 依赖。
引入 JSTL 依赖Maven 添加 javax.servlet.jsp.jstl 依赖。否则 JSTL 标签无法使用。
表单标签库使用 Spring 表单标签 <form:form><form:input> 等。需引入 spring-webmvc 和标签库声明。
错误信息展示<form:errors path="fieldName"/> 显示字段校验错误。需配合 @ModelAttribute 和 BindingResult。

7.2 Thymeleaf 视图集成

配置/使用方式说明注意事项
添加依赖Maven 引入 thymeleaf-spring5 和 thymeleaf。版本需与 Spring 兼容。
配置 TemplateResolver设置模板前缀、后缀、模式等。前缀通常为 classpath:/templates/。
配置 SpringTemplateEngine管理模板引擎,注册方言(如 SpringStandardDialect)。必须配置为 Spring Bean。
配置 ThymeleafViewResolver将逻辑视图名解析为 Thymeleaf 模板。设置 order 和 characterEncoding。
模板位置默认在 src/main/resources/templates/ 目录下。文件扩展名为 .html。
基本语法使用 th:text=”${name}“、th:each、th:if 等。支持自然模板,静态预览友好。
表单绑定th:object=”${user}” 绑定模型对象,th:field=”*{name}” 绑定字段。与 @ModelAttribute 配合使用。
错误信息th:errors=”*{fieldName}” 显示校验错误。需有 BindingResult。

7.3 JSON 数据返回(@ResponseBody)

使用方式/示例说明注意事项
@ResponseBody 注解标记方法,将返回值直接写入响应体,而非视图名。常用于 REST API。
返回 POJO 对象@ResponseBody User getUser() → 自动序列化为 JSON。需 Jackson 或 Gson 依赖。
返回集合@ResponseBody List<User> → 序列化为 JSON 数组。数据结构清晰,适合前端消费。
返回 Map@ResponseBody Map<String, Object> → 灵活返回键值对。适合动态结构响应。
配置消息转换器MappingJackson2HttpMessageConverter 自动注册。确保 jackson-databind 在类路径。
日期格式处理使用 @JsonFormat(pattern=“yyyy-MM-dd”) 控制输出格式。避免默认时间戳格式。
替代方案 @RestController类级别注解,等价于 @Controller + @ResponseBody。所有方法默认返回数据而非视图。

7.4 RESTful 风格视图支持

特性/注解说明注意事项
@PathVariable提取 URL 路径变量,如 /users/{id}。实现资源定位,符合 REST 理念。
@RequestBody接收 JSON 请求体并反序列化为对象。用于 POST/PUT 请求创建或更新资源。
@RestController简化 REST 控制器开发,所有方法返回数据。无需 @ResponseBody。
HTTP 方法映射使用 @GetMapping、@PostMapping 等简化注解。语义更清晰,替代 @RequestMapping(method=GET)。
ResponseEntity封装响应体、状态码、响应头,精确控制响应。如 return ResponseEntity.ok(user);。
状态码返回使用 ResponseEntity.status(HttpStatus.CREATED).body(data)。符合 REST 状态语义(201 Created)。
内容协商支持根据 Accept 头返回 JSON 或 XML。需配置多个 HttpMessageConverter。
HATEOAS 支持添加链接信息,实现超媒体驱动。可引入 spring-hateoas 框架。

第 8 章 表单处理与数据校验

8.1 表单提交处理

处理步骤说明注意事项
创建表单页面使用 JSP 或 Thymeleaf 编写 HTML 表单。method=“post”,action 指向控制器路径。
控制器接收使用 @PostMapping 映射提交请求。避免使用 GET 提交敏感数据。
参数绑定通过 @RequestParam 或 POJO 绑定表单数据。推荐使用 POJO 接收一组字段。
重定向防止重复提交处理成功后使用 redirect:/success。遵循 PRG 模式(Post-Redirect-Get)。
错误回显若校验失败,返回表单页并保留输入数据。使用 @ModelAttribute 保持表单对象。
静态资源处理确保 CSS/JS 图片等能被正确加载。配置 <mvc:resources> 或 WebMvcConfigurer。

8.2 使用 @ModelAttribute 绑定表单数据

使用方式说明注意事项
方法参数@ModelAttribute User user 将请求参数绑定到 User 对象。必须有 setter 方法。
类级别在控制器类上使用 @ModelAttribute,所有方法共享模型数据。如填充下拉列表选项。
方法级别定义 @ModelAttribute(“roles”) List<String> 为所有方法添加模型数据。在目标方法前执行。
表单对象命名默认使用类名首字母小写(如 user),可自定义名称。Thymeleaf 中 th:object=”${user}” 需匹配。
与 BindingResult 配合必须紧跟 @ModelAttribute 参数后声明 BindingResult result。否则校验异常无法捕获。

8.3 数据校验注解(JSR-303)

注解说明示例
@NotNull不能为 null。@NotNull(message=“年龄必填”) private Integer age;
@NotEmpty不能为 null 或空字符串(集合、数组、Map 同理)。@NotEmpty(message=“用户名不能为空”) private String name;
@NotBlank不能为 null 或空白字符串(仅用于 String)。@NotBlank(message=“邮箱不能为空”) private String email;
@Size(min=2, max=10)限制字符串长度或集合大小。@Size(min=6, max=20, message=“密码6-20位”) private String pwd;
@Min(value=18)数值最小值。@Min(18) private int age;
@Max(value=100)数值最大值。@Max(100) private int score;
@Email校验邮箱格式。@Email(message=“邮箱格式错误”) private String email;
@Pattern(regexp=”…”)正则表达式校验。@Pattern(regexp=“^1[3-9]\d{9}$”, message=“手机号错误”)
@Valid标记对象需要进行级联校验。public String save(@Valid @ModelAttribute User user, BindingResult result)

8.4 校验结果处理(BindingResult)

方法/属性说明注意事项
BindingResult result紧跟 @Valid 参数后声明,封装校验结果。顺序不能错,否则报错。
result.hasErrors()判断是否有校验错误。是则返回表单页,否则继续处理。
result.getFieldErrors()获取所有字段错误信息列表。可遍历输出到页面。
error.getField()获取出错的字段名。用于定位错误位置。
error.getDefaultMessage()获取错误提示消息。即注解中的 message 属性值。
页面显示错误JSP: <form:errors path="name"/>;Thymeleaf: <p th:errors="*{name}">需与表单字段关联。
自定义错误处理可在控制器内判断并添加全局错误。如 result.reject(“global.error”, “系统异常”);

第 9 章 拦截器(Interceptor)

9.1 拦截器的作用与生命周期

概念说明注意事项
作用在请求处理前后及视图渲染后执行预处理逻辑,实现横切关注点(如日志、权限、性能监控)。类似于 Servlet 的 Filter,但更贴近 Spring MVC,能访问 HandlerMethod 等上下文。
执行时机分为三个阶段:preHandle(前)、postHandle(中)、afterCompletion(后)。可对请求进行拦截或增强。
生命周期每次请求都会调用拦截器的相应方法,但拦截器实例由 Spring 容器管理,通常为单例。避免在拦截器中使用实例变量存储请求数据。
与 Filter 对比Filter 是 Servlet 规范,更底层;Interceptor 是 Spring MVC 特有,可注入 Spring Bean。优先使用 Interceptor 处理 MVC 相关逻辑。

9.2 实现 HandlerInterceptor 接口

方法执行时机返回值含义典型用途
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)控制器方法执行前调用。true:继续执行后续拦截器或目标方法;false:中断请求流程。权限校验、日志记录、请求参数预处理。
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)控制器方法执行后,视图渲染前调用。无返回值,仅用于后处理。修改 ModelAndView、性能监控(记录处理时间起始)。
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)视图渲染完成后调用,无论是否异常。无返回值,用于资源清理。性能监控(记录总耗时)、资源释放(如 ThreadLocal 清理)。

示例代码:

public class LoggingInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("请求开始: " + request.getRequestURI());
        request.setAttribute("startTime", System.currentTimeMillis());
        return true; // 继续处理
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        System.out.println("控制器执行完毕");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        long startTime = (Long) request.getAttribute("startTime");
        long duration = System.currentTimeMillis() - startTime;
        System.out.println("请求结束,耗时: " + duration + "ms");
        if (ex != null) {
            System.out.println("请求处理异常: " + ex.getMessage());
        }
    }
}

9.3 配置拦截器(Interceptor Registration)

配置方式说明示例
Java 配置实现 WebMvcConfigurer 接口,重写 addInterceptors 方法。见下方代码
XML 配置在 Spring MVC 配置文件中使用 <mvc:interceptors><mvc:interceptors><mvc:interceptor><mvc:mapping path="/user/**"/><mvc:exclude-mapping path="/user/login"/><bean class="com.example.LoggingInterceptor"/></mvc:interceptor></mvc:interceptors>
addPathPatterns指定拦截的路径模式,支持 Ant 风格(如 /api/**)。必须调用,否则不生效。
excludePathPatterns指定不拦截的路径,优先级高于拦截规则。常用于排除登录、静态资源等。
顺序控制多个拦截器按注册顺序执行 preHandle,反向执行 postHandle 和 afterCompletion。注意依赖关系,如日志拦截器通常最先注册。

Java 配置示例:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor())
                .addPathPatterns("/user/**")           // 拦截路径
                .excludePathPatterns("/user/login", "/user/register"); // 排除路径
    }
}

9.4 拦截器的应用场景(登录检查、日志记录)

应用场景实现方式注意事项
登录检查preHandle 中检查 HttpSession 是否包含用户信息,若无则重定向到登录页并返回 false。静态资源、登录/注册接口需排除拦截。
权限校验结合用户角色和请求路径,判断是否有访问权限。可从数据库或缓存加载权限配置。
日志记录preHandle 记录请求开始,afterCompletion 记录结束及耗时,可记录 IP、参数等。敏感信息(如密码)需脱敏。
性能监控利用 request 属性记录时间戳,计算请求处理总耗时。可结合 AOP 或 Micrometer 实现更精细监控。
跨域处理在 preHandle 中设置响应头(如 Access-Control-Allow-Origin),并处理 OPTIONS 预检请求。现代框架(如 Spring Boot)通常有专用配置。
请求幂等性在 preHandle 中验证请求 Token,防止重复提交。需结合 Redis 等存储 Token 状态。

第 10 章 异常处理

10.1 全局异常处理(@ControllerAdvice)

特性说明注意事项
作用定义一个全局的异常处理器,集中处理整个应用中控制器抛出的异常。避免在每个控制器中重复写异常处理逻辑。
注解@ControllerAdvice 是 @Component 的派生注解,会被 Spring 扫描并注册。可配合 @RestControllerAdvice 返回 JSON 数据。
选择性生效可通过 basePackages、annotations、assignableTypes 限制作用范围。如 @ControllerAdvice(basePackages = “com.example.user”)。
方法签名使用 @ExceptionHandler 标记处理方法,参数为异常类型。可返回 ModelAndView、String(视图名)或 ResponseEntity。

示例代码:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ModelAndView handleUserNotFound(UserNotFoundException ex) {
        ModelAndView mav = new ModelAndView("error/user-not-found");
        mav.addObject("message", ex.getMessage());
        return mav;
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGeneralException(Exception ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("系统繁忙,请稍后再试");
    }
}

10.2 方法级异常处理(@ExceptionHandler)

特性说明注意事项
作用范围仅处理当前控制器类中抛出的异常。优先级高于 @ControllerAdvice。
使用位置定义在控制器类中的方法上,用 @ExceptionHandler 标记。可处理多种异常类型。
执行逻辑当控制器方法抛出匹配异常时,跳过原方法返回,执行该处理方法。原方法中未捕获的异常才会被处理。
返回值与普通控制器方法相同,可返回视图名、ModelAndView 或 @ResponseBody 数据。保持与业务逻辑一致的响应格式。

示例代码:

@Controller
public class UserController {

    @GetMapping("/user/{id}")
    public String getUser(@PathVariable Long id) {
        if (id <= 0) {
            throw new IllegalArgumentException("ID必须大于0");
        }
        // ... 查找用户
        return "user/detail";
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public String handleIllegalArgument(IllegalArgumentException ex, Model model) {
        model.addAttribute("error", ex.getMessage());
        return "error/invalid-param"; // 返回错误页面
    }
}

10.3 自定义异常类

类型说明示例
业务异常继承 RuntimeException,表示可预期的业务错误(如余额不足、库存不足)。public class InsufficientBalanceException extends RuntimeException { public InsufficientBalanceException(String message) { super(message); } }
系统异常继承 Exception 或 RuntimeException,表示系统级错误(如数据库连接失败)。通常由框架抛出,也可自定义。
构造函数提供含 String message 的构造函数,便于传递错误信息。可添加错误码、严重级别等字段。
使用场景在 Service 层抛出,由 Controller 或 @ControllerAdvice 捕获处理。避免在 Controller 中直接抛 new Exception()。

10.4 异常视图解析

方式说明注意事项
SimpleMappingExceptionResolver传统 XML 配置方式,将异常类名映射到视图名。已逐渐被 @ControllerAdvice 取代。
@ControllerAdvice + ModelAndView在全局异常处理器中返回 ModelAndView 指向错误页面。可传递异常信息到视图。
返回 JSON 错误响应在 REST API 中,返回包含错误码、消息的 JSON 对象。使用 ResponseEntity 统一格式。
错误页面(Error Page)配置静态错误页(如 error/404.jsp, error/500.html)。Servlet 容器或 Spring Boot 的 ErrorController 可处理。
传递异常信息将异常消息、堆栈(开发环境)放入 Model,供 JSP/Thymeleaf 展示。生产环境避免暴露堆栈信息。
HTTP 状态码使用 response.setStatus() 或 ResponseEntity 设置合适的 HTTP 状态码(如 404、500)。符合语义,利于前端处理。

第 11 章 静态资源处理

11.1 静态资源映射(CSS、JS、图片)

资源类型默认位置说明注意事项
CSS 样式文件src/main/webapp/resources/css/ 或 src/main/resources/static/css/(Spring Boot)用于定义页面样式。路径需在映射配置中指定。
JavaScript 脚本…/js/实现页面交互逻辑。可分模块存放(如 lib/, app/)。
图片资源…/images/, …/img/包括 PNG, JPG, GIF 等格式。注意版权和文件大小优化。
字体文件…/fonts/如 .woff, .ttf 等。需在 CSS 中正确引用。
其他静态文件…/pdf/, …/doc/如文档、视频等。确保服务器有足够带宽。

11.2 使用 mvc:resources 配置

配置方式说明示例
XML 配置使用 <mvc:resources> 标签,由 DefaultServletHttpRequestHandler 处理。<mvc:resources mapping="/static/**" location="/resources/, classpath:/static/" />
mapping 属性定义 URL 访问路径模式(Ant 风格)。/static/** 表示所有以 /static/ 开头的请求。
location 属性指定资源在项目中的实际存放位置,可多个,用逗号分隔。location=“/resources/” 对应 webapp/resources/;classpath:/static/ 对应 src/main/resources/static/。
缓存设置通过 cache-period 属性设置缓存时间(秒)。cache-period=“31536000” 表示缓存 1 年。
Java 配置实现 WebMvcConfigurer 并重写 addResourceHandlers。见下方代码
优先级静态资源映射优先于 @RequestMapping,确保静态资源不被控制器拦截。若无此配置,DispatcherServlet 会尝试处理所有请求,导致 404。

Java 配置示例:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static/**")
            .addResourceLocations("/resources/", "classpath:/static/")
            .setCachePeriod(31536000);
}

11.3 静态资源版本控制与缓存

策略说明实现方式
查询参数版本(?v=1.0)在 URL 后添加版本号或时间戳。<script src="/static/js/app.js?v=1.2"></script>
文件名版本(app-v1.0.js)将版本号嵌入文件名。需构建工具(如 Webpack)支持重命名。
内容哈希(app-a1b2c3d.js)根据文件内容生成哈希值作为文件名。最佳实践,确保内容变更时 URL 改变。
长缓存(Long-term Caching)对带版本号的资源设置长时间缓存(如 1 年)。减少重复下载,提升性能。
HTML 不缓存主页面不设置缓存或短时间缓存。确保用户能获取最新的资源引用。
Spring 集成使用 ResourceUrlEncodingFilter 或 VersionResourceResolver。在 JSP/Thymeleaf 中自动重写资源 URL。
注意事项版本控制需在构建或部署阶段自动生成,避免手动维护。结合 CI/CD 流程自动化。

第 12 章 文件上传与下载

12.1 文件上传配置(MultipartResolver)

配置项说明示例/值
MultipartResolver 作用解析 multipart/form-data 类型的请求,将文件和表单数据封装为 MultipartFile。必须配置,否则无法处理文件上传。
实现类StandardServletMultipartResolver(推荐,基于 Servlet 3.0)或 CommonsMultipartResolver(需 Apache Commons FileUpload 依赖)。Spring 5+ 推荐前者。
XML 配置在 Spring 配置文件中注册 Bean。<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>
Java 配置无额外 Bean,但需在 WebMvcConfigurer 中通过 MultipartConfigElement 配置。见下方代码
常用限制参数maxFileSize: 单个文件最大大小;maxRequestSize: 整个请求最大大小;fileSizeThreshold: 内存阈值,超过则写入磁盘单位:KB, MB, GB。默认通常为 1MB,需根据业务调整。
Servlet 3.0 配置在 web.xml 或 ServletRegistrationBean 中设置 multipart-config。<multipart-config><max-file-size>10485760</max-file-size><max-request-size>10485760</max-request-size><file-size-threshold>0</file-size-threshold></multipart-config>

Java 配置示例:

@Bean
public MultipartConfigElement multipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();
    factory.setMaxFileSize("10MB");
    factory.setMaxRequestSize("10MB");
    return factory.createMultipartConfig();
}

12.2 单文件上传(@RequestParam MultipartFile)

步骤/代码说明注意事项
HTML 表单method=“post” 且 enctype=“multipart/form-data”。缺少 enctype 会导致文件无法上传。
控制器方法使用 @RequestParam(“file”) MultipartFile file 接收。参数名 file 需与表单 name 属性一致。
检查文件file.isEmpty() 判断是否选择了文件。空文件(用户未选)不会抛异常,需主动检查。
获取信息file.getOriginalFilename(), file.getSize(), file.getContentType()。原始文件名可能包含路径,需安全处理。
保存文件使用 file.transferTo(new File(“/path/to/dest”))。目标目录必须存在且有写权限。
异常处理IOException, IllegalStateException(如磁盘满)。需在 @ControllerAdvice 中捕获并友好提示。

示例代码:

@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file, Model model) {
    if (!file.isEmpty()) {
        try {
            String filename = file.getOriginalFilename();
            file.transferTo(new File("/uploads/" + filename));
            model.addAttribute("message", "上传成功: " + filename);
        } catch (IOException e) {
            model.addAttribute("error", "上传失败: " + e.getMessage());
        }
    } else {
        model.addAttribute("error", "请选择文件");
    }
    return "upload-result";
}

12.3 多文件上传

方法说明示例
MultipartFile 数组使用 MultipartFile[] files 接收同名多文件。@RequestParam(“files”) MultipartFile[] files
List使用 List 接收,更灵活。@RequestParam(“files”) List<MultipartFile> files
表单字段多个 <input type="file" name="files"> 或一个 multiple 属性的输入框。<input type="file" name="files" multiple>
遍历处理循环每个文件,执行检查和保存。注意处理部分成功的情况(如一个成功一个失败)。
限制数量在业务逻辑中检查 files.length 或 files.size()。防止恶意上传大量小文件。

示例代码:

@PostMapping("/upload-multiple")
public String uploadMultiple(@RequestParam("files") MultipartFile[] files, Model model) {
    List<String> successes = new ArrayList<>();
    List<String> failures = new ArrayList<>();

    for (MultipartFile file : files) {
        if (!file.isEmpty()) {
            try {
                file.transferTo(new File("/uploads/" + file.getOriginalFilename()));
                successes.add(file.getOriginalFilename());
            } catch (IOException e) {
                failures.add(file.getOriginalFilename() + ": " + e.getMessage());
            }
        }
    }
    model.addAttribute("successes", successes);
    model.addAttribute("failures", failures);
    return "upload-result";
}

12.4 文件下载实现

步骤/代码说明注意事项
设置响应头Content-Disposition: attachment; filename=“filename.txt”attachment 弹出下载框,inline 尝试浏览器内打开。
设置 MIME 类型response.setContentType(“application/octet-stream”) 或具体类型如 image/jpeg。正确的 MIME 类型有助于浏览器处理。
读取文件使用 FileInputStream 或 Files.newInputStream() 读取文件流。文件路径需安全校验,防止路径穿越(如 ../../../etc/passwd)。
写入响应使用 response.getOutputStream() 写入字节流。需设置 Content-Length(可选,利于进度条)。
异常处理文件不存在、无权限等,返回 404 或 500。避免暴露服务器路径。
大文件优化分块读取(缓冲区),避免 OutOfMemoryError。使用 BufferedInputStream 和 byte[] buffer。

示例代码:

@GetMapping("/download")
public void downloadFile(@RequestParam("filename") String filename,
                         HttpServletResponse response) throws IOException {
    // 安全校验:只允许下载特定目录下的文件
    if (filename.contains("..")) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }
    Path filePath = Paths.get("/uploads/").resolve(filename).normalize();
    if (!Files.exists(filePath) || !filePath.toFile().isFile()) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition", "attachment; filename=\"" +
                       URLEncoder.encode(filename, "UTF-8") + "\"");
    response.setContentLengthLong(Files.size(filePath));

    try (InputStream in = Files.newInputStream(filePath);
         OutputStream out = response.getOutputStream()) {
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }
    }
}

第 13 章 RESTful Web 服务

13.1 RESTful 设计原则

原则说明示例
无状态 (Stateless)每个请求包含处理所需的所有信息,服务器不保存客户端状态。使用 Token(如 JWT)代替 Session。
资源导向 (Resource-Oriented)将所有数据和功能抽象为”资源”,使用名词表示。/users, /orders, /products。
统一接口 (Uniform Interface)使用标准 HTTP 方法(GET, POST, PUT, DELETE)操作资源。GET /users(查询), POST /users(创建), PUT /users/1(更新), DELETE /users/1(删除)。
URI 设计清晰URI 应简洁、可读,使用复数名词,避免动词。优:/api/v1/users;劣:/api/getUsers。
HATEOAS(可选)响应中包含相关资源的链接,实现超媒体驱动。返回用户时包含 “links”: [{“rel”: “self”, “href”: “/users/1”}]。
版本控制在 URI 或请求头中指定 API 版本,保证向后兼容。/api/v1/users 或 Accept: application/vnd.company.api.v1+json。
HTTP 状态码语义化正确使用状态码表示结果。200 OK, 201 Created, 400 Bad Request, 404 Not Found, 500 Internal Server Error。
数据格式通常使用 JSON 或 XML 作为数据交换格式。响应头 Content-Type: application/json。

13.2 @RestController 注解

特性说明注意事项
组合注解@RestController = @Controller + @ResponseBody。类中所有方法默认返回数据而非视图名。
简化开发无需在每个方法上添加 @ResponseBody。适用于纯 API 服务。
返回值方法返回值(POJO, List, Map 等)自动序列化为 JSON(或 XML)。需 Jackson 或 Gson 依赖在类路径。
与 @Controller 区别@Controller 方法通常返回视图名,@RestController 返回数据。若 @RestController 中有方法需返回视图,可单独使用 @ResponseBody。
应用范围通常应用于专门提供 REST API 的控制器类。可与普通 @Controller 共存于同一应用。

示例代码:

@RestController
@RequestMapping("/api/users")
public class UserRestController {

    @GetMapping
    public List<User> getAllUsers() {
        // 返回用户列表,自动转为 JSON 数组
        return userService.findAll();
    }

    @PostMapping
    public User createUser(@RequestBody User user) {
        // 接收 JSON 创建用户,返回创建后的用户
        return userService.save(user);
    }
}

13.3 RESTful 路径设计(@PathVariable)

注解/用法说明示例
@PathVariable提取 URL 路径中的变量值。@GetMapping(“/users/{id}“)
绑定单个变量方法参数用 @PathVariable 注解,名称与路径占位符一致。public User getUser(@PathVariable Long id)
绑定多个变量URL 中可有多个 {} 占位符。@GetMapping(“/users/{uid}/orders/{oid}”) → @PathVariable Long uid, @PathVariable Long oid
名称不一致使用 @PathVariable(“name”) 指定路径变量名。@GetMapping(”/{userId}”) → @PathVariable(“userId”) Long id
类型转换支持基本类型和包装类,自动转换。{id} → Long id,转换失败返回 400。
正则约束(可选)在路径中使用正则表达式限制变量格式。@GetMapping(“/users/{id:\d+}”) 只匹配数字 ID。

13.4 使用 @RequestBody 接收 JSON 数据

用法说明注意事项
注解位置标记控制器方法的参数,表示从请求体读取数据。通常用于 POST、PUT 请求。
数据绑定JSON 数据自动反序列化为指定的 Java 对象(POJO)。对象需有默认构造函数和 setter 方法。
消息转换器依赖 HttpMessageConverter(如 MappingJackson2HttpMessageConverter)。确保 jackson-databind 在类路径。
参数校验常与 @Valid 结合,对反序列化后的对象进行 JSR-303 校验。校验失败抛 MethodArgumentNotValidException。
错误处理JSON 格式错误(如语法错误)会抛 HttpMessageNotReadableException。需在 @ControllerAdvice 中统一处理。
集合/Map也可接收 JSON 数组或对象。@RequestBody List<User> users 或 @RequestBody Map<String, Object> data。

示例代码:

@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody User user, BindingResult result) {
    if (result.hasErrors()) {
        // 处理校验错误
        throw new ValidationException("输入数据无效");
    }
    User savedUser = userService.save(user);
    return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}

13.5 ResponseEntity 返回响应

特性说明示例
封装响应包含状态码、响应头、响应体,提供对 HTTP 响应的完全控制。ResponseEntity<User>
构建响应使用静态工厂方法 ok(), created(), badRequest() 等。return ResponseEntity.ok(user);
设置状态码status(HttpStatus.CREATED) 设置 201 Created。用于资源创建成功。
设置响应头header(“Location”, “/users/1”) 设置创建资源的 URL。遵循 REST 最佳实践。
泛型支持ResponseEntity<T>,T 为响应体类型。可返回 ResponseEntity<String>, ResponseEntity<Map> 等。
空响应ResponseEntity<Void> 可用于 DELETE 操作。return ResponseEntity.noContent().build();(204 No Content)
错误响应统一返回错误信息和状态码。return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorMap);

示例代码:

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    if (user != null) {
        return ResponseEntity.ok(user);
    } else {
        return ResponseEntity.notFound().build(); // 404
    }
}

@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
    User savedUser = userService.save(user);
    return ResponseEntity.created(URI.create("/users/" + savedUser.getId()))
                       .body(savedUser); // 201 + Location 头
}

第 14 章 国际化支持(i18n)

14.1 国际化配置

配置项说明示例
MessageSource Bean配置 ResourceBundleMessageSource 加载消息资源文件。见下方代码
XML 配置在 Spring 配置文件中定义 Bean。<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><property name="basename" value="messages" /><property name="defaultEncoding" value="UTF-8" /></bean>
文件位置资源文件通常放在 src/main/resources/ 目录下。如 messages.properties, messages_zh_CN.properties。
依赖无额外依赖,Spring 核心支持。确保资源文件编码为 UTF-8。

Java 配置示例:

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource source = new ResourceBundleMessageSource();
    source.setBasename("messages"); // 文件基础名
    source.setDefaultEncoding("UTF-8");
    return source;
}

14.2 消息资源文件(messages.properties)

文件名说明内容示例
messages.properties默认语言(通常是英文)资源文件。welcome.message=Welcome to our application! / error.required=This field is required.
messages_zh_CN.properties中文(简体)资源文件。welcome.message=欢迎使用我们的应用! / error.required=该字段是必填项。
messages_en_US.properties美式英文资源文件。welcome.message=Welcome! (US)
键(Key)全局唯一标识符,通常使用点分层次结构。form.username, button.save, error.email.invalid
值(Value)对应语言的文本内容。需进行 UTF-8 编码(或使用 Unicode 转义)。
占位符使用 {0}, {1} 等占位符,配合 MessageSource.getMessage() 填充。user.greeting=Hello, {0}! You have {1} messages.

14.3 LocaleResolver 配置

实现类说明配置方式
AcceptHeaderLocaleResolver默认实现,根据 HTTP 请求头 Accept-Language 决定区域。无需额外配置。
CookieLocaleResolver通过 Cookie 保存用户选择的语言。见下方代码
SessionLocaleResolver将 Locale 存储在 HttpSession 中。resolver = new SessionLocaleResolver(); resolver.setDefaultLocale(Locale.US);
FixedLocaleResolver固定使用一个 Locale,忽略用户请求。用于测试或单语言应用。
自定义 LocaleResolver实现 LocaleResolver 接口,根据业务逻辑(如数据库、URL 参数)决定 Locale。如从用户配置表读取偏好语言。
切换语言提供链接调用 setLocale 方法(如 ?lang=zh_CN)。需配置 LocaleChangeInterceptor。

CookieLocaleResolver 配置示例:

@Bean
public LocaleResolver localeResolver() {
    CookieLocaleResolver resolver = new CookieLocaleResolver();
    resolver.setCookieName("language");
    resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
    return resolver;
}

LocaleChangeInterceptor 配置:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LocaleChangeInterceptor());
}

链接示例:<a href="?lang=zh_CN">中文</a>

14.4 JSP/Thymeleaf 中的国际化标签

视图技术标签库/语法说明示例
JSP(JSTL)<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>使用 fmt 标签。<fmt:message key="welcome.message" />
JSP - 参数化<fmt:message> 内嵌 <fmt:param><fmt:message key="user.greeting"><fmt:param value="${username}"/><fmt:param value="${msgCount}"/></fmt:message>
Thymeleaf使用 #{...} 表达式。需配置 MessageSource。<p th:text="#{welcome.message}">Welcome</p>
Thymeleaf - 参数化#{...(${...})} 传递参数。<p th:text="#{user.greeting(${user.name}, ${user.messages.size()})}">Greeting</p>
Thymeleaf - 变量表达式在属性中使用 #{...}<input type="text" th:placeholder="#{form.username}" />
Java 代码中获取注入 MessageSource 使用 getMessage(key, args, locale)String msg = messageSource.getMessage("error.required", null, LocaleContextHolder.getLocale());

第 15 章 Spring MVC 高级特性

15.1 异步请求处理(@Async)

特性说明注意事项
目的提高服务器吞吐量,避免长时间操作阻塞 Servlet 容器线程。适用于 I/O 密集型任务(如调用外部 API、发送邮件)。
@Async 注解标记方法为异步执行,调用时立即返回,由 Spring 的 TaskExecutor 在后台线程执行。方法返回值通常为 void 或 Future/CompletableFuture。
启用异步主配置类添加 @EnableAsync。必须添加,否则 @Async 不生效。
返回类型void: 简单异步执行;Future<T>: 可获取结果或检查完成状态;CompletableFuture<T>: 支持链式调用和组合CompletableFuture 更强大,推荐使用。
线程池配置自定义 TaskExecutor 以控制线程数、队列等。避免使用默认的简单线程池,防止资源耗尽。
异常处理@Async 方法内的异常不会被调用者直接感知,需在方法内捕获或配置 AsyncUncaughtExceptionHandler。对于 Future,异常在 get() 时抛出。
与 Web 集成结合 DeferredResult 或 Callable 在控制器中返回异步结果。Callable 由 Spring 管理线程,DeferredResult 可在任意线程设置结果。

示例代码:

@Service
public class AsyncService {

    @Async
    public CompletableFuture<String> asyncTask(String input) {
        try {
            // 模拟耗时操作
            Thread.sleep(3000);
            return CompletableFuture.completedFuture("Processed: " + input);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return CompletableFuture.failedFuture(e);
        }
    }
}

@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async")
    public DeferredResult<String> handleAsync() {
        DeferredResult<String> result = new DeferredResult<>();

        asyncService.asyncTask("test")
                   .whenComplete((data, ex) -> {
                        if (ex != null) {
                            result.setErrorResult("Error: " + ex.getMessage());
                        } else {
                            result.setResult(data);
                        }
                    });

        return result;
    }
}

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }
}

15.2 拦截器链与执行顺序

拦截器阶段执行顺序说明
preHandle按注册顺序执行。若任一拦截器返回 false,则中断,后续拦截器和目标方法不执行,已执行的 preHandle 对应的 afterCompletion 仍会执行。
postHandle按注册顺序的反向执行。只有 preHandle 返回 true 的拦截器才会执行 postHandle。
afterCompletion按注册顺序的反向执行。无论请求是否成功,都会执行,用于资源清理。

执行流程示例(假设 A、B、C 三个拦截器):

场景执行流程
全部通过preHandle(A) → preHandle(B) → preHandle(C) → Controller → postHandle(C) → postHandle(B) → postHandle(A) → afterCompletion(C) → afterCompletion(B) → afterCompletion(A)
B 拦截(preHandle 返回 false)preHandle(A) → preHandle(B) [返回 false] → afterCompletion(A)
Controller 抛异常preHandle(A) → preHandle(B) → preHandle(C) → Controller [异常] → afterCompletion(C) → afterCompletion(B) → afterCompletion(A)

配置示例:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    // 先注册的先执行 preHandle
    registry.addInterceptor(new LoggingInterceptor()).addPathPatterns("/**");
    registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**");
    registry.addInterceptor(new PerformanceInterceptor()).addPathPatterns("/**");
}

执行顺序:Logging → Auth → Performance(preHandle)

15.3 自定义注解与 AOP 结合

步骤说明示例
1. 定义自定义注解使用 @interface 创建注解,指定目标和保留策略。@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME)
2. 创建 AOP 切面定义切面类,使用 @Aspect 和 @Component。@Aspect @Component public class LoggingAspect { ... }
3. 定义切点(Pointcut)使用 @Pointcut 或直接在通知中定义,匹配带自定义注解的方法。@Around("@annotation(logExecution)")
4. 实现通知(Advice)在通知方法中编写横切逻辑(如日志、权限、缓存)。@Around 可控制方法执行;@Before, @After 仅执行前后逻辑。
5. 注入依赖切面中可注入 Spring Bean(如 LoggerService)。实现业务解耦。
6. 启用 AOP主配置添加 @EnableAspectJAutoProxy。Spring Boot 默认启用。

完整示例:

// 1. 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
    String operation() default "";
}

// 2. AOP 切面
@Aspect
@Component
public class ExecutionTimeAspect {

    private static final Logger logger = LoggerFactory.getLogger(ExecutionTimeAspect.class);

    @Around("@annotation(logExecution)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecution logExecution) throws Throwable {
        long start = System.currentTimeMillis();
        String operation = logExecution.operation();
        String method = joinPoint.getSignature().getName();

        try {
            logger.info("开始执行操作: {}, 方法: {}", operation, method);
            Object result = joinPoint.proceed(); // 执行目标方法
            long executionTime = System.currentTimeMillis() - start;
            logger.info("操作完成: {}, 耗时: {}ms", operation, executionTime);
            return result;
        } catch (Exception e) {
            logger.error("操作失败: {}, 方法: {}, 错误: {}", operation, method, e.getMessage());
            throw e;
        }
    }
}

// 3. 在控制器中使用
@RestController
public class UserController {

    @LogExecution(operation = "获取用户信息")
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Long id) {
        // 模拟业务逻辑
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return new User(id, "张三");
    }
}

15.4 性能优化建议

优化方向建议说明
静态资源启用 Gzip 压缩,配置 CDN,使用长缓存 + 版本控制。减少网络传输量和请求数。
视图渲染使用高效的模板引擎(如 Thymeleaf 3+),缓存模板,避免复杂逻辑。减少 CPU 消耗。
数据库访问合理使用连接池(如 HikariCP),避免 N+1 查询,使用二级缓存(如 Redis)。数据库通常是瓶颈。
对象映射选择高效 JSON 库(如 Jackson),避免过度序列化(使用 @JsonIgnore)。减少序列化/反序列化开销。
并发处理对于耗时操作,使用异步处理(@Async, CompletableFuture)。释放 Servlet 线程,提高吞吐量。
日志级别生产环境使用 INFO 或 WARN,避免 DEBUG 大量输出。日志 I/O 可能成为性能瓶颈。
JVM 调优合理设置堆内存(-Xms, -Xmx),选择合适的 GC 算法。避免频繁 Full GC。
代码层面避免在循环中进行数据库查询或远程调用,使用批量操作。减少方法调用和 I/O 次数。
监控集成 APM 工具(如 SkyWalking, Prometheus + Grafana)监控性能指标。及时发现瓶颈。
缓存对频繁读取且不常变的数据使用缓存(@Cacheable)。减少重复计算和数据库访问。