springboot 2 编程笔记 二
SpringBoot2核心技术与响应式编程 (二)核心功能
接上文 《springboot 2 编程笔记 一》
上篇文章我们了解了基础入门、底层注解、自动配置、以及实践等入门内容
接下来我们继续记录,有鉴于上篇更新的太慢了,这段时间我会加快进度,争取早日更新完!
配置文件
5.1 yml文件
YAML 是"YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML的意思其实是:"Yet Another Markup Language”(仍是一种标记语言)。
基本语法
key: value(k v之间有空格大小写敏感)- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
#表示注释'与"表示字符串内容 会被 转义/不转义
数据类型:
- 字面量:单个的,不可再分的值,date,boolean,string,number,null
k: v - 对象:键值对的集合。map、hash、set、object
行内的写法:k: {k1:v1,k2:v2,k3:v3}
或者k: k1: v1 k2: v2 k3: v3 - 数组:一组按次序排列的值,array、list、queue行内写法:
k: [v1,v2,v3]
或者k: - v1 - v2 - v3
我们带入到代码中,看代码中怎么写:
在Person类,我们添加相关的属性:
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Component//标记这个类是一个 "组件"(Component),是一个 Spring 管理的 Bean。
@ConfigurationProperties(prefix = "person")//将外部配置文件(如 application.yml 或 application.properties)中的属性批量绑定到这个类的字段上。
public class Person {
private String username;
private Boolean boss;
private Date birth;
private int age;
private Pet pet;
private String[] interests;
private List<String> animal;
private Map<String, Object> score;
private Set<Double> salarys;
private Map<String, List<Pet>> allPets;
}
在appliction.yml文件中为Person类的各个属性赋值:
person:
username: 张三
boss: false
birth: 2015/01/01
age: 11
#interests: [篮球,足球]
interests:
- 篮球
- 足球
- 18禁
animal: [桃子,金条]
#score:
#english: 80
#math: 90
score: {english: 80,math: 90}
salarys: [10000.00, 20000.01]
pet: {name: 桃子,weight: 5.0}
all-pets:
sick:
- {name: 金条, weight: 10.00}
health:
- {name: 桃子, weight: 5.00}
我们启动项目看下结果:
注意:
在yml文件中,'和"分别表示不同的意义,
- 需要转义字符时使用双引号
- 需要保持字符原样时使用单引号
最佳实践建议
- 当字符串包含特殊字符或需要转义时,使用双引号
- 当字符串包含反斜杠且需要保持原样时(如正则表达式),使用单引号
- 对于简单的字符串,可以省略引号以提高可读性
- 保持一致的使用风格,提高配置文件的可维护性
6. web场景
6.1 静态资源目录
只要静态资源放在类路径下:/static或者/public或者/resource或者/META-INF/resources
访问:
当前项目根路径 /+静态资源名


