第 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)、参数等信息。 |
| 2 | DispatcherServlet 接收请求 | 所有请求首先由 DispatcherServlet 接收,作为前端控制器统一入口。 | 必须配置为拦截特定路径(如 /),否则无法接管请求。 |
| 3 | HandlerMapping 查找处理器 | DispatcherServlet 查询 HandlerMapping,根据 URL 找到对应的 Controller 方法。 | 多个映射规则时,按优先级匹配最合适的处理器。 |
| 4 | HandlerAdapter 调用处理器 | HandlerAdapter 执行目标方法,完成参数绑定、数据转换、调用业务逻辑等。 | 参数绑定失败会抛出异常,需配置合适的转换器或校验机制。 |
| 5 | Controller 返回结果 | 处理完成后返回 ModelAndView 对象(或 @ResponseBody 数据)。 | 若返回 void 或 String 且无 @ResponseBody,默认视为逻辑视图名。 |
| 6 | ViewResolver 解析视图 | 根据逻辑视图名(如 “home”),ViewResolver 查找实际视图(如 “/WEB-INF/jsp/home.jsp”)。 | 需正确配置视图解析器的前缀和后缀,避免视图找不到错误。 |
| 7 | View 渲染响应 | 视图使用模型数据生成 HTML 页面,填充动态内容并输出到响应流。 | 静态资源(CSS/JS)需单独配置路径,避免被 DispatcherServlet 拦截。 |
| 8 | 返回响应给客户端 | DispatcherServlet 将渲染后的 HTML 页面返回给浏览器显示。 | 若为 REST API,直接返回 JSON/XML 数据,无需视图渲染。 |
第 2 章 开发环境搭建与项目初始化
2.1 环境准备(JDK、Maven、IDE)
| 工具名称 | 说明 | 注意事项 |
|---|
| JDK | Java 开发工具包,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。 |
| 注解/示例 | 说明 | 注意事项 |
|---|
| @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 的区别
| 对比项 | ModelMap | ModelAndView |
|---|
| 类型 | 实现 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)。 | 减少重复计算和数据库访问。 |