SpringBoot2核心技术与响应式编程 (二)核心功能

接上文 《springboot 2 编程笔记 一
上篇文章我们了解了基础入门、底层注解、自动配置、以及实践等入门内容
接下来我们继续记录,有鉴于上篇更新的太慢了,这段时间我会加快进度,争取早日更新完!

  1. 配置文件
    1. YML文件
  2. web场景
    1. 静态资源目录
    2. welcome和favicon功能
    3. 静态资源配置原理
  3. 请求处理
    1. 请求参数处理
    2. REST原理

配置文件

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,我们修改请求路径:
在这里插入图片描述

  1. 静态资源访问前缀:
    默认无前缀,如果想要放行以指定前缀的路径开始的所有请求

    当前项目+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 会返回相应的图片
    在这里插入图片描述

  2. 改变默认的静态资源路径,只有在指定路径下的静态资源才可被加载

    spring:
      mvc:
        static-path-pattern: /resources/**
      web:
        resources:
          static-locations: classpath:/jingtai
    

    此时我们再访问原路径:http://localhost:8083/resources/img3.png则会返回404
    在这里插入图片描述
    只有访问指定静态资源文件夹下的资源才会生效:
    在这里插入图片描述

  3. 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功能

  1. 欢迎页
    可以配置静态资源路径,但无法设置静态资源的访问前缀,否则会导致默认无法访问 index.html。
    spring:
    #  mvc:
    #    static-path-pattern: /resources/**
      web:
        resources:
          static-locations: classpath:/jingtai
    
    输入 IP地址+端口号 即可进入欢迎页
    在这里插入图片描述
  2. 在网页栏添加图标
    我在这里试了好几次都失败了,不知道什么原因

6.3 静态资源配置原理

  1. SpringBoot启动默认加载xxxautoconfiguration类(自动配管类)

  2. 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 {
    
  3. 核心配置类 主要包含三个重要的内部配置类:

  • 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());
      		});
      	}
      }
      
      主要功能:
      1. 静态资源处理:配置 /webjars/** 和默认的静态资源路径
      2. 视图解析器:配置 JSP 等视图的解析
      3. 消息转换器:处理 JSON、XML 等数据格式转换
      4. 路径匹配:配置 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());
      	}
      }
    
  1. 配置文件的相关属性和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 风格详解

什么是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 风格的核心思想:

  1. 面向资源:一切操作都围绕资源进行
  2. 使用 HTTP 标准:充分利用 HTTP 方法、状态码、头部
  3. 无状态:每次请求独立,服务器不保存客户端状态
  4. 统一接口:一致的 API 设计模式
  5. 可寻址性:每个资源都有唯一的 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 方法的问题。

原理解析:

  1. 前端页面表单提交时会带上 _method=PUT 后端拿到后通过 request.getParameter("_method") 获取值
  2. 请求过来被HiddenHttpMethodFilter 过滤器拦截,他会做两件事
    1. 判断请求是否正常,并且是POST ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null
      在这里插入图片描述

    2. 判断是否兼容以下请求; 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 则直接放行
        在这里插入图片描述

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 方法的主要职责:

  1. 预处理:日志记录、属性快照
  2. 环境设置:将 Spring MVC 核心组件暴露给请求
  3. 特殊功能处理:FlashMap 重定向数据传递
  4. 路径解析:为后续路由做准备
  5. 核心分发:调用 doDispatch 进行实际请求处理
  6. 清理恢复:确保请求后环境恢复原状

这个方法体现了 模板方法模式 的设计,为整个请求处理流程搭建了骨架,研究dispatchservlet类的源码,doService方法最终会执行本类里的 doDispatch(request, response) 方法,此为dispatchservlet类中我们要重点研究的方法,每个请求都会调用这个方法

完整请求处理流程总结

  1. 预处理:文件上传检查
  2. 路由:查找处理器和拦截器链
  3. 适配:获取对应的处理器适配器
  4. 缓存:HTTP 缓存验证
  5. 拦截前置:执行拦截器前置处理
  6. 执行:调用控制器方法
  7. 拦截后置:执行拦截器后置处理
  8. 结果处理:异常处理和视图渲染
  9. 拦截完成:执行拦截器完成回调
  10. 清理:资源清理工作

步骤实际演示:

假设请求 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. 获取处理器适配器
						根据处理器类型获取对应的适配器
						适配器模式:统一不同处理器的调用接口
						常见适配器:RequestMappingHandlerAdapterHttpRequestHandlerAdapter-->
				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 的核心组件。
  1. 第一轮:RequestMappingHandlerMapping.getHandler(request)

    • 查找 @GetMapping(“/user”) 注解的方法
    • 找到匹配的 UserController.getUser() 方法
    • 构建 HandlerExecutionChain,包含:
      • handler = HandlerMethod(UserController.getUser)
      • interceptors = [登录拦截器, 日志拦截器]
    • 返回非 null,循环终止
  2. 如果第一轮返回 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();
	}
}
  1. 性能优化:先尝试快速直接匹配,失败才全量匹配

  2. 确定性:通过严格排序规则确保结果可预测

  3. 安全性:歧义检查防止意外行为

  4. 扩展性:支持 CORS 等特殊场景
    在这里插入图片描述

    1. 查找匹配的处理器方法(从路径查找表中获取直接匹配的映射):
    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 普通参数与基本注解

  1. 普通参数与基本注解
  • 注解:

    • @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}
在这里插入图片描述

  1. RequestMappingHandlerAdapter - 最常用的适配器
    支持的处理器类型

    • HandlerMethod(由 @Controller 或 @RestController 注解的控制器方法)
    • HandlerMapping中找到能处理请求的Handler(Controller.method()
    • 为当前Handler 找一个适配器 HandlerAdapter
  2. HandlerFunctionAdapter - Spring WebFlux (函数式)风格的适配器
    支持的处理器类型
    HandlerFunction(函数式 Web 端点)

  3. HttpRequestHandlerAdapter - 简单 HTTP 处理器适配器
    支持的处理器类型
    HttpRequestHandler 接口的实现

  4. 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;
}

在这里插入图片描述

  1. 寻找并设置参数解析器
    参数解析器接口:
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);
包括确定目标方法每一个参数的值

  1. 获取方法参数值
    Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
  2. 如何确定目标方法每一个参数的值
// 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, PrincipalinputstreamReaderHttpMethodLocaleTimeZoneZoneld
  • 复杂参数:
    MapErrors/BindingResultModelRedirectAttributesServletResponseSessionStatusUriComponentsBuilderServletUricomponentsBuilder
  • 自定义对象参数:
    可以自动类型转换与格式化,可以级联封装

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 设置

更多推荐