一. SpringMVC的请求处理
2.1 请求映射路径的配置
配置映射路径,映射器处理器才能找到Controller的方法资源,目前主流映射路径配置方式就是@RequestMapping。
| 相关注解 | 作用 | 使用位置 |
|---|---|---|
@RequestMapping |
设置控制器方法的访问资源路径,不限定 HTTP 方法 | 方法和类上 |
@GetMapping |
设置控制器方法的访问资源路径,可以接收GET请求 | 方法和类上 |
@PostMapping |
设置控制器方法的访问资源路径,可以接收POST请求 | 方法和类上 |
@PutMapping |
设置控制器方法的访问资源路径,可以接收PUT请求 | 方法和类上 |
@DeleteMapping |
设置控制器方法的访问资源路径,可以接收DELETE请求 | 方法和类上 |
@PatchMapping |
设置控制器方法的访问资源路径,可以接收PATCH请求 | 方法和类上 |
2.2 请求数据的接收
在 Spring MVC 中,接收请求数据的方式非常灵活。框架底层通过HttpMessageConverter 等机制,能够自动将不同格式的请求数据(如 URL 参数、表单、JSON 等)转换为 Java 对象。根据数据来源和格式的不同,常用的参数接收方式可以总结为以下 8 种:
| 接收方式 | 作用 | 使用位置 |
|---|---|---|
基本类型参数 |
自动匹配请求参数名(URL查询参数或表单数据) | Controller方法参数 |
POJO类型参数 |
自动将请求参数绑定到POJO对象的对应属性 | Controller方法参数 |
@RequestParam |
将请求参数绑定到方法参数,支持设置默认值和是否必填 | 方法参数 |
@PathVariable |
将URL路径中的变量绑定到方法参数,常用于RESTful风格 | 方法参数 |
@RequestBody |
将请求体中的JSON/XML数据绑定到对象,需Content-Type为application/json | 方法参数 |
数组/集合类型 |
接收多个同名请求参数,数组可直接接收,集合需包装在POJO中 | Controller方法参数 |
@RequestHeader |
从HTTP请求头中提取指定参数(如Token、User-Agent等) | 方法参数 |
@CookieValue |
从HTTP请求的Cookie中提取指定值 | 方法参数 |
java
@RestController
@RequestMapping("/demo")
public class DemoController {
// 1. 基本类型参数 & 2. POJO类型参数
// 请求示例:GET /demo/basic?username=zhangsan&age=18
@GetMapping("/basic")
public String basicParam(String username, int age, User user) {
// username 和 age 会自动匹配请求参数
// user 对象的 username 和 age 属性也会自动绑定
return "接收成功";
}
// 3. @RequestParam 获取请求参数 defaultValue:默认值
// 请求示例:GET /demo/param?name=lisi
@GetMapping("/param")
public String requestParam(@RequestParam(name = "name", defaultValue = "Guest") String userName) {
return "Hello, " + userName;
}
// 4. @PathVariable 获取路径参数
// 请求示例:GET /demo/path/1001
@GetMapping("/path/{id}")
public String pathVariable(@PathVariable("id") Long userId) {
return "用户ID: " + userId;
}
// 5. @RequestBody 获取请求体参数
// 请求示例:POST /demo/body (Body: {"username":"wangwu","age":20})
@PostMapping("/body")
public String requestBody(@RequestBody User user) {
return "接收到的JSON数据: " + user;
}
// 6. 数组/集合类型参数
// 请求示例:GET /demo/array?hobbies=reading&hobbies=swimming
@GetMapping("/array")
public String arrayParam(String[] hobbies) {
return "爱好: " + Arrays.toString(hobbies);
}
// 7. @RequestHeader & 8. @CookieValue
// 请求示例:GET /demo/header (需携带自定义Header和Cookie)
@GetMapping("/header")
public String headerAndCookie(
@RequestHeader("User-Agent") String userAgent,
@CookieValue(value = "JSESSIONID", defaultValue = "") String sessionId) {
return "User-Agent: " + userAgent + ", SessionID: " + sessionId;
}
}
2.3 Javaweb常用对象获取
在传统的 Servlet 开发中,我们经常需要手动获取 HttpServletRequest、HttpServletResponse、HttpSession 等原生对象来处理业务。而在 Spring MVC 中,框架为我们提供了极大的便利:我们只需要在 Controller 方法的形参中直接声明这些对象,Spring MVC 就会在调用方法时自动将当前请求对应的原生对象注入进来。
| 对象类型 | 作用 | 获取方式 |
|---|---|---|
HttpServletRequest |
获取请求参数、请求头、客户端IP等信息 | 直接作为方法参数 |
HttpServletResponse |
设置响应头、状态码、输出流等 | 直接作为方法参数 |
HttpSession |
获取或设置会话域数据(如用户登录信息) | 直接作为方法参数 |
ServletContext |
获取全局应用域数据、读取配置文件等 | 通过 request.getServletContext() 获取 |
java
@Controller
@RequestMapping("/native")
public class NativeObjectController {
// 1. 获取 Request、Response、Session 对象
// Spring MVC 会自动将当前请求的原生对象注入到方法参数中
@GetMapping("/test")
public String testNativeObjects(
HttpServletRequest request,
HttpServletResponse response,
HttpSession session) {
// 使用 request 获取请求参数
String username = request.getParameter("username");
System.out.println("请求参数 username: " + username);
// 使用 session 存储和获取数据
session.setAttribute("loginUser", username);
String user = (String) session.getAttribute("loginUser");
System.out.println("Session中的用户: " + user);
// 使用 response 设置响应头
response.setHeader("Custom-Header", "SpringMVC");
return "success";
}
// 2. 获取 ServletContext (Application域)
// ServletContext 不能直接作为方法参数,需要通过 request 获取
@GetMapping("/app")
public String testApplication(HttpServletRequest request) {
ServletContext application = request.getServletContext();
// 获取全局配置或应用域数据
String appName = application.getInitParameter("appName");
System.out.println("应用名称: " + appName);
return "success";
}
}
2.4 请求静态资源(目前不重要)
在 Spring MVC 中,核心控制器 DispatcherServlet 默认会拦截所有的请求(通常配置 url-pattern 为 /)。这意味着,如果我们直接访问项目中的 CSS、JS、图片等静态资源,请求也会被 DispatcherServlet 拦截,由于找不到对应的 Controller 处理器,最终会导致 404 错误。
为了解决这个问题,Spring MVC 提供了专门的静态资源处理机制。目前主流的配置方式主要有以下两种:
| 配置方式 | 作用 | 适用场景 |
|---|---|---|
<mvc:resources> 标签 |
将特定的 URL 路径映射到指定的静态资源目录,支持缓存控制 | 传统 XML 配置项目 |
<mvc:default-servlet-handler> |
激活容器的默认 Servlet 来处理静态资源 | 简单项目,不想手动映射路径 |
- 使用
<mvc:resources>标签(推荐)
这种方式可以精确控制哪些路径映射到哪些资源目录,并且可以设置缓存时间。
xml
<!-- 在 spring-mvc.xml 配置文件中添加 -->
<!-- 1. 必须开启注解驱动,否则 mvc 标签不生效 -->
<mvc:annotation-driven />
<!-- 2. 配置静态资源映射 -->
<!-- mapping: 对外暴露的访问路径(支持 Ant 风格通配符) -->
<!-- location: 静态资源在项目中存放的实际位置(必须以 / 结尾) -->
<!-- cache-period: 浏览器缓存时间,单位是秒 -->
<mvc:resources mapping="/static/**" location="/static/" cache-period="3600" />
配置完成后,访问 /static/css/style.css 就会自动映射到项目根目录下的/static/css/style.css 文件。
- 使用
<mvc:default-servlet-handler>
这种方式最简单,它会告诉 Spring MVC:"遇到静态资源,就交给 Tomcat/Jetty 等容器默认的 Servlet 去处理"。
xml
<!-- 在 spring-mvc.xml 配置文件中添加 -->
<mvc:default-servlet-handler />
二. SpringMVC的响应处理
在 Spring MVC 中,控制器方法处理完请求后,需要把结果返回给客户端。根据返回内容的不同,响应方式可以分为两类:响应页面(返回视图)和响应数据(返回 JSON、文本等)。下面分别介绍。
3.1 响应页面
响应页面是传统 MVC 中最常见的场景:Controller 处理完业务后,跳转到指定页面,并把数据带到页面上。
| 方式 | 作用 | 特点 |
|---|---|---|
ModelAndView |
同时携带数据和视图名,由方法直接返回 | 数据和视图合在一起 |
Model + 返回视图名 |
方法返回字符串视图名,数据通过 Model 传入 | 写法更简洁,推荐使用 |
java
@Controller
@RequestMapping("/view")
public class ViewController {
// 方式一:返回 ModelAndView
@GetMapping("/mav")
public ModelAndView mav() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success"); // 逻辑视图名
modelAndView.addObject("msg", "通过 ModelAndView 返回"); // 携带数据
return modelAndView;
}
// 方式二:返回视图名 + Model 携带数据(推荐)
@GetMapping("/model")
public String model(Model model) {
model.addAttribute("msg", "通过 Model 返回");
return "success"; // 返回逻辑视图名,交给视图解析器解析
}
}
说明:上面返回的 "success" 是逻辑视图名,最终会被 ViewResolver 解析成实际页面,
例如:/WEB-INF/views/success.jsp。Model 中的数据会被放入 request 域,页面可直接通过 EL 表达式读取,如:${msg}。
3.2 响应文本数据
如果不需要跳转页面,而是直接把字符串返回给客户端,可以使用 @ResponseBody。它会将方法的返回值直接写入响应体,不再经过视图解析器。
java
@Controller
@RequestMapping("/text")
public class TextController {
@GetMapping("/hello")
@ResponseBody
public String hello() {
return "Hello, Spring MVC";
}
}
注意区分:方法返回 String 时,如果没有加 @ResponseBody,Spring MVC 会把它当作逻辑视图名去解析;加上@ResponseBody 后才会原样输出文本。
3.3 响应 JSON 数据
前后端分离开发时,接口最常返回 JSON。只需要在方法上标注 @ResponseBody 并返回一个 Java 对象,Spring MVC 会通过 HttpMessageConverter(默认使用 Jackson)把对象自动序列化成 JSON。
java
@Controller
@RequestMapping("/json")
public class JsonController {
@GetMapping("/user")
@ResponseBody
public User getUser() {
User user = new User();
user.setUsername("zhangsan");
user.setAge(18);
return user; // 自动转为 {"username":"zhangsan","age":18}
}
}
如果希望整个 Controller 的所有方法都返回 JSON,可以直接使用 @RestController,它等价于 @Controller + @ResponseBody。
java
@RestController // = @Controller + @ResponseBody
@RequestMapping("/json")
public class JsonController2 {
@GetMapping("/user")
public User getUser() {
User user = new User();
user.setUsername("zhangsan");
user.setAge(18);
return user;
}
}
注意:
返回 JSON 需要在项目中引入 Jackson 依赖,例如 jackson-databind;
Spring Boot 默认已集成。如果对象中某些属性不想被序列化,可以用 @JsonIgnore 忽略;日期等特殊类型可以用 @JsonFormat 格式化。
3.4 请求转发与重定向
在返回视图名时,可以加上 forward: 或 redirect: 前缀来控制跳转方式。
| 方式 | 语法 | 特点 |
|---|---|---|
请求转发 |
return "forward:/user/list"; | 一次请求,地址栏不变,可携带 request 域数据 |
重定向 |
return "redirect:/user/list"; | 两次请求,地址栏改变,request 域数据丢失 |
java
@Controller
@RequestMapping("/jump")
public class JumpController {
// 请求转发:地址栏不变
@GetMapping("/forward")
public String forward() {
return "forward:/jump/target";
}
// 重定向:地址栏变成 /jump/target
@GetMapping("/redirect")
public String redirect() {
return "redirect:/jump/target";
}
@GetMapping("/target")
@ResponseBody
public String target() {
return "跳转成功";
}
}
三. Spring MVC 拦截器
4.1 拦截器 Interceptor
拦截器是 Spring MVC 提供的一种机制,可以在 Controller 方法执行前后动态拦截请求。我们可以自定义一个类实现 HandlerInterceptor 接口,并重写其中三个方法:
| 方法 | 执行时机 | 典型作用 |
|---|---|---|
preHandle |
Controller 方法执行前 | 登录校验、权限校验;返回 false 会中断请求 |
postHandle |
Controller 方法执行后,视图渲染前 | 对 ModelAndView 做统一处理 |
afterCompletion |
整个请求完成后(视图渲染后) | 资源清理、日志记录等 |
拦截器和过滤器(Filter)经常被拿来比较,两者区别如下:

| 对比项 | Filter 过滤器 |
Interceptor 拦截器 |
|---|---|---|
| 技术归属 | Servlet 规范 | Spring 框架 |
| 作用范围 | 所有请求,可拦截静态资源 | Controller(Handler)方法 |
| 能否拿到 Spring 容器 Bean | 不能直接拿到 | 可以,由 Spring 容器管理 |
| 能否拿到当前 Handler | 不能 | 能,preHandle 参数中有 Object handler |
| 执行顺序 | 在拦截器之前 | 在过滤器之后 |
4.2 拦截器快速入门
实现HandlerInterceptor接口:
java
public class MyInterceptor01 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Controller方法执行之前...");
return true;//放行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Controller方法执行之后...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("渲染视图结束,整个流程完毕...");
}
}
配置Interceptor
xml
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--配置对哪些资源进行拦截操作-->
<mvc:mapping path="/*"/>
<bean class="com.itheima.interceptor.MyInterceptor01"></bean>
</mvc:interceptor>
</mvc:interceptors>
4.3 拦截器执行顺序
| 阶段 | 执行顺序 |
|---|---|
| preHandle | 按配置顺序正序执行 |
| postHandle | 按配置顺序逆序执行 |
| afterCompletion | 按配置顺序逆序执行 |
当每个拦截器都是放行状态时,三个方法的执行顺序如下:

当Interceptor1和Interceptor2处于放行,Interceptor3处于不放行时,三个方法的执行顺序如下:

4.4 拦截器执行原理

拦截器执行流程:
- 客户端发起请求:请求首先进入 Spring MVC 的前端控制器
DispatcherServlet。 - 查找
Handler:DispatcherServlet 调用getHandler(),由RequestMappingHandlerMapping根据 URL、HTTP 方法匹配出目标 Handler。 - 构建执行链:结合拦截器配置,生成
HandlerExecutionChain执行链,其中包含「目标 Handler + 命中的拦截器链」。 - 执行
preHandle:applyPreHandle() 按顺序执行拦截器的preHandle,任一返回 false 则中断请求流程。 查找适配器:getHandlerAdapter() 找到对应的RequestMappingHandlerAdapter。执行 Handler:HandlerAdapter.handle() 调用 Controller 方法,返回ModelAndView。- 执行
postHandle:applyPostHandle() 按逆序执行拦截器的postHandle。 - 执行
afterCompletion:triggerAfterCompletion() 按逆序执行 afterCompletion,无论 Handler 成功还是异常都会执行。
四. SpringMVC的全注解开发
4.1 spring-mvc.xml 组件转化为注解形式
原来的 spring-mvc.xml 大致如下:
xml
<!-- 组件扫描 web 层 -->
<context:component-scan base-package="com.itheima.controller"/>
<!-- 注解驱动 -->
<mvc:annotation-driven/>
<!-- 配置文件上传解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/>
<bean class="com.itheima.interceptor.MyInterceptor01"/>
</mvc:interceptor>
</mvc:interceptors>
<!-- 配置 DefaultServletHttpRequestHandler -->
<mvc:default-servlet-handler/>
转化为注解后,对应关系如下:
| XML 标签 | 注解形式 |
|---|---|
<context:component-scan> |
@ComponentScan |
<mvc:annotation-driven> |
@EnableWebMvc |
<bean>(自定义 Bean) |
@Bean |
<mvc:interceptors> |
实现 WebMvcConfigurer,重写 addInterceptors() |
<mvc:default-servlet-handler> |
实现 WebMvcConfigurer,重写 configureDefaultServletHandling() |
- 组件扫描:< context:component-scan> 可以通过
@ComponentScan注解完成:
java
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMVCConfig {
}
- 文件上传解析器:multipartResolver 这种非自定义 Bean,可以通过 @Bean 注解完成配置:
java
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMVCConfig {
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setDefaultEncoding("UTF-8");
multipartResolver.setMaxUploadSize(3145728); // 单次请求总大小 3MB
multipartResolver.setMaxUploadSizePerFile(1048576); // 单个文件 1MB
multipartResolver.setMaxInMemorySize(1048576); // 内存缓存 1MB
return multipartResolver;
}
}
注意:这里的 Bean 名称必须是 multipartResolver,Spring MVC 才能正确识别文件上传解析器。
- 注解驱动
@EnableWebMvc:那 < mvc:default-servlet-handler/> 和 < mvc:interceptors> 该怎么办?Spring MVC 提供了一个@EnableWebMvc注解,它的源码如下:
java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
可以看到,@EnableWebMvc 内部通过 @Import 导入了DelegatingWebMvcConfiguration 类:
java
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 从容器中注入 WebMvcConfigurer 类型的 Bean
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
// 省略其他代码
}
简单理解:@EnableWebMvc 会收集容器中所有 WebMvcConfigurer 的实现类,然后统一调用它们的方法,完成 Spring MVC 的配置。
WebMvcConfigurer 接口提供了大量默认方法:
java
public interface WebMvcConfigurer {
// 配置默认 Servlet 处理器
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { }
// 添加拦截器
default void addInterceptors(InterceptorRegistry registry) { }
// 添加资源处理器
default void addResourceHandlers(ResourceHandlerRegistry registry) { }
// 添加视图控制器
default void addViewControllers(ViewControllerRegistry registry) { }
// 配置视图解析器
default void configureViewResolvers(ViewResolverRegistry registry) { }
// 添加参数解析器
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { }
// ... 省略其他代码 ...
}
- 拦截器与静态资源配置:因此,只需要编写一个
WebMvcConfigurer 实现类,重写对应的方法即可:
java
@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// 开启 DefaultServlet,用来处理静态资源
configurer.enable();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 创建拦截器对象并注册
// 拦截器的执行顺序也取决于添加顺序
registry.addInterceptor(new MyInterceptor01()).addPathPatterns("/*");
}
}
注意:这里加 @Component 是为了让 Spring 扫描到该配置类,实际使用时要保证它位于 @ComponentScan的扫描路径内。
最后,在 Spring MVC 核心配置类上添加 @EnableWebMvc 注解:
java
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc
public class SpringMVCConfig {
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setDefaultEncoding("UTF-8");
multipartResolver.setMaxUploadSize(3145728);
multipartResolver.setMaxUploadSizePerFile(1048576);
multipartResolver.setMaxInMemorySize(1048576);
return multipartResolver;
}
}
4.2 DispatcherServlet 加载核心配置类
原来的 web.xml 中,DispatcherServlet 通过 contextConfigLocation 指定 Spring MVC 配置文件的位置:
xml
<!-- 配置 springMVC 前端控制器 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定 springMVC 配置文件位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 服务器启动就创建 -->
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
现在是用 SpringMVCConfig 核心配置类替代 spring-mvc.xml,那该怎么加载?
可以参照 Spring 的 ContextLoaderListener 加载核心配置类的做法,定义一个 AnnotationConfigWebApplicationContext,通过代码注册核心配置类:
java
public class MyAnnotationConfigWebApplicationContext extends AnnotationConfigWebApplicationContext {
public MyAnnotationConfigWebApplicationContext() {
// 注册核心配置类
super.register(SpringMVCConfig.class);
}
}
然后在 web.xml 中,通过 contextClass 指定这个类的全限定名:
xml
<!-- 指定 springMVC 的 applicationContext 全限定名 -->
<init-param>
<param-name>contextClass</param-name>
<param-value>com.itheima.config.MyAnnotationConfigWebApplicationContext</param-value>
</init-param>
这样,DispatcherServlet 创建时就会使用我们自定义的 WebApplicationContext,从而加载 SpringMVCConfig 配置类。
4.3 消除 web.xml
目前,几乎消除了配置文件,但 Web 工程的入口仍然需要 web.xml。要彻底去掉它,需要借助 Servlet 3.0 的机制:
- Servlet 3.0 环境中,Web 容器提供了 javax.servlet.ServletContainerInitializer 接口。
- 实现该接口后,在对应类加载路径的 META-INF/services 目录下创建一个名为 javax.servlet.ServletContainerInitializer 的文件,文件内容指定具体的 ServletContainerInitializer 实现类。
- 当 Web 容器启动时,就会自动运行这个初始化器,完成组件内的初始化工作。
基于这个特性,Spring 定义了 SpringServletContainerInitializer 来实现 ServletContainerInitializer 接口:
SpringServletContainerInitializer会查找所有实现了WebApplicationInitializer的类。- Spring 又提供了 WebApplicationInitializer 的基础实现类
AbstractAnnotationConfigDispatcherServletInitializer。 - 当我们编写类继承 AbstractAnnotationConfigDispatcherServletInitializer 时,容器就会自动发现这个类,并在其中配置 Spring 和 Spring MVC 的入口。
java
public class MyAnnotationConfigDispatcherServletInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
// 返回的带 @Configuration 注解的类,用来配置 ContextLoaderListener(根容器)
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{ApplicationContextConfig.class};
}
// 返回的带 @Configuration 注解的类,用来配置 DispatcherServlet(Web 容器)
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{SpringMVCConfig.class};
}
// 将一个或多个路径映射到 DispatcherServlet 上
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}