原理:
静态映射/**
请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到就报404
代码演示:
//写个请求方法
@RestController
public class HelloController {
@RequestMapping("/bug.png")
public String hello3(){
return "qwer";
}
}
在静态资源文件夹下添加图片
在浏览器上输入请求地址:http://localhost:8083/aaa.png
如果输入的是连静态资源都没有的就会报404,我们修改请求路径:
-
静态资源访问前缀:
默认无前缀,如果想要放行以指定前缀的路径开始的所有请求当前项目+static-path-pattern+静态资源名=静态资源文件夹下找
# 默认配置 #spring: # mvc: # static-path-pattern: /** # 设置路径 spring: mvc: static-path-pattern: /resources/**
此时我们再请求http://localhost:8083/aaa.png就会报404
如果我们添加请求resources路径:http://localhost:8083/resources/aaa.png会返回相应的图片
-
改变默认的静态资源路径,只有在指定路径下的静态资源才可被加载
spring: mvc: static-path-pattern: /resources/** web: resources: static-locations: classpath:/jingtai此时我们再访问原路径:
http://localhost:8083/resources/img3.png则会返回404
只有访问指定静态资源文件夹下的资源才会生效:
-
webjar jar包的自动映射 JS 文件
引入jar包,重启服务
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
在浏览器地址栏输入请求地址:http://localhost:8083/webjars/jquery/3.5.1/jquery.js
从这里也可看出 webjars的静态资源/META-INF/resources可以忽略static-path-pattern和static-locations配置的限制
6.2 welcome和Favicon功能
- 欢迎页
可以配置静态资源路径,但无法设置静态资源的访问前缀,否则会导致默认无法访问 index.html。
输入spring: # mvc: # static-path-pattern: /resources/** web: resources: static-locations: classpath:/jingtaiIP地址+端口号即可进入欢迎页
- 在网页栏添加图标
我在这里试了好几次都失败了,不知道什么原因
6.3 静态资源配置原理
-
SpringBoot启动默认加载xxxautoconfiguration类(自动配管类)
-
SpringMvc功能的自动配置类 WebMvcAutoConfiguration
@Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}) public class WebMvcAutoConfiguration { -
核心配置类 主要包含三个重要的内部配置类:
-
WebMvcAutoConfigurationAdapter- 主要配置器:这是最核心的部分,实现了WebMvcConfigurer接口,配置了 MVC 的各种行为。@Configuration(proxyBeanMethods = false) @Import({EnableWebMvcConfiguration.class}) @EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class}) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {- 处理静态资源方法:
主要功能:public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); } else { // 处理 webjars 资源(如 Bootstrap、jQuery) this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/"); // 处理静态资源(classpath:/static/, classpath:/public/ 等) this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> { registration.addResourceLocations(this.resourceProperties.getStaticLocations()); }); } }- 静态资源处理:配置 /webjars/** 和默认的静态资源路径
- 视图解析器:配置 JSP 等视图的解析
- 消息转换器:处理 JSON、XML 等数据格式转换
- 路径匹配:配置 URL 路径的匹配规则



- 处理静态资源方法:
-
EnableWebMvcConfiguration- 启用MVC配置:继承自DelegatingWebMvcConfiguration,提供了更高级的 MVC 配置
主要功能:- 处理器映射:配置请求如何映射到控制器方法
- 本地化解析:国际化支持
- 欢迎页面:配置默认的 index.html 页面
- 数据格式化:日期、数字等格式转换
@Bean @ConditionalOnMissingBean(name = {"localeResolver"}) public LocaleResolver localeResolver() { // 配置本地化解析器,支持中英文切换等 if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.webProperties.getLocale()); } else { AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.webProperties.getLocale()); return localeResolver; } }
-
ResourceChainCustomizerConfiguration- 资源链定制
优化静态资源的处理,比如缓存、版本控制等。@Configuration( proxyBeanMethods = false ) @ConditionalOnEnabledResourceChain static class ResourceChainCustomizerConfiguration { ResourceChainCustomizerConfiguration() { } @Bean ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer(WebProperties webProperties) { return new ResourceChainResourceHandlerRegistrationCustomizer(webProperties.getResources()); } }
-
配置文件的相关属性和xxx进行了绑定。WebMvcProperties==spring.mvc、WebProperties==spring.web(boot版本不同,相应的配置类也不同)
**注意:**配置类只有一个有参构造器
// 有参构造器所有参数的值都会从容器中确定 public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) { this.resourceProperties = webProperties.getResources(); this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConvertersProvider = messageConvertersProvider; this.resourceHandlerRegistrationCustomizer = (ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this.dispatcherServletPath = dispatcherServletPath; this.servletRegistrations = servletRegistrations; this.mvcProperties.checkConfiguration(); }
7 请求处理
什么是RESTful API?
RESTful 是一种软件架构风格,不是标准也不是协议。它由 Roy Fielding 在 2000 年的博士论文中提出,全称是 Representational State Transfer(表述性状态转移)。
1.1 什么是 RESTful?
RESTful 是一种设计 Web API 的指导思想,强调:
- 使用 HTTP 协议的特性
- 资源导向的设计
- 无状态通信
- 统一的接口
传统方式 vs RESTful 方式:
// ❌ 传统方式(基于动作)
@Controller
public class UserController {
@RequestMapping("/getUser") // 获取用户
@RequestMapping("/createUser") // 创建用户
@RequestMapping("/updateUser") // 更新用户
@RequestMapping("/deleteUser") // 删除用户
}
// ✅ RESTful 方式(基于资源)
@RestController
public class UserController {
@GetMapping("/users/{id}") // 获取用户
@PostMapping("/users") // 创建用户
@PutMapping("/users/{id}") // 更新用户
@DeleteMapping("/users/{id}") // 删除用户
}
RESTful API 设计规范
@RestController
public class UserController {
// ✅ 好的设计 - 资源层次清晰
@GetMapping("/users") // 用户集合
@PostMapping("/users") // 创建用户
@GetMapping("/users/{userId}") // 特定用户
@PutMapping("/users/{userId}") // 更新用户
@DeleteMapping("/users/{userId}") // 删除用户
// ✅ 子资源
@GetMapping("/users/{userId}/orders") // 用户的订单
@GetMapping("/users/{userId}/orders/{orderId}") // 用户的特定订单
// ❌ 不好的设计 - 动词在URI中
@GetMapping("/getAllUsers") // 不要用动词
@PostMapping("/createNewUser") // 不要用动词
}
HTTP 状态码的使用
@RestController
public class UserController {
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return ResponseEntity.notFound().build(); // 404
}
return ResponseEntity.ok(user); // 200
}
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
try {
User savedUser = userService.save(user);
return ResponseEntity.status(201).body(savedUser); // 201 Created
} catch (Exception e) {
return ResponseEntity.badRequest().build(); // 400
}
}
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build(); // 204 No Content
}
}
实际代码示例
完整的 RESTful 用户 API
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@Autowired
private UserService userService;
// 获取所有用户 - GET /api/v1/users
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.findAll();
return ResponseEntity.ok(users); // 200 OK
}
// 获取特定用户 - GET /api/v1/users/1
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(user -> ResponseEntity.ok(user)) // 200 OK
.orElse(ResponseEntity.notFound().build()); // 404 Not Found
}
// 创建用户 - POST /api/v1/users
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
User savedUser = userService.save(user);
// 创建成功后返回 201 和 Location 头
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(savedUser.getId())
.toUri();
return ResponseEntity.created(location).body(savedUser); // 201 Created
}
// 更新用户 - PUT /api/v1/users/1
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id,
@Valid @RequestBody User user) {
if (!userService.existsById(id)) {
return ResponseEntity.notFound().build(); // 404
}
user.setId(id); // 确保ID一致
User updatedUser = userService.save(user);
return ResponseEntity.ok(updatedUser); // 200 OK
}
// 删除用户 - DELETE /api/v1/users/1
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
if (!userService.existsById(id)) {
return ResponseEntity.notFound().build(); // 404
}
userService.deleteById(id);
return ResponseEntity.noContent().build(); // 204 No Content
}
// 搜索用户 - GET /api/v1/users/search?name=张三
@GetMapping("/search")
public ResponseEntity<List<User>> searchUsers(@RequestParam String name) {
List<User> users = userService.findByNameContaining(name);
return ResponseEntity.ok(users); // 200 OK
}
}
对应的前端调用示例
// 使用 JavaScript Fetch API 调用 RESTful 接口
// 获取所有用户
fetch('/api/v1/users')
.then(response => response.json())
.then(users => console.log(users));
// 创建新用户
fetch('/api/v1/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: '张三',
email: 'zhangsan@example.com'
})
})
.then(response => {
if (response.status === 201) {
console.log('用户创建成功');
// 可以从 Location 头获取新用户的URL
const userUrl = response.headers.get('Location');
}
});
// 更新用户
fetch('/api/v1/users/1', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: '李四',
email: 'lisi@example.com'
})
});
// 删除用户
fetch('/api/v1/users/1', {
method: 'DELETE'
});
RESTful 风格的核心思想:
- 面向资源:一切操作都围绕资源进行
- 使用 HTTP 标准:充分利用 HTTP 方法、状态码、头部
- 无状态:每次请求独立,服务器不保存客户端状态
- 统一接口:一致的 API 设计模式
- 可寻址性:每个资源都有唯一的 URI
简单记忆:
- 看 URL 就知道是什么资源
- 看 HTTP 方法就知道要做什么操作
- 看状态码就知道操作结果
- 这种设计让 API 更加直观、简洁、易于理解和维护,是现代 Web 开发中最主流的 API 设计风格。
7.1 请求参数处理
在讲解请求参数处理时,我们首先需要解析 @RequestMapping 这个注解。先看代码:
@Target({
// 可以用在类上(如 Controller 类)
ElementType.TYPE,
// 可以用在方法上(如 Controller 中的处理方法)
ElementType.METHOD
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 这是一个元注解,表示这是一个"映射"类型的注解
// Spring 会识别所有带有 @Mapping 的注解来进行请求映射
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
//作用:限制只处理特定的 HTTP 方法
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
这是一个非常核心的 Spring MVC 注解,用于将 HTTP 请求映射到具体的控制器方法。
这里对几个重要的参数解释
-
name- 映射名称:为这个映射指定一个唯一的名称- 在视图解析中引用
- 在测试中识别特定的映射
- 生成 URL 链接
实例:
-
method- HTTP 请求方法
作用:限制只处理特定的 HTTP 方法
常用值:-
RequestMethod.GET - 查询数据
-
RequestMethod.POST - 创建数据
-
RequestMethod.PUT - 更新数据
-
RequestMethod.DELETE - 删除数据
-
RequestMethod.PATCH - 部分更新
示例:
@RequestMapping(value = "/users", method = RequestMethod.GET) public List<User> getUsers() { /* 获取用户列表 */ } @RequestMapping(value = "/users", method = RequestMethod.POST) public User createUser(@RequestBody User user) { /* 创建用户 */ }
-
-
params- 请求参数条件
作用:要求请求必须包含特定的参数
语法:
“paramName” - 必须包含该参数
“!paramName” - 必须不包含该参数
“paramName=value” - 参数值必须等于指定值
示例:@RequestMapping(value = "/search", params = "keyword") public String search(@RequestParam String keyword) { // 只处理包含 keyword 参数的请求 } @RequestMapping(value = "/users", params = "type=admin") public String adminUsers() { // 只处理 /users?type=admin 的请求 } @RequestMapping(value = "/users", params = "!debug") public String usersNoDebug() { // 不处理包含 debug 参数的请求 } -
headers - 请求头条件
作用:要求请求必须包含特定的 HTTP 头
语法:与 params 类似 -
“Header-Name” - 必须包含该头
-
“!Header-Name” - 必须不包含该头
-
“Header-Name=value” - 头值必须匹配
示例:@RequestMapping(value = "/api", headers = "X-API-Version=1.0") public String apiV1() { // 只处理 API 版本为 1.0 的请求 } @RequestMapping(value = "/api", headers = "Content-Type=application/json") public String jsonApi() { // 只处理 Content-Type 为 JSON 的请求 } -
consumes- 请求内容类型
作用:指定方法可以消费的媒体类型(即请求体的格式)
示例:@RequestMapping(value = "/users", consumes = "application/json") public User createUserJson(@RequestBody User user) { // 只处理 Content-Type: application/json 的请求 } @RequestMapping(value = "/users", consumes = "application/xml") public User createUserXml(@RequestBody User user) { // 只处理 Content-Type: application/xml 的请求 } -
produces- 响应内容类型
作用:指定方法产生的媒体类型(即响应体的格式)
示例:@RequestMapping(value = "/users", produces = "application/json") @ResponseBody public User getUserJson() { // 返回 JSON 格式,设置 Content-Type: application/json } @RequestMapping(value = "/users", produces = "application/xml") @ResponseBody public User getUserXml() { // 返回 XML 格式,设置 Content-Type: application/xml }
实际应用:
@Controller
@RequestMapping("/api/users") // 用在类上 → 定义基础路径
public class UserController {
@RequestMapping("/list") // 用在方法上 → 定义具体路径
public String listUsers() {
return "user-list";
}
}
最终路径:/api/users/list

- @xxxMapping;
- Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户。现在:/user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
- 核心Filter; HiddenHttpMethodFilter
- 用法:表单method=post,隐藏域method=put
7.2 Rest原理(表单提交要使用REST的时候)
实现这个功能我们需要开启 Spring MVC 的 HiddenHttpMethodFilter 过滤器功能,它是专门用于解决 HTML 表单只支持 GET 和 POST 方法 的限制。
spring:
mvc:
hiddenmethod:
filter:
enabled: true
html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1 id="main">欢迎您</h1>
<form action = "/user" method="get">
<input type="submit" value="REST-GET 提交">
</form>
<form action = "/user" method="post">
<input type="submit" value="REST-POST 提交">
</form>
<form action = "/user" method="post" onsubmit="return debugForm(this)">
<input name="_method" type="hidden" value="delete">
<input type="submit" value="REST-DELETE 删除">
</form>
<form action = "/user" method="post">
<input name="_method" type="hidden" value="put">
<input type="submit" value="REST-PUT 推送">
</form>
</body>
</html>
Java代码:
@RestController
public class HelloController {
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getUser(){
return "GET-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String saveUser(){
return "POST-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.PUT)
public String putUser(){
return "PUT-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-张三";
}
}
这个机制是 Spring 为了支持 RESTful 风格而设计的,主要解决HTML 表单只支持 GET 和 POST 方法的问题。
原理解析:
- 前端页面表单提交时会带上
_method=PUT后端拿到后通过request.getParameter("_method")获取值 - 请求过来被HiddenHttpMethodFilter 过滤器拦截,他会做两件事
-
判断请求是否正常,并且是POST (
"POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null)
-
判断是否兼容以下请求; PUT、DELETE、PATCH →
ALLOWED_METHODS列表验证

- 如果是原生request (post),包装类 HttpMethodRequestWrapper 会重写了getMethod方法,返回的是传入的值,过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用RequestWrapper的 →
filterChain.doFilter(requestToUse, response)
HttpMethodRequestWrapper 重写了 getMethod 方法
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper { private final String method; public HttpMethodRequestWrapper(HttpServletRequest request, String method) { super(request); this.method = method; } public String getMethod() { return this.method; } }- 如果不是原生Request 则直接放行

- 如果是原生request (post),包装类 HttpMethodRequestWrapper 会重写了getMethod方法,返回的是传入的值,过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用RequestWrapper的 →
-
7.3 Rest使用客户端工具直接发
postman直接发delete和put等请求,无需Filter
7.4 Rest风格的其他注解
@PutMapping()
@DeleteMapping(“/user”)
7.5 扩展点:如何把_method 这个名字换成我们自己喜欢的
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
代码解析:
这段代码用了注解 @ConditionalOnMissingBean({HiddenHttpMethodFilter.class}),他表示 如果容器中没有 HiddenHttpMethodFilter 这个类型的组件就会给容器中注册一个 OrderedHiddenHttpMethodFilter组件,这个组件继承了 HiddenHttpMethodFilter 类,虽然这个类里面 DEFAULT_METHOD_PARAM 参数是 final修饰的,不能改,但是 methodParam 这个参数有set方法,通过修改这个参数可以把 _method 这个名字换成我们自己喜欢的名字
实现功能我们需要向容器中放入一个自己写的HiddenHttpMethodFilter方法。
代码如下:
@Configuration(proxyBeanMethods = false)
public class Webconfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_m");
return hiddenHttpMethodFilter;
}
}
同时因为参数名发生了变化,我们也需要调整前端代码的参数值,代码如下:
<form action = "/user" method="post">
<!--<input name="_method" type="hidden" value="delete">-->
<input name="_m" type="hidden" value="delete">
<input type="submit" value="REST-DELETE 删除">
</form>


7.6 请求映射原理
在Springboot中 dispatchservlet 是所有请求的开始,所有的请求都会来到dispatchservlet,那么每次发请求他是怎么找到哪个方法处理请求的呢?
他们的继承关系是 dispatchservlet 继承 FrameworkServlet,FrameworkServlet继承 HttpServletBean,HttpServletBean继承 HttpServlet。
idea怎么查看继承树?Ctrl键+H
查看 HttpServlet 源码发现他有doget、dopost方法,但是继承他的 HttpServletBean 并没有doget和dopost方法,继续往下找继承他的 FrameworkServlet ,该类里有这两个方法,并重写了 HttpServlet 里面的两个方法
我们无论调用什么方法都会执行 processRequest(request, response) 方法,并调用本类里面的 doService 方法
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}
protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}
protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
<!-- 这里都是初始化过程 -->
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = this.buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
this.initContextHolders(request, localeContext, requestAttributes);
<!-- 这里都是初始化过程 -->
try {
this.doService(request, response);
} catch (IOException | ServletException var16) {
failureCause = var16;
throw var16;
} catch (Throwable var17) {
failureCause = var17;
throw new NestedServletException("Request processing failed", var17);
} finally {
this.resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
this.logResult(request, response, (Throwable)failureCause, asyncManager);
this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
}
}
<!-- 这是一个抽象类方法,dispatchservlet类对他做了实现 -->
protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception;
虽然FrameworkServlet里的 doService 方法是抽象类,但是继承了 FrameworkServlet 类的 dispatchservlet 类有对 doservice方法的实现。
查看dispatchservlet源码:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 记录请求日志,用于调试和监控
this.logRequest(request);
<!--包含请求处理 作用:对于包含请求,保存请求属性的快照,以便后续恢复 -->
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) { // 判断是否为包含请求(forward/include)
attributesSnapshot = new HashMap();
Enumeration<?> attrNames = request.getAttributeNames();
label116:
while(true) {
String attrName;
do {
if (!attrNames.hasMoreElements()) {
// 没有更多属性时跳出循环
break label116;
}
attrName = (String)attrNames.nextElement();
} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));
// 只保存Spring相关属性或配置为清理包含时保存所有属性
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
<!-- 设置框架核心属性 -->
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext()); //应用上下文
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); 本地化解析器
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); //主题解析器
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource()); //主题源
<!-- FlashMap 处理 作用:处理重定向时的数据传递 -->
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); //从上一个请求传递过来的数据
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); //用于存储要传递到下一个请求的数据
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); //FlashMap 管理器
}
<!-- 请求路径解析 作用:解析请求路径信息并缓存,提高后续访问效率 -->
RequestPath previousRequestPath = null;
if (this.parseRequestPath) {
previousRequestPath = (RequestPath)request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request); // 解析并缓存请求路径
}
<!-- 核心请求分发 -->
try {
this.doDispatch(request, response); // 执行实际的请求分发处理
} finally {
// 清理工作
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot); // 恢复包含请求的属性
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request); // 恢复原始请求路径
}
}
}
doService 方法的主要职责:
- 预处理:日志记录、属性快照
- 环境设置:将 Spring MVC 核心组件暴露给请求
- 特殊功能处理:FlashMap 重定向数据传递
- 路径解析:为后续路由做准备
- 核心分发:调用 doDispatch 进行实际请求处理
- 清理恢复:确保请求后环境恢复原状
这个方法体现了 模板方法模式 的设计,为整个请求处理流程搭建了骨架,研究dispatchservlet类的源码,doService方法最终会执行本类里的 doDispatch(request, response) 方法,此为dispatchservlet类中我们要重点研究的方法,每个请求都会调用这个方法
完整请求处理流程总结
- 预处理:文件上传检查
- 路由:查找处理器和拦截器链
- 适配:获取对应的处理器适配器
- 缓存:HTTP 缓存验证
- 拦截前置:执行拦截器前置处理
- 执行:调用控制器方法
- 拦截后置:执行拦截器后置处理
- 结果处理:异常处理和视图渲染
- 拦截完成:执行拦截器完成回调
- 清理:资源清理工作
步骤实际演示:
假设请求 GET /user:
对 doService 方法打断点重启项目,发送请求
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 初始化变量
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
<!-- 1. 请求预处理
检查文件上传:如果是 multipart 请求,包装为 MultipartHttpServletRequest
设置标记以便后续清理-->
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
<!-- 2. 查找处理器执行链
调用之前分析的 getHandler() 方法
如果找不到处理器,返回 404 错误-->
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
<!--3. 获取处理器适配器
根据处理器类型获取对应的适配器
适配器模式:统一不同处理器的调用接口
常见适配器:RequestMappingHandlerAdapter、HttpRequestHandlerAdapter 等-->
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
<!--4. HTTP 缓存检查(GET/HEAD 请求)
检查 Last-Modified 头
如果资源未修改,返回 304 状态码,节省带宽-->
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return; // 资源未修改,直接返回 304
}
}
<!--5. 执行拦截器前置处理
按顺序执行所有拦截器的 preHandle 方法
如果任一拦截器返回 false,流程终止-->
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return; // 如果某个拦截器返回 false,中断请求处理
}
<!--6. 执行实际处理器方法
这是最核心的调用,以 RequestMappingHandlerAdapter 为例:
1. 参数解析:将请求参数绑定到方法参数
2. 方法调用:通过反射调用控制器方法
3. 返回值处理:处理 @ResponseBody、视图名等
4. 异常处理:处理方法抛出的异常-->
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
<!--7. 异步处理检查
如果处理器启动了异步处理,立即返回
后续处理由异步机制接管-->
if (asyncManager.isConcurrentHandlingStarted()) {
return; // 异步处理已开始,直接返回
}
<!--8. 视图处理和拦截器后置处理
applyDefaultViewName:如果没有明确设置视图名,应用默认视图名
applyPostHandle:执行拦截器的 postHandle 方法(逆序)-->
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
<!--9. 处理分发结果
这个方法内部:
处理异常:如果有异常,进行异常解析
渲染视图:调用视图渲染流程
触发完成回调:执行拦截器的 afterCompletion-->
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
<!--10. 最终清理工作 -->
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
请求进入 doDispatch方法的第2步 this.getHandler(processedRequest)(查找处理器执行链 )
这是 Spring MVC 中处理器查找的核心方法,它负责遍历所有处理器映射器来找到匹配当前请求的处理器执行链
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
<!-- handlerMappings 处理器映射
检查 handlerMappings 列表是否已初始化
如果为 null,说明没有配置任何处理器映射器,直接返回 null -->
if (this.handlerMappings != null) {
<!--2. 按配置顺序遍历所有的 HandlerMapping
基于你之前的信息,通常包含 5 种映射器:
RequestMappingHandlerMapping - 注解驱动控制器
BeanNameUrlHandlerMapping - Bean名称映射
RouterFunctionMapping - 函数式端点
SimpleUrlHandlerMapping - 显式URL映射
WelcomePageHandlerMapping - 欢迎页面 -->
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
//关键点:
//短路逻辑:第一个返回非 null 的映射器获胜
//顺序重要性:映射器的配置顺序影响匹配优先级
//返回类型:HandlerExecutionChain 而不仅仅是 Handler
HandlerMapping mapping = (HandlerMapping)var2.next();
//责任链模式 (Chain of Responsibility)
//多个处理器映射器组成责任链
//每个映射器检查自己是否能处理该请求
//第一个能处理的映射器承担责任
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

当请求到达时,DispatcherServlet 会按顺序遍历所有 HandlerMapping
一般的请求映射有5个,这里主讲 RequestMappingHandlerMapping 和 WelcomePageHandlerMapping这两个注解
this.handlerMappings = {ArrayList} size = 5
1 = {RequestMappingHandlerMapping} //处理注解驱动的控制器方法映射
2 = {BeanNameUrlHandlerMapping} //基于 Bean 名称的 URL 映射
3 = {RouterFunctionMapping} //处理函数式 Web 端点
4 = {SimpleUrlHandlerMapping} //显式配置的 URL 映射
5 = {WelcomePageHandlerMapping} //处理欢迎页面
程序会遍历所有 handlerMapping,优先查找 RequestMappingHandlerMapping(通常排在首位),然后执行其 mapping.getHandler(request) 方法。
RequestMappingHandlerMapping (保存了所有 @RequestMapping 和 handler 的映射规则)
是 Spring MVC 中最核心的处理器映射器,它的主要作用是:将 HTTP 请求映射到具体的控制器方法具体来说就是:处理注解驱动的映射
- 解析 @RequestMapping、@GetMapping、@PostMapping 等注解
- 建立 URL 路径与控制器方法的映射关系
RequestMappingHandlerMapping 通常是第一个被查询的,因为它处理最常用的注解驱动控制器。- 作用:
负责处理使用 @RequestMapping、@GetMapping、@PostMapping、@RestController 等注解控制器方法。
是 Spring MVC 中最常用的 HandlerMapping,支持基于注解的控制器映射。- 使用场景:
当你在 Controller 类或方法上使用 @RequestMapping(“/users”) 或 @GetMapping(“/list”)时,Spring会通过此 HandlerMapping 将请求映射到对应的方法。- 优先级:
优先级最高(通常排在第一位),是 Spring MVC 的核心组件。
-
第一轮:
RequestMappingHandlerMapping.getHandler(request)- 查找 @GetMapping(“/user”) 注解的方法
- 找到匹配的 UserController.getUser() 方法
- 构建 HandlerExecutionChain,包含:
- handler = HandlerMethod(UserController.getUser)
- interceptors = [登录拦截器, 日志拦截器]
- 返回非 null,循环终止
-
如果第一轮返回 null,继续:
- BeanNameUrlHandlerMapping.getHandler(request)
- RouterFunctionMapping.getHandler(request)
- …依次尝试
//这是 AbstractHandlerMapping.clss 类中的 getHandler 方法的完整实现,它构建了完整的处理器执行链
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 这是一个模板方法,定义了获取处理器执行链的标准流程
<!--1. 获取内部处理器
核心查找:调用抽象方法 getHandlerInternal(),由子类实现具体查找逻辑
默认处理器:如果找不到,尝试获取默认处理器(fallback)-->
Object handler = this.getHandlerInternal(request);
if (handler == null) {
handler = this.getDefaultHandler();
}
//2. 空值检查
if (handler == null) {
return null;
} else {
//3. 处理器名称解析
// Bean名称解析:如果返回的是字符串,将其视为 Bean 名称
// 从容器获取:从 Spring 应用上下文中获取实际的处理器 Bean(主要用于 BeanNameUrlHandlerMapping)
if (handler instanceof String) {
String handlerName = (String)handler;
handler = this.obtainApplicationContext().getBean(handlerName);
}
//4. 路径初始化
//路径缓存检查:避免重复解析请求路径
//初始化查找路径:解析并缓存 URL 路径信息
if (!ServletRequestPathUtils.hasCachedPath(request)) {
this.initLookupPath(request);
}
//5. 构建执行链
HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
//6. 日志记录
//分级日志:根据日志级别记录不同的详细信息
//异步请求特殊处理:异步请求不记录 debug 日志
if (this.logger.isTraceEnabled()) {
this.logger.trace("Mapped to " + handler);
} else if (this.logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
this.logger.debug("Mapped to " + executionChain.getHandler());
}
//7. CORS 处理
if (this.hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = this.getCorsConfiguration(handler, request);
if (this.getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = this.getCorsConfigurationSource().getCorsConfiguration(request);
config = globalConfig != null ? globalConfig.combine(config) : config;
}
if (config != null) {
config.validateAllowCredentials();
}
executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
}
<!--1. 获取内部处理器
核心查找:调用抽象方法 getHandlerInternal(),由子类实现具体查找逻辑
默认处理器:如果找不到,尝试获取默认处理器(fallback)-->
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 1. 初始化查找路径
String lookupPath = this.initLookupPath(request);
// 2. 获取读锁(线程安全)
this.mappingRegistry.acquireReadLock();
HandlerMethod var4;
try {
// 3. 核心查找逻辑
HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
// 4. 创建解析后的处理器方法
var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;
} finally {
// 5. 释放读锁
this.mappingRegistry.releaseReadLock();
}
return var4;
}
//5. 构建执行链(AbstractHandlerMethodMapping.class 方法内)
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// 将处理器和匹配的拦截器组合成执行链
HandlerExecutionChain chain = handler instanceof HandlerExecutionChain ? (HandlerExecutionChain)handler : new HandlerExecutionChain(handler);
//2. 遍历适配的拦截器
Iterator var4 = this.adaptedInterceptors.iterator();
while(var4.hasNext()) {
HandlerInterceptor interceptor = (HandlerInterceptor)var4.next();
// 对每个拦截器进行处理...
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
} else {
chain.addInterceptor(interceptor);
}
}
return chain;
}




