本模块回顾 Spring MVC 所需的基础知识,介绍核心概念 IoC/DI,并指导如何搭建基础的 Spring MVC 开发环境。
• Java 语言基础回顾: 学习 Spring MVC 至少需要掌握 Java 的基本语法、面向对象编程思想(类、对象、继承、多态、封装)、集合框架(List, Set, Map)以及对多线程有初步了解。Web 开发中常涉及并发处理。
• Web 基础回顾:
• HTTP 协议: 理解请求 (Request) 和响应 (Response) 的基本结构、常见的 HTTP 方法 (GET, POST, PUT, DELETE 等)、状态码 (200, 404, 500 等)。
• Servlet 原理基础: 理解 Servlet 在 Java Web 应用中的作用,它是处理客户端请求、生成服务器响应的核心组件。Servlet 生命周期 (init, service, destroy) 以及 web.xml 的基本配置(如 < servlet>, < servlet-mapping>)。Spring MVC 的核心 DispatcherServlet 就是一个特殊的 Servlet。
• MVC 设计模式原理: MVC (Model-View-Controller) 是一种软件架构模式,将应用程序分为三个相互关联的部分:
• Model (模型): 负责处理业务逻辑和数据。可以是简单的数据对象(POJO),也可以是负责数据访问和业务处理的服务层。
• View (视图): 负责呈现用户界面。显示模型中的数据,并将用户输入(如表单提交)发送给控制器。在 Web 应用中,视图通常是 HTML 页面,可以使用 JSP, Thymeleaf, FreeMarker 等模板技术。
• Controller (控制器): 作为模型和视图之间的协调者。接收用户输入,调用模型进行业务处理,选择合适的视图来显示结果。控制器不处理业务逻辑,只负责请求的转发和调度。
+-------------+ +----------------+ +-----------+
| User | ---->| Controller |<---->| Model |
+-------------+ +----------------+ +-----------+
^ | ^
| | |
| v |
| +---------------+ |
+------------| View |<--------------+
+---------------+
说明:用户操作视图触发请求 -> Controller 接收请求并调用 Model -> Model 处理数据/业务逻辑 -> Controller 选择 View 并传递 Model 数据 -> View 渲染数据并呈现给用户。
Spring 框架的核心是 IoC 容器,它负责管理应用程序中对象的生命周期和依赖关系。
• IoC (Inversion of Control - 控制反转): 指的是对象创建和依赖关系的管理不是由对象本身负责,而是交给容器来完成。传统的开发中,一个对象如果依赖另一个对象,需要自己去创建或查找被依赖的对象。IoC 将这个控制权反转给了容器。
• DI (Dependency Injection - 依赖注入): 是实现 IoC 的一种方式。容器在创建对象时,将其依赖的其他对象注入进来。这样,对象之间解耦,不再需要硬编码地查找或创建依赖。
• Spring 容器 (ApplicationContext): Spring 提供了多种容器实现,ApplicationContext 是常用的高级接口,提供了更丰富的功能(如国际化、事件发布等)。容器负责实例化、配置和组装 Bean。
• Bean 的定义与配置: Bean 是 Spring 容器管理的对象。Bean 的定义描述了如何创建和配置一个对象。可以通过 XML 或 JavaConfig 方式配置 Bean。
• 依赖注入的实现方式: Spring 支持多种注入方式,最常用的是:
• 构造器注入: 通过 Bean 的构造器参数注入依赖。
• Setter 方法注入: 通过 Bean 的 Setter 方法注入依赖。
• 字段注入: 直接在字段上使用注解注入(不推荐,影响可测试性)。
• @Component: 标记一个类为 Spring 组件。Spring 容器会自动扫描并将其注册为 Bean。它是 @Controller, @Service, @Repository 的通用原型注解。
• @Service: 标记一个类为业务逻辑层组件。是 @Component 的一个特化,更具语义性,通常用于 Service 层。
• @Repository: 标记一个类为数据访问层组件。是 @Component 的一个特化,更具语义性,通常用于 DAO/Repository 层。Spring 为 Repository 提供了一些额外特性(如异常转换)。
• @Autowired: 最常用的依赖注入注解。 可以用在构造器、Setter 方法、字段上。Spring 会根据类型在容器中查找匹配的 Bean 进行注入。
• @Autowired 默认是按类型匹配。如果同类型有多个 Bean,会进一步按名称匹配(变量名)。
• 可以使用 @Autowired(required = false) 使依赖成为可选。
• @Qualifier: 当使用 @Autowired 按类型注入时,如果容器中有多个相同类型的 Bean,可以使用 @Qualifier 指定按名称注入哪个 Bean。
• 示例:@Autowired @Qualifier(“mySpecificBean”) private MyInterface myDependency;
• @Resource: (JSR-250 标准注解,Spring 也支持) 默认按名称注入。如果指定了 name 属性,则按名称注入;如果没有指定 name,则先按名称(字段名/Setter名)查找,找不到再按类型查找。
使用构建工具 (Maven 或 Gradle) 创建一个 Web 项目,并添加 Spring MVC 核心依赖。
在 IDE 中创建 Maven Project,选择 maven-archetype-webapp 或类似的 Web 应用骨架。
编辑 pom.xml 文件,添加 Spring MVC 及相关依赖:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.20version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>javax.servlet.jsp-apiartifactId>
<version>2.3.3version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jstlartifactId>
<version>1.2version>
dependency>
配置 web.xml 初始化 DispatcherServlet (Servlet 3.0 之前的标准方式):
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Applicationdisplay-name>
<servlet>
<servlet-name>dispatcherservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/spring-mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
说明:/ 表示 DispatcherServlet 会处理所有请求(除了 .jsp 默认由容器处理)。
在 /WEB-INF/ 目录下创建 spring-mvc.xml Spring MVC 配置文件(基于 XML 配置方式):
<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.yourpackage.controller"/>
<context:component-scan base-package="com.yourpackage.service"/>
<context:component-scan base-package="com.yourpackage.repository"/>
<mvc:annotation-driven/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
bean>
<mvc:resources mapping="/resources/**" location="/resources/"/>
beans>
说明:你需要创建 src/main/webapp/WEB-INF/views/ 和 src/main/webapp/resources/ 目录。
Servlet 3.0+ 规范允许通过代码配置 Servlet 容器,Spring MVC 提供了抽象基类简化配置。
不再需要 web.xml 文件(或者 web.xml 仅用于一些兼容性配置)。
创建一个配置类,继承 AbstractAnnotationConfigDispatcherServletInitializer:
package com.yourpackage.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
// 配置 Service/Repository 等 Bean 的 Spring Root Context
return new Class<?>[]{RootConfig.class}; // 可以是 null 如果没有 Root Context
}
@Override
protected Class<?>[] getServletConfigClasses() {
// 配置 Controller, ViewResolver 等 Spring MVC Bean 的 Servlet Context
return new Class<?>[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
// 将所有请求映射到 DispatcherServlet
return new String[]{"/"};
}
// 可选:配置 DispatcherServlet 的其他属性,如文件上传等
// @Override
// protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// registration.setInitParameter("contextConfigLocation", "/WEB-INF/spring-mvc.xml"); // 如果仍然需要加载 XML
// }
}
创建 WebConfig 类,作为 Spring MVC 的配置源:
package com.yourpackage.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration // 标记这是一个配置类
@EnableWebMvc // 启用 Spring MVC 的注解驱动功能
@ComponentScan(basePackages = {"com.yourpackage.controller", "com.yourpackage.service", "com.yourpackage.repository"}) // 扫描组件
public class WebConfig implements WebMvcConfigurer { // 推荐实现 WebMvcConfigurer 进行更多配置
@Bean // 将方法的返回值注册为 Spring Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
// 配置静态资源处理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
// 其他配置... 如拦截器、格式化器等
}
(可选)创建 RootConfig 类,用于配置非 Web 层的 Bean:
package com.yourpackage.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@ComponentScan(basePackages = {"com.yourpackage"}, // 扫描整个应用包
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)}) // 排除掉 WebConfig,避免重复扫描 Controller 等
public class RootConfig {
// 配置数据源、事务管理器、Service、Repository 等非 Web 组件
}
说明:Root Context 通常包含业务逻辑和数据访问层的 Bean,而 Servlet Context 包含 Web 层的 Bean (如 Controller, ViewResolver)。DispatcherServlet 创建自己的 Servlet Context,它持有对 Root Context 的引用,可以访问 Root Context 中的 Bean。这样可以将 Web 层与业务层/数据层解耦。
• 构建工具: 强烈推荐使用 Maven 或 Gradle 管理项目依赖,它们可以简化版本管理和构建流程。
• 依赖管理: 明确项目中需要的依赖,避免引入不必要的库,关注依赖的版本兼容性。
• 配置文件分离: 将 Spring MVC 的配置(如 ViewResolver, HandlerAdapter 等)与业务逻辑/数据访问层的配置分开管理(尤其是在使用 JavaConfig 时通过 Root Context 和 Servlet Context)。
• 组件扫描范围: 明确 context:component-scan 或 @ComponentScan 的 base-package 范围,只扫描需要 Spring 管理的包,提高启动速度和减少潜在冲突。
• Servlet 版本: 了解你使用的 Servlet 容器(如 Tomcat, Jetty)支持的 Servlet API 版本,选择匹配的 javax.servlet-api 依赖。推荐使用 Servlet 3.0+ 以支持 Java Config。
深入理解 Spring MVC 的核心组件及其协作方式是掌握框架的关键。本模块将详细解析 DispatcherServlet 和其他核心组件,并阐述请求处理的完整流程。
• 前端控制器模式: DispatcherServlet 是 Spring MVC 中的核心,扮演着前端控制器 (Front Controller) 的角色。所有进入应用的 Web 请求首先都会被它接收。它负责将请求分发给具体的处理器 (Controller),并协调整个请求处理过程。这种模式的好处是提供了一个统一的入口,便于集中管理和配置。
• Spring MVC 核心组件: Spring MVC 的请求处理流程是由一系列可插拔的核心组件协作完成的。理解这些组件的功能和交互是理解框架工作原理的关键。
DispatcherServlet 的工作流程是一个请求从接收到响应的完整生命周期。以下是主要的组件及其在流程中的作用:
DispatcherServlet (前端控制器):
作用: 统一接收所有请求,根据请求信息(如 URL、请求方法)查找合适的处理器(Controller)。协调其他组件完成请求处理,并将结果发送给客户端。
配置: 在 web.xml 中配置 和 ,或使用 Java Config (AbstractAnnotationConfigDispatcherServletInitializer) 进行配置,指定其监听的 URL 模式。
初始化: 容器启动时,DispatcherServlet 会被初始化,它会创建或加载 Spring 容器 (ApplicationContext),并在容器中查找其需要的各种组件 Bean (如 HandlerMapping, HandlerAdapter, ViewResolver 等)。
HandlerMapping (处理器映射器):
作用: 根据请求信息(URL、Header、参数等),查找能够处理该请求的处理器 (Handler,通常是 Controller 中的某个方法)。
Spring MVC 内置实现: RequestMappingHandlerMapping 是处理 @RequestMapping 注解的主要实现。
配置: 通常不需要手动配置 RequestMappingHandlerMapping,因为它在 mvc:annotation-driven/ 或 @EnableWebMvc 生效时会自动注册。
HandlerAdapter (处理器适配器):
作用: DispatcherServlet 找到 Handler 后,并不能直接调用它。HandlerAdapter 的作用是根据找到的 Handler 类型,以统一的方式调用 Handler 的方法。因为 Handler 可以是多种类型(如实现了特定接口、使用了特定注解等),HandlerAdapter 提供了适配能力。
Spring MVC 内置实现: RequestMappingHandlerAdapter 是处理带有 @RequestMapping 注解的 Controller 方法的主要实现。它负责解析方法参数、执行方法、处理返回值等。
配置: 通常不需要手动配置 RequestMappingHandlerAdapter,它在 mvc:annotation-driven/ 或 @EnableWebMvc 生效时会自动注册。
Controller (处理器/控制器):
作用: 包含具体的业务处理逻辑。接收 HandlerAdapter 传递的请求参数,调用 Service 层等进行业务处理,然后返回处理结果(通常是 ModelAndView、字符串视图名、或者直接写入响应体)。
配置: 使用 @Controller 或 @RestController 标记,并通过 @ComponentScan 扫描注册为 Spring Bean。
ModelAndView (模型与视图):
作用: 封装了 Controller 处理完请求后的结果。包含两部分:Model (数据) 和 View Name (逻辑视图名)。
Model: 是一个 Map 结构,存储了需要传递给视图的数据。
View Name: 是一个字符串,逻辑上代表一个视图(如 “userList”, “addUserForm”)。DispatcherServlet 会将其交给 ViewResolver 进行解析。
ViewResolver (视图解析器):
作用: 根据 Controller 返回的逻辑视图名,解析出实际的视图对象 (View)。视图对象可以是 JSP、HTML 文件路径,或者其他视图技术(如 Thymeleaf、FreeMarker)的 View 实现。
Spring MVC 内置实现: InternalResourceViewResolver (用于解析 JSP 或静态 HTML)、ThymeleafViewResolver 等。
配置: 需要在 Spring MVC 配置文件(XML 或 Java Config)中明确配置,指定视图文件存放的位置(前缀)和文件类型(后缀)。
View (视图):
作用: 是一个接口,表示具体的视图实现(如 JSP 页面)。它负责接收 Model 中的数据,并根据视图模板渲染最终的响应内容(如 HTML)。
其他辅助组件:
MultipartResolver:处理文件上传请求。
LocaleResolver:用于国际化,解析用户的区域设置。
ThemeResolver:用于主题切换。
以下是 DispatcherServlet 处理请求的详细步骤:
+----------------+ +-----------------+ +-----------------+ +-------------+ +--------------+
| User Request | --> | DispatcherServlet | --> | HandlerMapping | --> | Controller | --> | ModelAndView |
+----------------+ +-----------------+ +-----------------+ +-------------+ +--------------+
| ^ ^ | ^ |
| | | | | |
|请求找到Controller |找到HandlerAdapter | 执行业务逻辑 |返回ModelAndView
| | | | | |
v | | v | v
+-----------------+ +-----------------+ +-------------+ +-------------+
| HandlerAdapter | --> | DispatcherServlet | --> | ViewResolver| --> | View |
+-----------------+ +-----------------+ +-------------+ +-------------+
^ | | |
|调用Controller方法 |解析View Name |找到View对象 |渲染视图
| | | |
+-----------------------------+---------------------+-----------------+
|
v
+------------+
| Response |
+------------+
用户发起请求: 用户在浏览器中输入 URL 或提交表单,请求被发送到 Web 服务器(如 Tomcat)。
服务器接收请求: Web 服务器接收到请求,根据 web.xml 或 Java Config 中的配置,将匹配的请求转发给 DispatcherServlet。
DispatcherServlet 接收请求: DispatcherServlet 作为前端控制器接收到请求。
查找 Handler: DispatcherServlet 请求 HandlerMapping,根据请求信息(如 URL)查找能够处理该请求的处理器 (Handler)。
HandlerMapping 返回 Handler: HandlerMapping 找到匹配的 Handler(例如,带有 @RequestMapping 注解的 Controller 方法),将其返回给 DispatcherServlet。
查找 HandlerAdapter: DispatcherServlet 根据返回的 Handler,查找合适的 HandlerAdapter,因为不同的 Handler 可能需要不同的调用方式。
HandlerAdapter 调用 Handler: HandlerAdapter 调用 Handler(即执行 Controller 方法)。在调用 Controller 方法之前,HandlerAdapter 还会处理方法参数的解析、数据绑定等。
Controller 执行业务逻辑: Controller 方法执行应用程序的业务逻辑,可能调用 Service 层或数据访问层。
Controller 返回 ModelAndView/其他: Controller 方法执行完毕后,通常返回 ModelAndView 对象、逻辑视图名 (String)、或者直接将数据写入响应体 (@ResponseBody)。
处理返回值: DispatcherServlet 根据 Controller 的返回值类型进行处理。
如果返回 ModelAndView 或逻辑视图名,DispatcherServlet 将请求 ViewResolver 进行视图解析。
如果使用了 @ResponseBody,DispatcherServlet 将返回值通过 HttpMessageConverter 直接写入响应体。
ViewResolver 解析视图: 如果需要视图渲染,DispatcherServlet 请求 ViewResolver,根据逻辑视图名解析得到实际的 View 对象。
View 渲染: DispatcherServlet 请求 View 对象进行渲染。View 负责使用 Model 中的数据,结合视图模板生成最终的响应内容(如 HTML)。
DispatcherServlet 完成响应: DispatcherServlet 将渲染生成的响应返回给 Web 服务器,最终由服务器发送给客户端浏览器。
• 理解流程是关键: 多次回顾并理解 DispatcherServlet 的工作流程图,这是掌握 Spring MVC 的核心。
• 配置 DispatcherServlet: 在 Servlet 3.0+ 环境下,推荐使用 Java Config (AbstractAnnotationConfigDispatcherServletInitializer) 代替 web.xml 配置 DispatcherServlet,更加灵活和类型安全。
• 组件自动配置: 启用 mvc:annotation-driven/ 或 @EnableWebMvc 会自动注册许多核心组件(如 RequestMappingHandlerMapping, RequestMappingHandlerAdapter, ContentNegotiatingViewResolver, HttpMessageConverter 等),简化配置。
• 自定义组件: 如果需要自定义特定的组件(如自定义 ViewResolver),可以通过在 Spring MVC 配置类或 XML 文件中定义相应的 Bean 来覆盖默认配置。
• 日志查看: 在开发过程中,开启 Spring 的日志(如 DEBUG 级别),可以观察到 DispatcherServlet 如何接收请求、查找 Handler、解析视图等过程,有助于理解内部工作原理。
本模块学习如何编写 Spring MVC 控制器,并掌握接收和处理用户请求的各种方式。
• Controller (控制器): 在 Spring MVC 中,任何被 @Controller 或 @RestController 注解标记的类都可以作为控制器。控制器负责接收 HTTP 请求,调用业务逻辑处理,并决定返回什么响应(视图或数据)。
• 请求映射: 需要将特定的 HTTP 请求 URL 映射到 Controller 类的某个方法上,这个过程通过 @RequestMapping 系列注解来实现。
• @Controller: 标记一个类是 Spring MVC 控制器。Spring 会扫描并注册此类为 Bean,并能够处理 Web 请求。通常配合 @RequestMapping 使用。
• @RequestMapping: 核心请求映射注解。
• 可以应用在类上:指定基础 URL 路径,该类中所有方法的映射路径都会基于这个基础路径。
• 可以应用在方法上:指定方法处理的具体 URL 路径。
• 常用参数:
• value 或 path:指定请求的 URL 路径(支持 Ant 风格路径匹配和路径变量)。
• 示例:/users, /users/{id}, /files/*.txt
• method: 指定支持的 HTTP 请求方法 (RequestMethod 枚举,如 RequestMethod.GET, RequestMethod.POST)。
• 示例:method = RequestMethod.GET
• params: 指定请求必须包含或不包含某个参数。
• 示例:params = “action=save”, params = {“!id”, “type=user”}
• headers: 指定请求必须包含或不包含某个特定的请求头。
• 示例:headers = “Accept=application/json”, headers = {“!Content-Type”, “X-MyHeader=someValue”}
• consumes: 指定请求的 Content-Type 头,即客户端发送的数据类型(用于 @RequestBody)。
• 示例:consumes = “application/json”, consumes = {“!text/plain”, “application/xml”}
• produces: 指定响应的 Content-Type 头,即服务器返回的数据类型(用于 @ResponseBody)。
• 示例:produces = “application/json”, produces = “text/plain;charset=UTF-8”
• HTTP 方法快捷注解 (Spring 4.3+ 推荐): 这些注解是 @RequestMapping 的变体,语义更清晰。
• @GetMapping: 相当于 @RequestMapping(method = RequestMethod.GET)
• @PostMapping: 相当于 @RequestMapping(method = RequestMethod.POST)
• @PutMapping: 相当于 @RequestMapping(method = RequestMethod.PUT)
• @DeleteMapping: 相当于 @RequestMapping(method = RequestMethod.DELETE)
• @PatchMapping: 相当于 @RequestMapping(method = RequestMethod.PATCH)
• @RequestParam: 用于获取请求参数 (Query Parameter 或 Form Parameter)。
• 可以绑定请求 URL 中的查询参数(如 ?id=123)或 POST 请求体中的表单参数到 Controller 方法参数。
• 常用参数:
• value 或 name: 指定请求参数的名称。如果方法参数名与请求参数名一致,可以省略。
• required: boolean 类型,默认为 true。如果设置为 false,表示该参数不是必需的,请求中可以不包含此参数。
• defaultValue: 当请求中不包含该参数时,提供一个默认值。
• @PathVariable: 用于获取 URI 模板变量 (路径参数)。
• 当 @RequestMapping 路径中包含 {variable} 这样的占位符时,使用 @PathVariable 将占位符的值绑定到方法参数。
• 示例:@GetMapping(“/users/{userId}”) public String getUser(@PathVariable(“userId”) Long userId)
• 常用参数:
• value: 指定要绑定的 URI 模板变量名。如果方法参数名与模板变量名一致,可以省略。
• required: boolean 类型,默认为 true。路径变量通常是必需的。
• @RequestHeader: 用于获取请求头 (Request Header) 的值。
• 示例:@GetMapping(“/”) public String handleRequest(@RequestHeader(“User-Agent”) String userAgent)
• @CookieValue: 用于获取 Cookie 的值。
• 示例:@GetMapping(“/”) public String handleRequest(@CookieValue(“JSESSIONID”) String sessionId)
• @ModelAttribute:
• 用在方法参数上:将请求参数绑定到一个对象(通常是 JavaBean),常用于处理表单提交。Spring MVC 会自动创建该对象实例,然后填充请求参数中与对象属性同名的值。
• 示例:@PostMapping(“/users”) public String addUser(@ModelAttribute(“user”) User user)
• 用在方法上:标记一个方法,该方法的返回值会作为 Model 属性添加到所有由该 Controller 处理的请求的 Model 中。常用于为表单提供预填充数据或提供所有视图共享的数据。
• 示例:@ModelAttribute(“user”) public User setupForm()
以下示例展示一个简单的用户控制器,处理用户列表显示、详情查看和用户创建表单。
package com.yourpackage.controller;
import com.yourpackage.model.User; // 假设有一个 User 类
import com.yourpackage.service.UserService; // 假设有一个 UserService 接口/类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; // 用于传递数据到视图
import org.springframework.web.bind.annotation.*; // 包含大部分常用注解
import org.springframework.web.servlet.ModelAndView; // 另一种传递数据和视图的方式
import java.util.List;
@Controller // 标记为控制器
@RequestMapping("/users") // 类级别的基础路径
public class UserController {
private final UserService userService; // 依赖注入 UserService
@Autowired // 构造器注入推荐方式
public UserController(UserService userService) {
this.userService = userService;
}
// 处理 GET 请求 /users,显示用户列表
// 返回字符串表示逻辑视图名
@GetMapping // 相当于 @RequestMapping(method = RequestMethod.GET)
public String listUsers(Model model) {
List<User> userList = userService.getAllUsers(); // 调用 Service 获取数据
model.addAttribute("users", userList); // 将数据添加到 Model
return "userList"; // 返回视图名,ViewResolver 会解析到 /WEB-INF/views/userList.jsp (如果配置的是 InternalResourceViewResolver)
}
// 处理 GET 请求 /users/{userId},显示用户详情
// 使用 @PathVariable 获取路径变量
// 使用 ModelAndView 返回数据和视图
@GetMapping("/{userId}")
public ModelAndView showUserDetail(@PathVariable("userId") Long userId) {
User user = userService.getUserById(userId); // 调用 Service 获取用户
ModelAndView mav = new ModelAndView("userDetail"); // 创建 ModelAndView,指定视图名
mav.addObject("user", user); // 添加模型数据
return mav;
}
// 处理 GET 请求 /users/new,显示创建用户表单
// @ModelAttribute 用在方法上,为表单提供一个空的 User 对象
@GetMapping("/new")
@ModelAttribute("user") // 这个方法返回的对象会以名称 "user" 添加到 Model 中
public User setupUserForm() {
return new User(); // 返回一个空的 User 对象供表单绑定
}
// 处理 POST 请求 /users,提交创建用户表单
// @ModelAttribute 用在方法参数上,绑定表单数据到 User 对象
// @RequestParam 用于获取非对象属性的简单参数,如这里的 redirect 参数
@PostMapping
public String addUser(@ModelAttribute("user") User user,
@RequestParam(value = "redirect", required = false, defaultValue = "false") boolean redirectToList,
Model model) {
// 简单的表单验证(实际应用中更复杂)
if (user.getUsername() == null || user.getUsername().isEmpty()) {
model.addAttribute("error", "Username cannot be empty");
return "userForm"; // 返回表单视图,显示错误
}
userService.saveUser(user); // 调用 Service 保存用户
if (redirectToList) {
// 重定向到用户列表页面
return "redirect:/users";
} else {
// 转发到用户详情页面(假设根据保存的用户ID重定向)
// 转发和重定向将在模块六详细讲解
// return "forward:/users/" + user.getId(); // 示例,假设 User 有 getId() 方法
// 这里简化为返回成功页
return "userSuccess"; // 返回成功视图
}
}
// 示例:获取请求参数和请求头
@GetMapping("/example")
public String exampleParams(@RequestParam(value = "name", required = false) String name,
@RequestHeader("Accept-Language") String acceptLanguage,
Model model) {
model.addAttribute("name", name);
model.addAttribute("language", acceptLanguage);
return "exampleView";
}
}
package com.yourpackage.service;
import com.yourpackage.model.User;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
// 假设这里会引入 Repository/DAO 层
@Service // 标记为 Service 层组件
public class UserServiceImpl implements UserService {
// 模拟数据,实际应从数据库获取
private List<User> dummyUsers = Arrays.asList(
new User(1L, "Alice"),
new User(2L, "Bob")
);
@Override
public List<User> getAllUsers() {
System.out.println("UserService: Getting all users");
return dummyUsers; // 模拟返回数据
}
@Override
public User getUserById(Long id) {
System.out.println("UserService: Getting user with ID: " + id);
// 模拟查找
return dummyUsers.stream()
.filter(user -> user.getId().equals(id))
.findFirst()
.orElse(null);
}
@Override
public void saveUser(User user) {
System.out.println("UserService: Saving user: " + user.getUsername());
// 模拟保存逻辑
// 在真实应用中,这里会调用 Repository/DAO 层的 save 方法
// dummyUsers.add(user); // 简单添加到模拟列表 (注意:List 的 add 不会修改原始 Arrays.asList 的列表)
// 在实际应用中,保存后可能需要更新 dummyUsers 或重新加载数据
}
}
package com.yourpackage.service;
import com.yourpackage.model.User;
import java.util.List;
public interface UserService {
List<User> getAllUsers();
User getUserById(Long id);
void saveUser(User user);
}
package com.yourpackage.model;
// 简单的 POJO 作为模型
public class User {
private Long id;
private String username;
// 需要一个无参构造器供 Spring MVC 数据绑定使用
public User() {
}
public User(Long id, String username) {
this.id = id;
this.username = username;
}
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
参考模块一的 Spring MVC 配置,确保 @ComponentScan 扫描到 com.yourpackage.controller 和 com.yourpackage.service 包。
• /WEB-INF/views/userList.jsp: 显示用户列表
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
User List
User List
ID
Username
Actions
<%-- 遍历 Model 中的 "users" 数据 --%>
${user.id}
${user.username}
View Detail
• /WEB-INF/views/userDetail.jsp: 显示用户详情
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
User Detail
User Detail
ID: ${user.id}
<%-- 显示 Model 中的 "user" 对象的属性 --%>
Username: ${user.username}
• /WEB-INF/views/userForm.jsp: 显示创建用户表单
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> <%-- Spring 表单标签库 --%>
Add New User
Add New User
${error}
<%-- form:form 标签会自动绑定到 Model 中名为 "user" 的对象 --%>
<%-- form:input 绑定到 user 对象的 username 属性 --%>
<%-- 示例:一个额外的非绑定字段,用于演示 @RequestParam --%>
注意:使用 Spring 的 标签需要添加 spring-webmvc 依赖,并确保 JSP 页面头部引入了对应的标签库。
• /WEB-INF/views/userSuccess.jsp: 显示成功页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
Success
Operation Successful!
• 区分 @Controller 和 @RestController: 如果控制器主要返回视图(如 JSP, Thymeleaf 模板),使用 @Controller。如果主要用于构建 RESTful API 返回 JSON/XML 等数据,使用 @RestController(详见模块五)。
• 使用快捷注解: 优先使用 @GetMapping, @PostMapping 等注解代替 @RequestMapping(method = …),代码更简洁易读。
• 类级别 @RequestMapping: 在 Controller 类上定义基础路径,有助于组织 URL 结构,避免重复。
• ** @RequestParam 的 required 和 defaultValue:** 合理使用这两个参数可以使控制器方法更健壮,处理可选参数和提供默认值。
• ** @PathVariable:** 用于从 URL 路径本身获取参数,适合表示资源的 ID 等。
• ** @ModelAttribute 处理表单:** @ModelAttribute 是处理复杂表单绑定的强大工具,它可以将整个表单数据绑定到一个 JavaBean 对象,简化参数接收。
• Controller 方法参数类型: Controller 方法支持多种参数类型,除了 @RequestParam, @PathVariable, @ModelAttribute 绑定的参数外,还可以直接注入 HttpServletRequest, HttpServletResponse, HttpSession, Model, RedirectAttributes 等对象。
本模块深入学习 Spring MVC 如何将请求数据绑定到 Java 对象,以及如何使用视图技术将 Model 中的数据呈现给用户。
• 数据绑定 (Data Binding): Spring MVC 核心功能之一,它能够自动将 HTTP 请求中的参数值转换为 Controller 方法参数或 JavaBean 对象的属性值。这大大简化了从请求中获取数据的过程。
• 表单处理: Web 应用中常见的交互方式是用户填写表单并提交。Spring MVC 提供方便的方式接收、绑定和校验表单数据。
• 视图解析与渲染: Controller 处理完请求后,需要将结果呈现给用户。ViewResolver 负责根据 Controller 返回的逻辑视图名找到具体的视图,而 View 负责结合 Model 中的数据和视图模板生成最终的响应内容。
• HandlerAdapter 中的数据绑定: 数据绑定主要发生在 HandlerAdapter 调用 Controller 方法之前。RequestMappingHandlerAdapter 使用 WebDataBinder 来完成数据绑定。
• WebDataBinder: 负责将请求参数绑定到目标对象(如方法参数或 @ModelAttribute 对象)。它能够进行类型转换(字符串转数字、日期等),并支持注册自定义的 PropertyEditor 或 Converter 来处理复杂的类型转换。
• ModelAndView: 在模块二已介绍,用于携带模型数据和逻辑视图名从 Controller 返回到 DispatcherServlet。
• ViewResolver: 在模块二已介绍,根据逻辑视图名解析出 View 对象。
• View: 在模块二已介绍,负责使用 Model 数据渲染视图。常见的视图技术有:
• JSP (JavaServer Pages): 传统的 Java Web 视图技术,通过 JSP 标签、EL 表达式和 JSTL 来访问 Model 数据并生成 HTML。
• Thymeleaf: 一种现代的服务器端 Java 模板引擎,语法自然,可以直接在浏览器中打开模板文件进行静态原型设计。与 Spring 深度集成。
• FreeMarker: 另一种流行的模板引擎。
• 其他: Velocity, Groovy Templates, JSON View 等。
请求到达 Controller 方法。
HandlerAdapter (具体如 RequestMappingHandlerAdapter) 准备调用该方法。
HandlerAdapter 识别方法参数(如 @RequestParam, @PathVariable, @ModelAttribute)。
对于需要绑定的参数(如 @ModelAttribute 修饰的 User 对象):
HandlerAdapter 创建或获取 WebDataBinder。
WebDataBinder 获取请求中的相关参数。
WebDataBinder 将参数值绑定到目标对象的相应属性上,进行类型转换和格式化。
绑定成功后,将填充好数据的对象作为参数传递给 Controller 方法。
对于 @RequestParam 或 @PathVariable 等简单类型参数:
HandlerAdapter 直接将请求参数或路径变量的值进行类型转换后作为方法参数。
Controller 方法执行完毕,返回 ModelAndView 或逻辑视图名。
DispatcherServlet 接收到返回值。
DispatcherServlet 找到配置好的 ViewResolver。
DispatcherServlet 调用 ViewResolver 的 resolveViewName() 方法,传入逻辑视图名和 Locale 等信息。
ViewResolver 根据其内部规则(如前后缀配置)解析逻辑视图名,找到对应的实际视图资源(如 /WEB-INF/views/userList.jsp),并返回一个 View 对象。
DispatcherServlet 调用 View 对象的 render() 方法,传入 Model 数据和 Request/Response 对象。
View 对象(例如 JSP 引擎)使用 Model 中的数据,结合视图模板文件生成最终的响应内容(HTML),并写入 HttpServletResponse。
DispatcherServlet 完成响应。
代码示例主要基于模块三的 UserController、User 模型和 JSP 视图,这里补充 @ModelAttribute 在方法参数上的详细说明,以及视图解析器的配置。
在 UserController 的 addUser 方法中:
@PostMapping // 处理 POST 请求 /users
public String addUser(@ModelAttribute("user") User user, // <-- 数据绑定发生在这里
@RequestParam(value = "redirect", required = false, defaultValue = "false") boolean redirectToList,
Model model) {
// ... 方法体
}
当请求(例如表单提交)到达 /users 且方法是 POST 时,Spring MVC 执行以下步骤:
看到 addUser 方法的 @ModelAttribute(“user”) User user 参数。
查找 Model 中是否有名为 “user” 的属性。
如果在 Controller 中有 @ModelAttribute(“user”) public User setupForm() 方法,并且该方法在当前请求处理之前执行了(例如,因为它是 GET /users/new 请求的处理方法,且 POST /users 提交时可能是在同一个会话中),Spring MVC 会使用 setupForm() 方法返回的 User 对象作为绑定目标。
如果 Model 中没有名为 “user” 的属性,Spring MVC 会使用 User 类的无参构造器创建一个新的 User 对象作为绑定目标。
WebDataBinder 开始将请求参数绑定到这个 User 对象。例如,如果请求包含参数 username=Alice&id=123,WebDataBinder 会调用 user.setUsername(“Alice”) 和 user.setId(123L)。
绑定成功后,将填充好数据的 user 对象作为参数传递给 addUser 方法。
无论 XML 还是 JavaConfig,配置 ViewResolver 是将逻辑视图名映射到实际视图文件的关键。
XML 配置 (spring-mvc.xml):
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
bean>
说明:逻辑视图名 “userList” 会被解析为 /WEB-INF/views/userList.jsp。
JavaConfig 配置 (WebConfig.java):
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
说明:效果同上。
首先,需要在 pom.xml 中添加 Thymeleaf 的 Spring 集成依赖:
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
<version>3.0.11.RELEASEversion>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
<scope>providedscope>
dependency>
然后,在 JavaConfig (WebConfig.java) 中配置 Thymeleaf 相关的 Bean:
package com.yourpackage.config;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.yourpackage.controller", "com.yourpackage.service", "com.yourpackage.repository"})
public class WebConfig implements WebMvcConfigurer, ApplicationContextAware { // 实现 ApplicationContextAware 获取 ApplicationContext
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
// 配置模板资源解析器
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("/WEB-INF/templates/"); // Thymeleaf 模板存放路径,通常放在 WEB-INF 下
templateResolver.setSuffix(".html"); // 模板后缀
templateResolver.setTemplateMode(TemplateMode.HTML); // 模板模式
templateResolver.setCacheable(false); // 开发时建议关闭缓存
return templateResolver;
}
// 配置模板引擎
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver()); // 设置模板资源解析器
templateEngine.setEnableSpringELCompiler(true); // 启用 Spring EL 表达式
return templateEngine;
}
// 配置视图解析器
@Bean
public ViewResolver thymeleafViewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine()); // 设置模板引擎
viewResolver.setCharacterEncoding("UTF-8"); // 设置编码
return viewResolver;
}
// 配置静态资源处理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
说明:你需要创建 src/main/webapp/WEB-INF/templates/ 目录来存放 Thymeleaf 模板文件 (.html)。
• /WEB-INF/templates/userList.html (Thymeleaf 示例):
DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>User Listtitle>
head>
<body>
<h1>User Listh1>
<table>
<thead>
<tr>
<th>IDth>
<th>Usernameth>
<th>Actionsth>
tr>
thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.id}">1td>
<td th:text="${user.username}">Alicetd>
<td><a th:href="@{/users/{id}(id=${user.id})}">View Detaila>td>
tr>
tbody>
table>
<p><a th:href="@{/users/new}">Add New Usera>p>
body>
html>
• 选择合适的视图技术: JSP 适用于传统 Web 应用,Thymeleaf 和 FreeMarker 更适合现代应用,它们语法简洁,支持 HTML 原型设计。RESTful API 通常不需要服务器端视图渲染。
• 数据绑定: @ModelAttribute 是处理复杂对象(如表单)绑定的首选。确保要绑定的对象有无参构造器和对应的 Setter 方法。
• 类型转换: Spring MVC 提供了默认的类型转换器,但对于复杂或自定义类型的绑定,可能需要注册自定义的 Converter 或 Formatter。
• 数据校验: 通常在数据绑定后立即进行数据校验(Validation),Spring MVC 集成了 Bean Validation (JSR 303/380)。虽然不是本模块核心,但在处理表单时非常重要。
• 避免在视图中写业务逻辑: 视图只负责数据显示和页面结构,所有业务逻辑应放在 Controller 或 Service 层。
• 视图文件存放位置: 将视图文件放在 /WEB-INF/ 目录下是最佳实践,这样可以防止用户通过直接 URL 访问视图文件,只能通过 Controller 访问。
• 静态资源处理: 使用 mvc:resources 或 addResourceHandlers 配置处理静态资源(CSS, JS, 图片等),避免 DispatcherServlet 处理这些请求。
本模块学习如何使用 Spring MVC 构建 RESTful 风格的 Web 服务接口,实现前后端数据交互。
• RESTful (Representational State Transfer): 一种架构风格,用于设计网络应用程序。RESTful 服务基于 HTTP 协议,强调无状态、客户端-服务器分离、统一接口等原则。核心概念包括:
• 资源 (Resource): Web 上的命名事物(如用户、订单)。每个资源有一个唯一的 URI 来标识。
• 资源操作: 对资源的操作通过 HTTP 方法来表示,如 GET (获取), POST (创建), PUT (更新), DELETE (删除)。
• 表述 (Representation): 资源的状态通过某种格式(如 JSON, XML)来表述,在请求和响应中传输。
• 无状态 (Stateless): 服务器不保存客户端的状态信息。每次请求都包含处理该请求所需的所有信息。
• 数据交互格式: RESTful API 常使用 JSON (JavaScript Object Notation) 或 XML 作为数据交换格式,因为它们易于解析和跨平台传输。JSON 更加轻量和常用。
• @ResponseBody: 用在 Controller 方法上。 表示该方法的返回值不会被当做视图名,而是直接写入 HTTP 响应体中。常用于返回 JSON 或 XML 数据。Spring MVC 会使用 HttpMessageConverter 将返回值转换为指定的格式。
• @RequestBody: 用在 Controller 方法参数上。 表示方法参数的值来自 HTTP 请求体。Spring MVC 会使用 HttpMessageConverter 将请求体的内容(如 JSON 或 XML)转换为指定类型的 Java 对象。
• @RestController: 用在类上。 是 @Controller 和 @ResponseBody 的组合注解。标记一个类是 RESTful Controller。这意味着该类中所有方法的返回值默认都会直接写入响应体,而不需要额外加 @ResponseBody。简化了 RESTful API 的开发。
• HttpMessageConverter:
• 作用: 负责在 Java 对象和 HTTP 请求/响应体之间进行转换。例如,将 Java 对象转换为 JSON 字符串写入响应体(@ResponseBody),或将请求体中的 JSON 字符串转换为 Java 对象 (@RequestBody)。
• Spring MVC 内置实现: Spring MVC 提供了多种 HttpMessageConverter 实现,如 MappingJackson2HttpMessageConverter (处理 JSON,依赖 Jackson 库)、Jaxb2RootElementHttpMessageConverter (处理 XML,依赖 JAXB 库) 等。
• 配置: mvc:annotation-driven/ 或 @EnableWebMvc 会自动注册常用的 HttpMessageConverter。如果需要处理 JSON/XML,只需要确保项目中引入了 Jackson 或 Gson 等库的依赖。
请求到达 DispatcherServlet。
DispatcherServlet -> HandlerMapping -> 找到带有 @RestController 或 @Controller + @RequestMapping (返回值为 @ResponseBody 方法) 的 Handler。
DispatcherAdapter 调用 Handler 方法。
如果方法参数使用了 @RequestBody:
HandlerAdapter 调用 HttpMessageConverter 将请求体内容反序列化为方法参数对象。
Controller 方法执行业务逻辑。
Controller 方法返回一个 Java 对象。
HandlerAdapter 或 DispatcherServlet 检测到方法或类上有 @ResponseBody (或使用了 @RestController)。
调用 HttpMessageConverter 将返回的 Java 对象序列化为指定格式 (如 JSON)。
将序列化后的数据写入 HTTP 响应体,并设置正确的 Content-Type 头(例如 application/json)。
以下示例基于模块三的用户管理,将其改造为 RESTful API。
package com.yourpackage.controller;
import com.yourpackage.model.User;
import com.yourpackage.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; // HTTP 状态码
import org.springframework.http.ResponseEntity; // 封装响应
import org.springframework.web.bind.annotation.*; // 包含大部分常用注解
import java.util.List;
@RestController // 标记为 RESTful 控制器,等同于 @Controller + 类级别 @ResponseBody
@RequestMapping("/api/users") // API 基础路径
public class UserRestController {
private final UserService userService;
@Autowired
public UserRestController(UserService userService) {
this.userService = userService;
}
// 获取所有用户
// GET /api/users
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers(); // 直接返回 List,会被 HttpMessageConverter 转为 JSON 写入响应体
}
// 获取特定用户
// GET /api/users/{userId}
@GetMapping("/{userId}")
public ResponseEntity<User> getUserById(@PathVariable Long userId) { // 使用 ResponseEntity 可以自定义状态码和头部
User user = userService.getUserById(userId);
if (user != null) {
return new ResponseEntity<>(user, HttpStatus.OK); // 返回用户对象和 200 状态码
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND); // 返回 404 状态码
}
}
// 创建新用户
// POST /api/users
@PostMapping
@ResponseStatus(HttpStatus.CREATED) // 指定响应状态码为 201 Created
public User createUser(@RequestBody User user) { // @RequestBody 将请求体 (JSON) 转换为 User 对象
// 实际应用中,这里会调用 Service 保存用户,并可能设置用户ID等
userService.saveUser(user); // 模拟保存
return user; // 返回保存后的用户对象 (可能包含生成的ID)
}
// 更新用户
// PUT /api/users/{userId}
@PutMapping("/{userId}")
public ResponseEntity<User> updateUser(@PathVariable Long userId, @RequestBody User user) {
User existingUser = userService.getUserById(userId); // 模拟查找现有用户
if (existingUser != null) {
// 模拟更新逻辑
existingUser.setUsername(user.getUsername());
// 实际中会调用 Service 更新
System.out.println("Updating user " + userId + " with new username: " + user.getUsername());
return new ResponseEntity<>(existingUser, HttpStatus.OK); // 返回更新后的用户和 200 状态码
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND); // 返回 404 状态码
}
}
// 删除用户
// DELETE /api/users/{userId}
@DeleteMapping("/{userId}")
@ResponseStatus(HttpStatus.NO_CONTENT) // 指定响应状态码为 204 No Content
public void deleteUser(@PathVariable Long userId) {
// 实际中会调用 Service 删除
System.out.println("Deleting user with ID: " + userId);
// 模拟删除成功,返回 204
}
// 示例:接受简单 JSON 数据
@PostMapping("/search")
public List<User> searchUsers(@RequestBody SearchRequest searchRequest) { // SearchRequest 是一个简单的 POJO { "keyword": "..." }
System.out.println("Searching for keyword: " + searchRequest.getKeyword());
// 模拟根据 keyword 搜索
return userService.getAllUsers(); // 简化,实际会调用根据 keyword 搜索的方法
}
// 示例模型对象
public static class SearchRequest {
private String keyword;
// Getter, Setter, No-arg constructor
public SearchRequest() {}
public String getKeyword() { return keyword; }
public void setKeyword(String keyword) { this.keyword = keyword; }
}
}
使用模块三的 UserService 接口和 UserServiceImpl 实现即可。
使用模块三的 User 类即可。
• XML 配置 (spring-mvc.xml): 确保 mvc:annotation-driven/ 已启用,并且 jackson-databind 等库已添加到依赖。
<mvc:annotation-driven/>
<context:component-scan base-package="com.yourpackage.controller"/>
beans>
• JavaConfig 配置 (WebConfig.java): 确保 @EnableWebMvc 已启用,并且 jackson-databind 等库已添加到依赖。
@Configuration
@EnableWebMvc // 启用 MVC 注解驱动,会自动注册 HttpMessageConverter
@ComponentScan(basePackages = {"com.yourpackage.controller", "com.yourpackage.service", "com.yourpackage.repository"})
public class WebConfig implements WebMvcConfigurer {
// ... ViewResolver 等其他配置,REST API 通常不需要 ViewResolver
// 如果是混合应用,ViewResolver 和 HttpMessageConverter 都会生效
}
除了 Spring MVC 核心依赖,为了处理 JSON,需要添加 Jackson 库:
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.13.0version>
dependency>
• 使用 @RestController: 当开发 RESTful API 时,优先使用 @RestController,它可以省去在每个方法上添加 @ResponseBody 的麻烦。
• 合理的 HTTP 方法: 根据 RESTful 规范,使用正确的 HTTP 方法来表示资源的操作 (GET for retrieve, POST for create, PUT for update/replace, PATCH for partial update, DELETE for delete)。
• 状态码: 返回正确的 HTTP 状态码来表示操作结果 (200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found, 500 Internal Server Error 等)。ResponseEntity 类可以方便地控制响应状态码、头部和体。@ResponseStatus 注解可以用于方法或异常类上设置固定的状态码。
• 请求和响应格式: 在企业开发中,通常使用 JSON 作为 RESTful API 的数据交换格式。确保项目中引入了 Jackson 或 Gson 库,并且 HttpMessageConverter 配置正确(通常由 @EnableWebMvc 或 mvc:annotation-driven/ 自动完成)。
• 版本控制: 对于 RESTful API,考虑如何在 URL 中进行版本控制 (如 /api/v1/users)。
• 统一异常处理: 使用 @ControllerAdvice 和 @ExceptionHandler 实现全局的异常处理,返回统一的错误格式(详见模块六)。
• 数据校验: 对 @RequestBody 接收的对象进行数据校验,通常结合 Bean Validation。
本模块学习 Spring MVC 的一些高级特性,如异常处理、重定向与转发、文件上传下载、拦截器等,并指导如何整合数据访问层,最后引入 Spring Boot。
本模块涵盖 Spring MVC 中一些更复杂但非常实用的功能,它们能够提升应用的健壮性、用户体验和可维护性。
• @ExceptionHandler: 用在 Controller 类或 @ControllerAdvice 类的方法上。 用于处理特定 Controller 或全局范围内发生的特定类型的异常。当 Controller 方法抛出该异常时,被 @ExceptionHandler 标记的方法会被调用来处理异常并生成响应。
• 示例:@ExceptionHandler(UserNotFoundException.class)
• @ControllerAdvice: 用在类上。 标记一个类为全局控制器增强器。此类中的 @ExceptionHandler, @ModelAttribute, @InitBinder 方法会应用到应用程序中所有或指定范围的 @Controller 类上。常用于实现全局异常处理、全局数据绑定预处理等。
• 异常处理:
• 当 Controller 方法执行过程中抛出异常时,Spring MVC 会通过 HandlerExceptionResolver 机制来处理。
• ExceptionHandlerExceptionResolver 是处理 @ExceptionHandler 注解的主要解析器。它会在抛出异常后查找匹配的 @ExceptionHandler 方法来处理异常。
• 重定向与转发:
• 这不是一个独立组件,而是 Controller 方法返回值的一种特殊约定。
• 转发 (Forward): 服务器内部的跳转。客户端只发出一次请求,服务器将请求的处理权交给另一个资源(如另一个 Controller 方法或 JSP 页面)。URL 不变。
• 重定向 (Redirect): 客户端发出两次请求。服务器返回一个重定向响应(状态码 3xx),包含新的 URL。客户端收到响应后向新的 URL 发起第二次请求。URL 改变。
• 文件上传:
• 需要配置 MultipartResolver 组件。它负责解析 multipart/form-data 类型的请求(文件上传请求),将文件内容封装到 MultipartFile 对象中。
• Spring MVC 内置实现: CommonsMultipartResolver (依赖 Apache Commons FileUpload) 和 StandardServletMultipartResolver (基于 Servlet 3.0+ 的文件上传 API)。推荐使用后者。
• 拦截器 (Interceptor):
• Spring MVC 拦截器允许在请求处理流程的特定阶段(Controller 方法执行前、后)插入自定义逻辑。类似于 Servlet 的 Filter,但 Filter 在 DispatcherServlet 之前/后执行,Interceptor 在 DispatcherServlet 内部、HandlerMapping 之后、HandlerAdapter 调用 Controller 之前/后执行。
• 通过实现 HandlerInterceptor 接口(或继承 HandlerInterceptorAdapter,已废弃,现在通常实现 HandlerInterceptor 并提供默认方法实现)来创建拦截器。
• 需要通过配置注册拦截器,指定拦截的 URL 模式。
• HandlerInterceptor 接口的三个方法:
• preHandle(request, response, handler): 在 Controller 方法执行前调用。返回 true 继续执行,返回 false 终止请求。可用于权限检查、日志记录等。
• postHandle(request, response, handler, modelAndView): 在 Controller 方法执行后、视图渲染前调用。可用于修改 Model 数据、修改视图名等。
• afterCompletion(request, response, handler, ex): 在整个请求处理完成后调用(包括视图渲染完成),无论是否发生异常。可用于资源清理等。
使用 @ControllerAdvice 实现全局异常处理。
package com.yourpackage.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
// @ControllerAdvice 默认扫描所有 Controller
// 可以指定扫描的 Controller 包或类
// @ControllerAdvice("com.yourpackage.controller")
@ControllerAdvice
public class AppExceptionHandler {
// 处理特定的 UserNotFoundException 异常,返回 404
@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND) // 设置响应状态码为 404
@ResponseBody // 直接返回响应体 (通常用于 RESTful API)
public ErrorResponse handleUserNotFoundException(UserNotFoundException ex) {
// 返回自定义的错误信息结构
return new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
}
// 处理所有其他未捕获的异常,返回 500
// 可以定义更具体的异常处理方法
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 设置响应状态码为 500
@ResponseBody
public ErrorResponse handleGeneralException(Exception ex) {
// 记录异常日志 (实际应用中应使用日志框架)
ex.printStackTrace();
return new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error: " + ex.getMessage());
}
// 自定义错误响应结构 (POJO)
private static class ErrorResponse {
private int status;
private String message;
public ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
}
public int getStatus() { return status; }
public String getMessage() { return message; }
}
}
// 模拟一个自定义异常
class UserNotFoundException extends RuntimeException {
public UserNotFoundException(Long userId) {
super("User not found with ID: " + userId);
}
}
在 Controller 中可以抛出自定义异常:
// UserRestController 中的 getUserById 方法
@GetMapping("/{userId}")
public ResponseEntity<User> getUserById(@PathVariable Long userId) {
User user = userService.getUserById(userId);
if (user != null) {
return new ResponseEntity<>(user, HttpStatus.OK);
} else {
// 抛出自定义异常
throw new UserNotFoundException(userId);
}
}
在 Controller 方法中,通过返回特定的字符串前缀实现。
// 在 UserController 中
@PostMapping // 处理 POST 请求 /users (创建用户)
public String addUser(@ModelAttribute("user") User user) {
userService.saveUser(user);
// 重定向到用户列表页面
// URL 会变为 /users,浏览器会发起新的 GET 请求
// 注意:重定向会丢失 Model 数据,如果需要传递数据,可以使用 RedirectAttributes (Spring 3.1+)
return "redirect:/users";
}
// 示例:转发到另一个 Controller 方法或视图
@GetMapping("/process")
public String processRequest(@RequestParam("action") String action) {
if ("showForm".equals(action)) {
// 转发到显示表单的方法
// URL 不变,服务器内部跳转
return "forward:/users/new"; // 转发到 UserController 的 /users/new 方法
} else {
// 转发到某个视图
return "forward:/WEB-INF/views/someView.jsp"; // 转发到 JSP 页面
}
}
确保 Servlet 容器支持 Servlet 3.0+(如 Tomcat 7+)。
在配置类 WebConfig.java 中配置 MultipartResolver Bean:
package com.yourpackage.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.yourpackage.controller"})
public class WebConfig implements WebMvcConfigurer {
// 配置 StandardServletMultipartResolver
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
// 其他配置...
}
在 Controller 方法中接收文件:使用 MultipartFile 作为方法参数。
package com.yourpackage.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Controller
@RequestMapping("/files")
public class FileUploadController {
// 处理文件上传表单提交
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
try {
// 获取原始文件名
String originalFilename = file.getOriginalFilename();
// 定义文件保存路径 (实际应用中应更加健壮)
String uploadDir = "/path/to/your/upload/directory/"; // !!! 替换为你的实际路径
File destFile = new File(uploadDir + originalFilename);
// 保存文件到服务器
file.transferTo(destFile);
System.out.println("File uploaded successfully: " + originalFilename);
return "uploadSuccess"; // 返回成功视图
} catch (IOException e) {
e.printStackTrace();
return "uploadFailure"; // 返回失败视图
}
} else {
System.out.println("Uploaded file is empty");
return "uploadFailure"; // 返回失败视图
}
}
// 显示文件上传表单的 GET 请求
@GetMapping("/uploadForm")
public String showUploadForm() {
return "uploadForm"; // 返回上传表单视图 (如 uploadForm.jsp/html)
}
}
上传表单视图 (uploadForm.jsp):
表单的 enctype 必须是 multipart/form-data。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
File Upload
Upload File
<%-- 注意:method 必须是 POST,enctype 必须是 multipart/form-data --%>
实现一个简单的登录检查拦截器。
package com.yourpackage.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
// 在 Controller 方法执行前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("LoginInterceptor preHandle...");
HttpSession session = request.getSession();
// 检查用户是否已登录 (这里简化判断 session 中是否有 "loggedInUser" 属性)
Object user = session.getAttribute("loggedInUser");
if (user != null) {
// 用户已登录,继续处理请求
return true;
} else {
// 用户未登录,重定向到登录页面
System.out.println("User not logged in, redirecting to login page...");
response.sendRedirect(request.getContextPath() + "/login"); // 假设登录页面的 URL 是 /login
return false; // 阻止后续的请求处理
}
}
// 在 Controller 方法执行后,视图渲染前执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("LoginInterceptor postHandle...");
// 可以在这里对 ModelAndView 进行修改
}
// 在整个请求处理完成后执行 (视图渲染后)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("LoginInterceptor afterCompletion...");
// 可以进行资源清理等
}
}
配置拦截器 (在 WebConfig.java 中实现 WebMvcConfigurer):
package com.yourpackage.config;
import com.yourpackage.interceptor.LoginInterceptor; // 引入拦截器
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
// @ComponentScan...
public class WebConfig implements WebMvcConfigurer {
// 将拦截器注册为 Spring Bean
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
// 配置拦截器及其拦截规则
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor()) // 添加拦截器实例
.addPathPatterns("/**") // 拦截所有请求路径
.excludePathPatterns("/login", "/logout", "/resources/**"); // 排除登录、注销、静态资源等路径
}
// 其他配置,如 ViewResolver, MultipartResolver 等
}
在模块三的示例中,UserServiceImpl 模拟了数据访问。在实际应用中,Service 层会通过依赖注入的方式引用 Repository 或 DAO 层 Bean。
package com.yourpackage.service;
import com.yourpackage.model.User;
import com.yourpackage.repository.UserRepository; // 假设有一个 UserRepository 接口/类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository; // 依赖注入 UserRepository
@Autowired
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public List<User> getAllUsers() {
return userRepository.findAll(); // 调用 Repository 获取数据
}
@Override
public User getUserById(Long id) {
return userRepository.findById(id); // 调用 Repository 获取单条数据
}
@Override
public void saveUser(User user) {
userRepository.save(user); // 调用 Repository 保存数据
}
}
需要对应的 UserRepository 接口或类,例如使用 Spring Data JPA:
package com.yourpackage.repository;
import com.yourpackage.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
// 如果使用 Spring Data JPA,只需要定义接口并继承 JpaRepository
// Spring 会自动生成实现
@Repository // 标记为 Repository 层组件
public interface UserRepository extends JpaRepository<User, Long> {
// JpaRepository 提供了 findAll(), findById(), save(), delete() 等方法
// 可以定义查询方法如: List findByUsername(String username);
}
配置数据访问层需要额外的 Spring Context 配置(数据源、JPA/MyBatis 配置、事务管理器等),通常放在 Root Context 中。
• 简化配置: Spring Boot 的核心思想是“约定大于配置”。它提供了大量的 Starter POMs (例如 spring-boot-starter-web 包含了 Spring MVC、嵌入式 Tomcat、Jackson 等常用依赖) 和自动配置 (Auto-configuration)。
• 快速启动: Spring Boot 应用内嵌了 Web 服务器 (Tomcat, Jetty 或 Undertow),可以直接运行 JAR 文件启动应用,无需单独部署到外部 Web 容器。
• Spring Boot 项目结构: 通常有一个主应用类,包含 @SpringBootApplication 注解,它是一个复合注解,包含了 @Configuration, @EnableAutoConfiguration, @ComponentScan。
• 通过 Spring Boot 创建 Spring MVC 应用: 使用 Spring Initializr (start.spring.io) 可以快速生成 Spring Boot 项目骨架,选择 Web 依赖即可。编写 Controller 与原生 Spring MVC 类似,但配置大大简化。
// 简单的 Spring Boot 应用主类
package com.yourpackage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 包含了 @EnableAutoConfiguration 和 @ComponentScan
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args); // 直接运行
}
}
Spring Boot 会自动配置 DispatcherServlet、HandlerMapping、HandlerAdapter、HttpMessageConverter (如果 classpath 中有 Jackson/Gson)、默认的 ViewResolver (取决于依赖,如 Thymeleaf/FreeMarker)。你只需要编写 Controller、Service、Repository 等业务代码即可。
掌握 Spring MVC 原生配置有助于理解框架原理,但实际企业开发中,强烈推荐使用 Spring Boot 来快速构建应用。
• 异常处理: 使用 @ControllerAdvice 和 @ExceptionHandler 实现统一的异常处理,避免在每个 Controller 方法中编写 try-catch 块。返回统一的错误响应格式(尤其是 RESTful API)。
• 重定向与转发的选择: 根据需求选择。重定向用于 POST 请求后的 PRG (Post/Redirect/Get) 模式,避免表单重复提交;转发用于服务器内部的流程跳转,URL 不变,可以共享 Request 属性。
• 文件上传大小限制: 配置 MultipartResolver 时,可以设置文件总大小和单个文件大小的限制,防止恶意上传。
• 拦截器: 适用于跨多个请求处理的横切关注点,如用户认证、权限检查、日志记录、性能监控等。与 Filter 相比,拦截器能访问 Spring 上下文和 Handler 对象。
• 数据访问整合: Service 层依赖 Repository/DAO 层是标准的分层架构。通过 Spring 的依赖注入将它们关联起来。配置数据源、事务管理是关键。
• 拥抱 Spring Boot: 在掌握 Spring MVC 基础后,立即学习 Spring Boot。它极大地提高了开发效率,是现代 Spring 应用开发的标准。
学习建议再次强调:
理论与实践结合: 每个知识点都动手写代码。
理解工作流程: DispatcherServlet 工作流程是核心,反复琢磨。
官方文档: Spring 官方文档是权威且全面的参考资料。
善用调试: 利用 IDE 调试功能跟踪代码执行,观察对象状态变化。
// 简单的 Spring Boot 应用主类
package com.yourpackage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 包含了 @EnableAutoConfiguration 和 @ComponentScan
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args); // 直接运行
}
}
Spring Boot 会自动配置 DispatcherServlet、HandlerMapping、HandlerAdapter、HttpMessageConverter (如果 classpath 中有 Jackson/Gson)、默认的 ViewResolver (取决于依赖,如 Thymeleaf/FreeMarker)。你只需要编写 Controller、Service、Repository 等业务代码即可。
掌握 Spring MVC 原生配置有助于理解框架原理,但实际企业开发中,强烈推荐使用 Spring Boot 来快速构建应用。
• 异常处理: 使用 @ControllerAdvice 和 @ExceptionHandler 实现统一的异常处理,避免在每个 Controller 方法中编写 try-catch 块。返回统一的错误响应格式(尤其是 RESTful API)。
• 重定向与转发的选择: 根据需求选择。重定向用于 POST 请求后的 PRG (Post/Redirect/Get) 模式,避免表单重复提交;转发用于服务器内部的流程跳转,URL 不变,可以共享 Request 属性。
• 文件上传大小限制: 配置 MultipartResolver 时,可以设置文件总大小和单个文件大小的限制,防止恶意上传。
• 拦截器: 适用于跨多个请求处理的横切关注点,如用户认证、权限检查、日志记录、性能监控等。与 Filter 相比,拦截器能访问 Spring 上下文和 Handler 对象。
• 数据访问整合: Service 层依赖 Repository/DAO 层是标准的分层架构。通过 Spring 的依赖注入将它们关联起来。配置数据源、事务管理是关键。
• 拥抱 Spring Boot: 在掌握 Spring MVC 基础后,立即学习 Spring Boot。它极大地提高了开发效率,是现代 Spring 应用开发的标准。
学习建议再次强调:
理论与实践结合: 每个知识点都动手写代码。
理解工作流程: DispatcherServlet 工作流程是核心,反复琢磨。
官方文档: Spring 官方文档是权威且全面的参考资料。
善用调试: 利用 IDE 调试功能跟踪代码执行,观察对象状态变化。
循序渐进,持续实践。
小型图书管理系统案例 (Spring MVC + Spring Data JPA + Thymeleaf)
企业级Spring MVC高级主题与实用技术讲解