在getHandlerInternal(HttpServletRequest request)方法里执行 3. 核心查找逻辑 lookupHandlerMethod 方法
这是 Spring MVC 中查找处理器方法的核心逻辑,实现了从多个候选方法中选择最佳匹配的算法
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
//1. 查找匹配的处理器方法( 直接路径匹配 )
// 返回最佳匹配的 HandlerMethod,如果没有匹配返回 null
List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); //从路径查找表中获取直接匹配的映射
if (directPathMatches != null) {
//进一步筛选(HTTP方法、参数等条件)
this.addMatchingMappings(directPathMatches, matches, request);
}
<!--2. 全量匹配(如果直接匹配失败)-->
//第二轮查找:遍历所有注册的映射
//使用模式匹配(如 /user/*、/user/{id} 等)
//同样会进行条件筛选
if (matches.isEmpty()) {
this.addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
<!--3. 无匹配处理-->
//如果没有找到任何匹配,调用 handleNoMatch
//通常抛出 404 异常或返回 null
if (matches.isEmpty()) {
return this.handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
} else {
<!--4. 单匹配直接返回-->
AbstractHandlerMethodMapping<T>.Match bestMatch = (Match)matches.get(0);
<!-- 当找到多个匹配时,Spring 需要确定最佳匹配 -->
if (matches.size() > 1) {
//4.1 排序匹配项
Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new MatchComparator(this.getMappingComparator(request));
matches.sort(comparator);
bestMatch = (Match)matches.get(0);
<!-- 排序规则(由 RequestMappingInfo 比较器决定):
路径模式:精确匹配 > 路径参数 > 通配符
/user/123 > /user/{id} > /user/'*'
参数条件:有参数条件 > 无参数条件
请求头条件:有头条件 > 无头条件
消费媒体类型:更具体的类型优先
生产媒体类型:更具体的类型优先
HTTP方法:具体方法 > 通配方法 -->
if (this.logger.isTraceEnabled()) {
this.logger.trace(matches.size() + " matching mappings: " + matches);
}
<!-- 4.2 预检请求特殊处理
对于 CORS 预检请求,如果有多个匹配且都有 CORS 配置,返回特殊标记-->
if (CorsUtils.isPreFlightRequest(request)) {
Iterator var7 = matches.iterator();
while(var7.hasNext()) {
AbstractHandlerMethodMapping<T>.Match match = (Match)var7.next();
if (match.hasCorsConfig()) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
}
} else {
<!--4.3 歧义检查
- 关键检查:如果最佳匹配和第二匹配的分数相同,抛出歧义异常
- 防止出现无法确定唯一处理器的情况-->
AbstractHandlerMethodMapping<T>.Match secondBestMatch = (Match)matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.getHandlerMethod().getMethod();
Method m2 = secondBestMatch.getHandlerMethod().getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
}
// 设置属性并返回最佳匹配
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
this.handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.getHandlerMethod();
}
}
-
性能优化:先尝试快速直接匹配,失败才全量匹配
-
确定性:通过严格排序规则确保结果可预测
-
安全性:歧义检查防止意外行为
-
扩展性:支持 CORS 等特殊场景

- 查找匹配的处理器方法(从路径查找表中获取直接匹配的映射):
private void addMatchingMappings(Collection<T> mappings, List<AbstractHandlerMethodMapping<T>.Match> matches, HttpServletRequest request) { Iterator var4 = mappings.iterator(); while(var4.hasNext()) { T mapping = var4.next(); T match = this.getMatchingMapping(mapping, request); if (match != null) { matches.add(new Match(match, (MappingRegistration)this.mappingRegistry.getRegistrations().get(mapping))); } } }



如果访问欢迎页,先走到 DispatcherServlet 类的 doDispatch(request, response) 方法,走到 getHandler(processedRequest),遍历所有的 handlermapping 找到 WelcomePageHandlerMapping,执行 mapping.getHandler(request) 方法,执行 getHandlerInternal(request) 方法,执行 lookupHandlerMethod 方法
再执行 addMatchingMappings 方法


总结:
- SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping。访问/能访问到index.html;
- SpringBoot自动配置了默认的RequestMappingHandlerMapping
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 HandlerMapping
- 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义HandlerMapping

7.7 普通参数与基本注解
- 普通参数与基本注解
-
注解:
-
@PathVariable- 用途:绑定 URL 路径中的占位符变量(RESTful 风格)
- 来源:URI 路径
@GetMapping(value = "/house/{houseId}/owner/{username}", name = "user-detail") public String getCar(@PathVariable Long houseId, @PathVariable String username) { return "houseId:"+houseId+" username:"+username; } //或者可以这么写:自定义变量名(当方法参数名 ≠ 路径变量名) @GetMapping("/users/{userId}") public User getUser(@PathVariable("userId") Long id) { return "houseId:"+id; }
-
@RequestHeader
用途:绑定 HTTP 请求头(Header)的值
🔍 完整语法(@RequestHeader 的常用属性)@RequestHeader( value = "Header-Name", // 要获取的 Header 名称(必填或通过默认位置传) defaultValue = "默认值", // 可选,默认值(如果 Header 不存在) required = false // 可选,是否必须存在(默认 true) )⚠️ 注意:defaultValue 和 required = true 不能同时使用,否则会冲突
实际上,只要写了 defaultValue,required 就自动变成 false@RequestMapping(value = "/user", method = RequestMethod.GET) public ResponseEntity getUser(@RequestHeader(value = "User-Agent", defaultValue = "unknown") String userAgent, @RequestHeader Map<String, String> allHeaders, @RequestParam("username") String name, @RequestParam(defaultValue = "1") int page, @RequestParam(required = false) String flag, @RequestParam List<Integer> ids ) { int id = ids.get(0); StringBuilder sb = new StringBuilder(); sb.append("=== 所有请求头 ===\n"); for (Map.Entry<String, String> entry : allHeaders.entrySet()) { sb.append(entry.getKey()) .append(": ") .append(entry.getValue()) .append("\n"); } return ResponseEntity.ok(allHeaders); }
-
@ModelAttribute
@ModelAttribute 是 Spring MVC 中非常重要的注解,主要用于数据绑定和模型数据管理用途:
- 方式一:将请求参数(query 或 form)绑定到一个 Java Bean 的属性上(类似 @RequestParam 的对象版);
- 方式二:在控制器方法执行前预加载数据到 Model(作为方法返回值或参数)。
工作原理:
当你在控制器方法中写:public String handle(@ModelAttribute User user)。Spring 会:- 创建一个 User 对象(调用无参构造函数);
- 遍历 User 类的所有 setter 方法或 public 字段(如 setName, setAge);
- 从当前请求的“请求参数”中查找同名参数(如 name, age);
- 自动调用 setter,把参数值注入进去。
这个过程叫 数据绑定(Data Binding),@ModelAttribute 不区分参数来自 URL 还是 body,只要request.getParameter(“xxx”) 能拿到,就能绑定
@PostMapping("/register") public String register(@ModelAttribute User user) { System.out.println(user.getName()); // 比如输出 "张三" System.out.println(user.getAge()); // 比如输出 30 return "success"; }html代码:
<form action="/register" method="post"> <input name="name" value="张三" /> <input name="age" value="30" /> <button type="submit">提交</button> </form> -
@RequestParam- 来源:?key=value 或 application/x-www-form-urlencoded 表单。
- 高级用法:设置默认值、是否必需、传递数组(⚠️ 注意:不能用于 JSON 请求体(那是 @RequestBody 的职责))等
@RequestMapping(value = "/user", method = RequestMethod.GET) public String getUser(@RequestParam("username") String name, @RequestParam(defaultValue = "1") int page, @RequestParam(required = false) String flag, @RequestParam List<Integer> ids) { int id = ids.get(0); return "GET-张三" + name + " page:" + page + "+ flag:" + flag + " and id:" + id; }
-
@Matrixvariable -
@Cookievalue -
@RequestBody- 用途:将 HTTP 请求体(body)反序列化为 Java 对象
- 来源:请求体(通常为 JSON 或 XML)
- 要求:请求头 Content-Type 通常为 application/json
@RequestMapping(value = "/user", method = RequestMethod.POST) public String saveUser(@RequestBody User user) { return "POST-李四"; }注意事项:
- 一个方法只能有一个 @RequestBody;
- 适用于 PUT/POST/PATCH 等带 body 的请求;
- 不适用于 application/x-www-form-urlencoded(此时用 @RequestParam 或 @ModelAttribute)。


-
-
@RequestAttribute
@RequestAttribute 是 Spring MVC 提供的一个注解,用于从 HTTP 请求(HttpServletRequest)的属性(Attribute)中获取指定名称的值,并将其注入到 Controller 方法的参数中常用于:
-
在拦截器中预处理数据并存入 request;
-
在多个 Controller 或 Handler 之间传递上下文;
-
存储当前用户信息、权限、traceId 等
⚠️ 注意:@RequestAttribute 不支持 defaultValue(和 @RequestParam 不同)!
如果需要默认值,必须用 required = false + 手动判空。✅ 基本用法
设置 RequestAttribute(通常在 Filter / Interceptor 中)
注意事项:- 一个方法只能有一个 @RequestBody;
- 适用于 PUT/POST/PATCH 等带 body 的请求;
- 不适用于 application/x-www-form-urlencoded(此时用 @RequestParam 或 @ModelAttribute)
✅ 常用属性
@RequestAttribute( value = "attrName", // 要获取的属性名(必填) required = true // 是否必须存在(默认 true) )✅ 三、典型使用场景
场景 1:统一认证后传递用户信息- 拦截器解析 JWT → 设置 request.setAttribute(“user”, user)
- Controller 用 @RequestAttribute(“user”) User user 直接获取
场景 2:链路追踪(Trace ID)
- Filter 生成 traceId → 存入 request attribute
- 所有日志、下游调用都可从中获取
场景 3:多步骤流程共享上下文
- 步骤1:设置 request.setAttribute(“orderContext”, ctx)
- 步骤2:用 @RequestAttribute 读取继续处理
-
⚠️ 注意:它操作的是 请求属性(Request Attribute),不是请求参数(Parameter)、请求头(Header)或 Cookie!
🔍 Request Attribute 是在一次 HTTP 请求生命周期内,由服务器端(如 Filter、Interceptor、Controller)临时存储在 HttpServletRequest 对象中的键值对,它不会从客户端传来,而是服务端自己设置的。
7.8 各类参数解析原理
在分析请求发过来后对参数的解析前,我们需要知道部分源码原理
HandlerAdapter 是 Spring MVC 中连接处理器与执行框架的核心接口,它解决了不同类型处理器的统一调用问题
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
// 关键步骤一:找到能处理请求的Handler
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 关键步骤二:用找到的合适的参数解析器执行目标方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
关键步骤一:HandlerMapping中找到能处理请求的Handler(Controller.method())
- 为当前Handler 找一个适配器 HandlerAdapter;


查找步骤如下:
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); 方法先找到原生的 handler ,再次进入 DispatcherServlet.getHandlerAdapter(Object handler) 方法。
遍历获取到的4个adapter,判断当前 adapter 是否支持获取到的 handlermethod,如果支持则返回对应的HandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException >{ if (this.handlerAdapters != null) { Iterator var2 = this.handlerAdapters.iterator(); while(var2.hasNext()) { HandlerAdapter adapter = (HandlerAdapter)var2.next(); if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }

this.handlerAdapters size = 4
0 = {RequestMappingHandlerAdapter}
1 = {HandlerFunctionAdapter}
2 = {HttpRequestHandlerAdapter}
3 = {SimpleControllerHandlerAdapter}
-
RequestMappingHandlerAdapter - 最常用的适配器
支持的处理器类型- HandlerMethod(由 @Controller 或 @RestController 注解的控制器方法)
- HandlerMapping中找到能处理请求的Handler(Controller.method()
- 为当前Handler 找一个适配器 HandlerAdapter
-
HandlerFunctionAdapter - Spring WebFlux (函数式)风格的适配器
支持的处理器类型
HandlerFunction(函数式 Web 端点) -
HttpRequestHandlerAdapter - 简单 HTTP 处理器适配器
支持的处理器类型
HttpRequestHandler 接口的实现 -
SimpleControllerHandlerAdapter - 传统控制器适配器
支持的处理器类型
Controller 接口的实现(传统的 Spring MVC 控制器)
关键步骤二:执行目标方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());(ha是适配器 RequestMappingHandlerAdapter - 最常用的适配器)
执行步骤:
先得到目标 handler ,再调用 AbstractHandlerMethodAdapter类的handle方法,handleInternal方法是一个抽象类,RequestMappingHandlerAdapter 继承了 AbstractHandlerMethodAdapter 类,所以会转到RequestMappingHandlerAdapter的handleInternal方法里
//这是 RequestMappingHandlerAdapter 中的核心方法,负责处理控制器方法的执行并确保线程安全
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
this.checkRequest(request);
ModelAndView mav;
//会话同步处理(关键部分)
//作用有三:
//1. 确保同一会话的请求按顺序处理
//2. 避免多个请求同时修改会话属性导致数据不一致
//3. 保护 @SessionAttributes 的数据完整性
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
// 如果没有互斥锁,使用 HttpSession 对象本身作为锁
Object mutex = WebUtils.getSessionMutex(session);
synchronized(mutex) {
// 关建步骤
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// 关建步骤
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
//普通响应的缓存准备
// 关建步骤
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
//缓存控制检查:只有当响应中没有设置 Cache-Control 头时才进行默认缓存处理
if (!response.containsHeader("Cache-Control")) {
if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
this.prepareResponse(response);
}
}
return mav;
}

- 寻找并设置参数解析器
参数解析器接口:
public interface HandlerMethodArgumentResolver {
//判断是否支持这种参数
boolean supportsParameter(MethodParameter parameter);
//支持就调用 resolve 解析
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
获取参数解析器,共计27个(因为Springboot版本不同,参数解析器数量也不同,有的是26个,有的是25个):
确定将要执行的目标方法的每一个参数的值是什么
argumentResolvers = {ArrayList@6967} size = 27
0 = {RequestParamMethodArgumentResolver@6970}
1 = {RequestParamMapMethodArgumentResolver@6971}
2 = {PathVariableMethodArgumentResolver@6972}
3 = {PathVariableMapMethodArgumentResolver@6973}
4 = {MatrixVariableMethodArgumentResolver@6974}
5 = {MatrixVariableMapMethodArgumentResolver@6975}
6 = {ServletModelAttributeMethodProcessor@6976}
7 = {RequestResponseBodyMethodProcessor@6977}
8 = {RequestPartMethodArgumentResolver@6978}
9 = {RequestHeaderMethodArgumentResolver@6979}
10 = {RequestHeaderMapMethodArgumentResolver@6980}
11 = {ServletCookieValueMethodArgumentResolver@6981}
12 = {ExpressionValueMethodArgumentResolver@6982}
13 = {SessionAttributeMethodArgumentResolver@6983}
14 = {RequestAttributeMethodArgumentResolver@6984}
15 = {ServletRequestMethodArgumentResolver@6985}
16 = {ServletResponseMethodArgumentResolver@6986}
17 = {HttpEntityMethodProcessor@6987}
18 = {RedirectAttributesMethodArgumentResolver@6988}
19 = {ModelMethodProcessor@6989}
20 = {MapMethodProcessor@6990}
21 = {ErrorsMethodArgumentResolver@6991}
22 = {SessionStatusMethodArgumentResolver@6992}
23 = {UriComponentsBuilderMethodArgumentResolver@6993}
24 = {PrincipalMethodArgumentResolver@6994}
25 = {RequestParamMethodArgumentResolver@6995}
26 = {ServletModelAttributeMethodProcessor@6996}
确定将要执行的目标方法的每一个参数的值是什么。
SpringMVC目标方法能写多少种参数类型。取决于参数解析器有多少
4. 获取返回值处理器,共计15个
returnValueHandlers = {ArrayList@7001} size = 15
0 = {ModelAndViewMethodReturnValueHandler@7003}
1 = {ModelMethodProcessor@7004}
2 = {ViewMethodReturnValueHandler@7005}
3 = {ResponseBodyEmitterReturnValueHandler@7006}
4 = {StreamingResponseBodyReturnValueHandler@7007}
5 = {HttpEntityMethodProcessor@7008}
6 = {HttpHeadersReturnValueHandler@7009}
7 = {CallableMethodReturnValueHandler@7010}
8 = {DeferredResultMethodReturnValueHandler@7011}
9 = {AsyncTaskMethodReturnValueHandler@7012}
10 = {ServletModelAttributeMethodProcessor@7013}
11 = {RequestResponseBodyMethodProcessor@7014}
12 = {ViewNameMethodReturnValueHandler@7015}
13 = {MapMethodProcessor@7016}
14 = {ServletModelAttributeMethodProcessor@7017}
4. 获取返回值处理器,共计15个
returnValueHandlers = {ArrayList@7001} size = 15
0 = {ModelAndViewMethodReturnValueHandler@7003}
1 = {ModelMethodProcessor@7004}
2 = {ViewMethodReturnValueHandler@7005}
3 = {ResponseBodyEmitterReturnValueHandler@7006}
4 = {StreamingResponseBodyReturnValueHandler@7007}
5 = {HttpEntityMethodProcessor@7008}
6 = {HttpHeadersReturnValueHandler@7009}
7 = {CallableMethodReturnValueHandler@7010}
8 = {DeferredResultMethodReturnValueHandler@7011}
9 = {AsyncTaskMethodReturnValueHandler@7012}
10 = {ServletModelAttributeMethodProcessor@7013}
11 = {RequestResponseBodyMethodProcessor@7014}
12 = {ViewNameMethodReturnValueHandler@7015}
13 = {MapMethodProcessor@7016}
14 = {ServletModelAttributeMethodProcessor@7017}
真正执行目标方法核心逻辑:Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
包括确定目标方法每一个参数的值
- 获取方法参数值
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs); - 如何确定目标方法每一个参数的值
// InvocableHandlerMethod.class 处理请求的方法
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
//这一行代码一放行就会执行目标方法(controller)
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return this.doInvoke(args);
}
//获取目标方法每一个参数的值
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
//获取方法的所有参数的说明
MethodParameter[] parameters = this.getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
} else {
Object[] args = new Object[parameters.length];
//遍历Controller方法上所有参数,我们这里有5个,会遍历5次
for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
//遍历所有参数解析器,判断27个参数解析器中哪个支持当前参数
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
// 解析参数以及参数赋的值
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
} catch (Exception var10) {
if (logger.isDebugEnabled()) {
String exMsg = var10.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw var10;
}
}
}
return args;
}
}
protected Object doInvoke(Object... args) throws Exception {
Method method = this.getBridgedMethod();
try {
return KotlinDetector.isSuspendingFunction(method) ? CoroutinesUtils.invokeSuspendingFunction(method, this.getBean(), args) : method.invoke(this.getBean(), args);
} catch (IllegalArgumentException var5) {
this.assertTargetBean(method, this.getBean(), args);
String text = var5.getMessage() != null ? var5.getMessage() : "Illegal argument";
throw new IllegalStateException(this.formatInvokeError(text, args), var5);
} catch (InvocationTargetException var6) {
Throwable targetException = var6.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException)targetException;
} else if (targetException instanceof Error) {
throw (Error)targetException;
} else if (targetException instanceof Exception) {
throw (Exception)targetException;
} else {
throw new IllegalStateException(this.formatInvokeError("Invocation failure", args), targetException);
}
}
}









解析已经获取的参数解析器 并获取参数 AbstractNamedValueMethodArgumentResolver
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
} else {
Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
} else if (namedValueInfo.required && !nestedParameter.isOptional()) {
this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
} else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
} catch (ConversionNotSupportedException var11) {
throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
} catch (TypeMismatchException var12) {
throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
}
if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) {
this.handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
}
}
this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
}
- Servlet APl:
WebRequest,ServletRequest,MultipartReguest,HttpSession,javax.servlet.http.PushBuilder,Principal、inputstream、Reader、HttpMethod、Locale、TimeZone、Zoneld - 复杂参数:
Map、Errors/BindingResult,Model、RedirectAttributes、ServletResponse、SessionStatus、UriComponentsBuilder、ServletUricomponentsBuilder - 自定义对象参数:
可以自动类型转换与格式化,可以级联封装
7.10 复杂参数
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes(重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilderServletUriComponentsBuilder
Map<String,Object> map,Model model,HttpServletRequest request 都是可以给request域中放数据: request.getAttribute();
@Controller
public class RedirectController {
@ResponseBody
@GetMapping("/success")
public Map gotosuccess(@RequestAttribute(required = false) String msg,
@RequestAttribute(required = false) Integer code,
HttpServletRequest request) {
Object msg1 = request.getAttribute("msg");
Object hello = request.getAttribute("hello");
Object world = request.getAttribute("world");
Object message = request.getAttribute("message");
Map<String, Object> map = new HashMap<>();
map.put("reqMethod", msg1);
map.put("annotation_msg", msg);
map.put("hello", hello);
map.put("world", world);
map.put("message",message);
return map;
}
@GetMapping("/params")
public String testParam(Map<String, Object> map,
Model model,
HttpServletRequest request,
HttpServletResponse response) { // 添加 RedirectAttributes
map.put("hello", "12345");
model.addAttribute("world", "hello666");
request.setAttribute("message", "helloworld");
Cookie cookie = new Cookie("c1", "v1");
cookie.setDomain("localhost");
response.addCookie(cookie);
return "forward:/success"; // 注意:redirect 而非 forward
}
@GetMapping("/gotopage")
public String gotopage(HttpServletRequest request) {
request.setAttribute("msg", "成功了");
request.setAttribute("code", 200);
return "forward:/success";
}
}
注意
controller类不能用@RestController注解@RestController = @Controller + @ResponseBody
因为@ResponseBody 的作用是:将方法返回值直接写入 HTTP 响应体(Response Body),而不是作为视图名进行跳转!
这会导致 Spring 不会把它当作“重定向指令”,而是当作一个普通字符串;最终浏览器不会跳转,只会显示这个字符串














7.11 spring mvc 用户自定义参数封装过程原理【用实体类接收参数】
因为篇幅过长,我单独写一篇文章,请移步至spring mvc 用户自定义参数封装过程原理【用实体类接收参数】
7.12 spring mvc 用户自定义参数封装过程原理【用实体类接收参数】
因为篇幅过长,我单独写一篇文章,请移步至spring mvc 自定义Converter 设置
更多推荐








所有评论(0)