跳至主要內容

apzs...大约 116 分钟

1、前置准备

1.1 基础入门-SpringBoot2课程介绍

  1. Spring Boot 2核心技术

  2. Spring Boot 2响应式编程

image-20220829103716146
image-20220829103716146

1.2 基础入门-Spring生态圈

1.2.1 Spring能做什么

1. Spring的能力

官方文档:https://spring.io

image-20220829093535446
image-20220829093535446

Microservices:微服务(将一个应用拆分为一个个微小的功能模块,每一个微小的模块称为一个微服务)

Reactive:响应式编程(异步非阻塞方式,通过整个应用之间构建异步数据流的方式,可以占用少量资源(线程、CPU、内存),构建高吞吐量的应用)

Cloud:分布式(将大型应用全部拆分为微小模块后,就会产生分布式应用)

Web apps:网站应用(使用MVC开发Web应用,发请求返回json数据或发请求返回页面)

Serverless:无服务(简单快速开发一个函数式服务,无需购买任何服务器。将应用上传到云平台,按量实时计费,节省人力、财力、物力)

Event Driven:事件驱动(Spring将整个分布式系统来构建出一个实时的streaming data数据流,通过响应式的方式让整个系统占用少量的资源,就能完成高吞吐量的业务)

Batch:批处理

2. Spring的生态open in new window
image-20220828183928526
image-20220828183928526

覆盖了:

  • web开发
  • 数据访问
  • 安全控制
  • 分布式
  • 消息服务
  • 移动开发
  • 批处理
  • ......
3. Spring5重大升级
  • 响应式编程
  • 内部源码设计

基于Java8的一些新特性,如:接口默认实现。重新设计源码架构。

1.2.2 为什么用SpringBootopen in new window

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".

能快速创建出生产级别的Spring应用。

1. SpringBoot优点
  • Create stand-alone Spring applications
    • 创建独立Spring应用(使用SpringBoot也能创建Spring应用,不必再使用原生的Spring Framework来设置一大推的配置)
  • Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
    • 内嵌web服务器(不用打war包再部署到tomcat)
  • Provide opinionated 'starter' dependencies to simplify your build configuration
    • 自动starter依赖,简化构建配置(不用导入一大堆的jar包,不用手动控制各jar包的版本,不用创建一大堆的配置)
  • Automatically configure Spring and 3rd party libraries whenever possible
    • 自动配置Spring以及第三方功能(不用配置数据源等等)
  • Provide production-ready features such as metrics, health checks, and externalized configuration
    • 提供生产级别的监控、健康检查及外部化配置(不需要打开源代码。再修改配置重新打包再发布)
  • Absolutely no code generation and no requirement for XML configuration
    • 无代码生成、无需编写XML

SpringBoot是整合Spring技术栈的一站式框架

SpringBoot是简化Spring技术栈的快速开发脚手架

2. SpringBoot缺点
  • 人称版本帝,迭代快,需要时刻关注变化
  • 封装太深,内部原理复杂,不容易精通

1.3 基础入门-SpringBoot的大时代背景

1.3.1 微服务

James Lewis and Martin Fowler (2014)open in new window 提出微服务完整概念。

英文文档:https://martinfowler.com/microservices/

中文文档:微服务|YYGCui's blog (cuicc.com)open in new window

In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.——James Lewis and Martin Fowler (2014)open in new window

  • 微服务是一种架构风格
  • 一个应用拆分为一组小型服务
  • 每个服务运行在自己的进程内,也就是可独立部署和升级
  • 服务之间使用轻量级HTTP交互
  • 服务围绕业务功能拆分
  • 可以由全自动部署机制独立部署
  • 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术

1.3.2 分布式

image
image
1. 分布式的困难
  • 远程调用(通过发送HTTP使A服务调用B服务)
  • 服务发现(A想要调用B,此时B部署了4台,但并不知道哪台服务是否可用,因此需要服务发现,判断哪些机器可用)
  • 负载均衡(B4台正常运行的机器,调用哪台机器是由负载均衡决定)
  • 服务容错(有可能由于网络延迟,导致B服务调用失败,失败了该怎么做(返回一个默认数据))
  • 配置管理(B服务修改配置后,不可能将几十台机器全部修改配置,应该将所有的配置都放到配置中心里,只需修改配置中心里的配置,所有机器自动从配置中心里把配置都同步过来)
  • 服务监控(很多小服务,上到云平台后,每个服务所占用的资源,健康状况等等全部监控起来)
  • 链路追踪(A->B->C->D,当某个服务调用失败后,搜索整个链路,查看到底是哪个服务调用失败,导致整个链路失败)
  • 日志管理(大型分布式应用,日志如何管理)
  • 任务调度(比如A想执行定时任务,到底是这十几台同时触发,还是某一台触发。触发是并行方式还是串行方式)
  • ......
2. 分布式的解决
  • SpringBoot + SpringCloud
image2
image2

1.3.3 云原生

原生应用如何上云。 Cloud Native

1. 上云的困难
  • 服务自愈(当某个负责某个功能模块的机器宕机后,能够自动创建一个相同功能模块的服务)
  • 弹性伸缩(流量高峰期间,自动部署更多的机器来处理该功能,流量高峰过去以后能够自动减少机器运行该功能模块)
  • 服务隔离(某台机器同时部署ABC,当AB出现故障以后不会影响C
  • 自动化部署(不用手动的一个机器一个机器的配置)
  • 灰度发布(A调用B默认调1.0版,当B有新版本2.0后,可以让少量的机器部署B2.0版本,经过一段时间的验证后,才全部部署为2.0版本)
  • 流量治理(某些服务器执行的慢,某些服务器执行的快,则给并发高的服务器更高的流量,并能动态扩松服务器)
  • ......
2. 上云的解决
在这里插入图片描述
在这里插入图片描述

1.3、基础入门-SpringBoot官方文档架构

1.3.1 官网文档架构

image-20220828201900093image-20220828202938010

1.3.2 查看版本新特性open in new window

![GIF 2022-8-28 20-40-32](https://gitlab.com/apzs/image/-/raw/master/image/GIF 2022-8-28 20-40-32.gif)

2、SpringBoot基础入门

2.1 HelloWorld

Developing Your First Spring Boot Applicationopen in new window

image-20220828211653996
image-20220828211653996

2.1.1. 系统要求

  • Java 8
  • Maven 3.3+
  • IntelliJ IDEA 2019.1.2

2.1.2. 修改Maven配置文件

conf\settings.xml文件的相应位置添加如下代码

<mirrors>
	<mirror>
		<id>nexus-aliyun</id>
		<mirrorOf>central</mirrorOf>
		<name>Nexus aliyun</name>
		<url>http://maven.aliyun.com/nexus/content/groups/public</url>
	</mirror>
</mirrors>

<profiles>
	<profile>
		<id>jdk-1.8</id>

		<activation>
			<activeByDefault>true</activeByDefault>
			<jdk>1.8</jdk>
		</activation>

		<properties>
			<maven.compiler.source>1.8</maven.compiler.source>
			<maven.compiler.target>1.8</maven.compiler.target>
			<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
		</properties>
	</profile>
</profiles>

2.1.3. 需求

需求:浏览发送/hello请求,响应 “Hello,Spring Boot 2”

2.1.4 创建maven工程

1. 引入依赖
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.3.4.RELEASE</version>
</parent>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
</dependencies>
2. 创建主程序
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

3. 编写业务
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String handle01(){
        return "Hello, Spring Boot 2!";
    }
}
4. 运行&测试
  • 运行MainApplication
  • 浏览器输入http://localhost:8888/hello,将会输出Hello, Spring Boot 2!
5. 设置配置open in new window

maven工程的resource文件夹中创建application.properties文件。

## 设置端口号
server.port=8888
image-20220828212102157
image-20220828212102157
6. 打包部署open in new window

在pom.xml添加如下插件

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

在IDEA的Maven工具上点击运行 clean 、package,把helloworld工程项目的打包成jar包,

打包好的jar包被生成在helloworld工程项目的target文件夹内。

用cmd运行java -jar boot-01-helloworld-1.0-SNAPSHOT.jar,既可以运行helloworld工程项目。

将jar包直接在目标服务器执行即可。

2.2 依赖管理特性

  • 父项目做依赖管理
依赖管理
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.3.4.RELEASE</version>
</parent>

spring-boot-starter-parent项目的父项目如下:
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-dependencies</artifactId>
	<version>2.3.4.RELEASE</version>
</parent>
它几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制,spring-boot-dependencies项目的pom文件有如下依赖,这是Spring Boot的核心启动器
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter</artifactId>
	<version>2.3.4.RELEASE</version>
</dependency>
image-20220907102419282
image-20220907102419282
image-20220907102702629
image-20220907102702629
image-20220828215428180
image-20220828215428180
所有场景启动器最底层的依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter</artifactId>
	<version>2.3.4.RELEASE</version>
	<scope>compile</scope>
</dependency>
  • 无需关注版本号,自动版本仲裁

    1. 引入依赖默认都可以不写版本
    2. 引入非版本仲裁的jar,要写版本号。
  • 可以修改默认版本号

    1. 查看spring-boot-dependencies里的<properties>标签规定当前依赖的版本用的 key。
    2. 在当前项目里面重写配置,如下面的代码。
<properties>
	<mysql.version>5.1.43</mysql.version>
</properties>

<dependencies>
	<dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <!-- <version>${mysql.version}</version> -->
	</dependency>
</dependencies>

IDEA快捷键:

  • ctrl + shift + alt + U:以图的方式显示项目中依赖之间的关系。
  • alt + ins:相当于Eclipse的 Ctrl + N,创建新类,新包等。

2.3 自动配置特性

  • 自动配好Tomcat
    • 引入Tomcat依赖。
    • 配置Tomcat
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-tomcat</artifactId>
	<version>2.3.4.RELEASE</version>
	<scope>compile</scope>
</dependency>
  • 自动配好SpringMVC

    • 引入SpringMVC全套组件
    • 自动配好SpringMVC常用组件(功能)
  • 自动配好Web常见功能,如:字符编码问题

    • SpringBoot帮我们配置好了所有web开发的常见场景
public static void main(String[] args) {
    //1、返回我们IOC容器
    ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

    //2、查看容器里面的组件
    String[] names = run.getBeanDefinitionNames();
    for (String name : names) {
        System.out.println(name);
    }
}
@SpringBootApplication
等同于
@SpringBootConfiguration  (相当于@Configuration)
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
image-20220829085038868
image-20220829085038868
  • 各种配置拥有默认值

    • 默认配置最终都是映射到某个类上,如:MultipartProperties
    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
  • 按需加载所有自动配置项

    • 非常多的starter
    • 引入了哪些场景这个场景的自动配置才会开启
    • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
  • ......

3、注解

3.1 常用注解

@Bean@Component@Controller@Service@Repository@ComponentScan(组件扫描),它们是Spring的基本标签,在Spring Boot中并未改变它们原来的功能。

1. @Configuration详解

  • 基本使用
    • Full模式与Lite模式
    • 示例
/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods = true)(保证每个@Bean方法被调用多少次返回的组件都是单实例的)(默认)
 *      Lite(proxyBeanMethods = false)(每个@Bean方法被调用多少次返回的组件都是新创建的)
 */
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

@Configuration测试代码如下:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {

    public static void main(String[] args) {
    	//1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

    	//2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

    	//3、从容器中获取组件,配置类里面使用@Bean标注在方法上给容器注册组件,默认是单实例的
        Pet tom01 = run.getBean("tom", Pet.class);
        Pet tom02 = run.getBean("tom", Pet.class);
        System.out.println("组件:"+(tom01 == tom02)); //true

    	//4、配置类本身也是组件
        //如果设置@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
        //proxyBeanMethods属性在Spring 5.2版本新增,默认为true
        //拿到的是代理对象 com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
        //如果设置proxyBeanMethods=false,拿到的就不是代理对象 com.atguigu.boot.config.MyConfig@304a9d7b
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);
    	
        //如果设置proxyBeanMethods=true,外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例
        //因此输出的结果true
        User user = bean.user01();
        User user1 = bean.user01();
        //如果设置proxyBeanMethods=false,拿到的就不是代理对象而是原对象,调用user01()获取的User就不一样,因此结果为false
        System.out.println(user == user1);  

        //如果设置proxyBeanMethods=true,结果为true,表明是容器中的宠物
        ////如果设置proxyBeanMethods=false,结果为false,表明是又new的一个宠物
        User user01 = run.getBean("user01", User.class);
        Pet tom = run.getBean("tom", Pet.class);
        System.out.println("用户的宠物:"+(user01.getPet() == tom));
    }
}

使用@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)注解可以设置为多例模式

Full:(proxyBeanMethods=true)

Lite:(proxyBeanMethods=false)

  • 最佳实战
    • 配置类里配置的组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
    • 配置类里配置的组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)

lite:轻量级


IDEA快捷键:

  • Alt + Ins:生成getter,setter、构造器等代码。
  • Ctrl + Alt + B:查看类的具体实现代码。

2. @Import导入组件

@Import({User.class, DBHelper.class})给容器中自动创建出这两个类型的组件(默认使用无参构造)、默认组件的名字就是全类名

@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
}

测试类:


//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

//...

//5、获取组件
String[] beanNamesForType = run.getBeanNamesForType(User.class);
//com.atguigu.boot.bean.User
for (String s : beanNamesForType) {
    System.out.println(s);
}
//ch.qos.logback.core.db.DBHelper@2aa27288
DBHelper bean1 = run.getBean(DBHelper.class);
System.out.println(bean1);

3. @Conditional条件装配

条件装配:满足Conditional指定的条件,则进行组件注入

image3
image3
ConditionalOnBean:容器中存在某一个Bean
ConditionalOnMissingBean:容器中不存在某一个Bean

ConditionalOnClass:容器中有某一个类
ConditionalOnMissingClass:容器中没有某一个类

ConditionalOnResource:项目的类路径存在某-一个资源
ConditionalOnJava:是指定的Java版本号
ConditionalOnWebApplication:是一个Web应用
ConditionalOnNotWebApplication:不是一个Web应用
ConditionalOnSingleCandidate:容器中某个组件只有一个实例、或有多个实例但有一个实例是主实例(@Primary)
ConditionalOnProperty:配置文件里配置了某个属性

用@ConditionalOnMissingBean举例说明

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "tom")//没有tom名字的Bean时,MyConfig类的Bean才能生效。
public class MyConfig {

    @Bean
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

public static void main(String[] args) {
    //1、返回我们IOC容器
    ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

    //2、查看容器里面的组件
    String[] names = run.getBeanDefinitionNames();
    for (String name : names) {
        System.out.println(name);
    }

    boolean tom = run.containsBean("tom");
    System.out.println("容器中Tom组件:"+tom);//false

    boolean user01 = run.containsBean("user01");
    System.out.println("容器中user01组件:"+user01);//true

    boolean tom22 = run.containsBean("tom22");
    System.out.println("容器中tom22组件:"+tom22);//true

}

4. @ImportResource导入Spring配置文件

如果使用bean.xml文件配置bean,可以使用@ImportResource导入该配置文件

bean.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans ...">

    <bean id="haha" class="com.lun.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.lun.boot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>
</beans>

使用方法:

@ImportResource("classpath:beans.xml")
public class MyConfig {
...
}

测试类:

public static void main(String[] args) {
    //1、返回我们IOC容器
    ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

	boolean haha = run.containsBean("haha");
	boolean hehe = run.containsBean("hehe");
	System.out.println("haha:"+haha);//true
	System.out.println("hehe:"+hehe);//true
}

5. @ConfigurationProperties配置绑定

如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用

传统方法:

public class getProperties {
     public static void main(String[] args) throws FileNotFoundException, IOException {
         Properties pps = new Properties();
         pps.load(new FileInputStream("a.properties"));
         Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
         while(enum1.hasMoreElements()) {
             String strKey = (String) enum1.nextElement();
             String strValue = pps.getProperty(strKey);
             System.out.println(strKey + "=" + strValue);
             //封装到JavaBean。
         }
     }
 }

Spring Boot一种配置配置绑定:

@ConfigurationProperties + @Component

假设有配置文件application.properties

mycar.brand=BYD
mycar.price=100000

只有在容器中的组件,才会拥有SpringBoot提供的强大功能

@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}

Spring Boot另一种配置配置绑定:

@EnableConfigurationProperties + @ConfigurationProperties

  1. 开启Car配置绑定功能
  2. 把Car这个组件自动注册到容器中
@Configuration
@EnableConfigurationProperties(Car.class)
public class MyConfig {
...
}
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}

3.2、自动配置【源码分析】

自动包规则原理

Spring Boot应用的启动类:

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }

}

分析下@SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    ...
}

重点分析@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

1. @SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

@Configuration代表当前是一个配置类。

2. @ComponentScan

指定扫描哪个包下的Spring注解。

3. @EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

重点分析@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage

自动配置包,指定了默认的包规则。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)//给容器中导入一个组件
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}
  1. 利用Registrar给容器中批量导入一系列组件
  2. 将指定的一个包下的所有组件导入进MainApplication所在包下。

AutoConfigurationPackages.Registrar类的registerBeanDefinitions方法批量导入了一些组件。

public abstract class AutoConfigurationPackages {
    ...... 
	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
	    Registrar() {
	    }

	    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
	        AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
	    }

	    public Set<Object> determineImports(AnnotationMetadata metadata) {
	        return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
	    }
	}
    ......
}

该方法有两个参数AnnotationMetadata metadata, BeanDefinitionRegistry registry

AnnotationMetadata metadata表示注解的元信息(注解标在了哪,它的属性值都是什么)该注解被标注在com.atguigu.boot.MainApplication

image-20220907110452049
image-20220907110452049

new PackageImports(metadata).getPackageNames()创建PackageImports对象,将注解的元信息传进去,获得标注该注解的类所在的包集合

image-20220907111007455
image-20220907111007455

然后把它转化为数组

image-20220907111616519
image-20220907111616519

传给AutoConfigurationPackages类的register方法

image-20220907111941957
image-20220907111941957
@Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector类的selectImports方法规定了要导入哪些组件

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
image-20220907112428070
image-20220907112428070

所有的组件都是调用getAutoConfigurationEntry(annotationMetadata)方法得到的

利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   configurations = removeDuplicates(configurations);
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   configurations = getConfigurationClassFilter().filter(configurations);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

调用getCandidateConfigurations(annotationMetadata, attributes);方法获取所有候选配置,从这里面先移除一些重复的removeDuplicates(configurations),再排除一些东西getExclusions(annotationMetadata, attributes) checkExcludedClasses(configurations, exclusions) configurations.removeAll(exclusions)

如果把getCandidateConfigurations(annotationMetadata, attributes);获取所有候选配置放行,可以看到有127个,这127个全类名组件都是要准备导入进去的

image-20220907113136053
image-20220907113136053

调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
         + "are using a custom packaging, make sure that file is correct.");
   return configurations;
}

使用SpringFactoriesLoader Spring的工厂加载器,加载一些东西

image-20220907114152454
image-20220907114152454

加载什么呢?我们按住ctrl键,点击loadFactoryNames

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

可以看到调用本类SpringFactoriesLoaderloadSpringFactories方法

image-20220907114544184
image-20220907114544184

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader)方法首先判断result是否为空(如果不为空,重启一下项目就行了)

不管classLoader是否为空,都会在META-INF/spring.factories里查找配置

image-20220907141110112
image-20220907141110112
image-20220907140551588
image-20220907140551588
image-20220907140702554
image-20220907140702554

利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件

META-INF/spring.factories位置来加载一个文件。

  • 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
  • spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
## 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
## spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
#在`Auto Configure`注释里指明`EnableAutoConfiguration注解`需要加载的类,148-22+1=127
21 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
22 org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
23 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
...\
147 org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
148 org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
image-20220907141718643
image-20220907141718643

虽然我们127个场景的所有自动配置启动的时候默认全部加载,但是xxxxAutoConfiguration会按照条件装配规则(@Conditional)按需配置。

按需加载

AopAutoConfiguration类使用@ConditionalOnProperty注解当spring.aop.auto=true时就生效,不过后面添加了一个属性matchIfMissing = true指明如果没配置也认为匹配成功。由于我们此时没配置,所以该AopAutoConfiguration类也是生效的

内部类AspectJAutoProxyingConfiguration上添加@ConditionalOnClass(Advice.class)注解,如果不导aspectj相关的包,AspectJAutoProxyingConfiguration自动配置就不会生效

package org.springframework.boot.autoconfigure.aop;

import org.aspectj.weaver.Advice;
...
    
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Advice.class)
	static class AspectJAutoProxyingConfiguration {
        ..
    }
}
image-20220907142936482
image-20220907142936482

内部类ClassProxyingConfiguration上添加@ConditionalOnMissingClass("org.aspectj.weaver.Advice"),指明不存在org.aspectj.weaver.Advice类,该配置就生效。又添加@ConditionalOnProperty注解,指明spring.aop.proxy-target-class=true时或没有找到该配置时,该配置就生效。只要这些条件有一个不匹配,使用该注解标注的配置就不会生效。由于这两个都匹配了,所以最终该配置生效了,最后成功启用了jdk的动态代理

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
      matchIfMissing = true)
static class ClassProxyingConfiguration {

   ClassProxyingConfiguration(BeanFactory beanFactory) {
      if (beanFactory instanceof BeanDefinitionRegistry) {
         BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
         AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
         AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
      }
   }

}
image-20220907144630962
image-20220907144630962

自动配置流程

@AutoConfigureOrder用来指定自动配置的优先级,数值越小,优先级越高。Ordered.HIGHEST_PRECEDENCE=Integer.MIN_VALUE

@Configuration指明该类是配置类,proxyBeanMethods = false指明该类不使用cglib代理,直接使用原对象

@ConditionalOnWebApplication当该项目是Web项目才生效。type = Type.SERVLET指明必须是servlet类型的web项目(而不是reactive类型的web项目)

@ConditionalOnClass(DispatcherServlet.class)由于DispatcherServlet类是spring-webmvc-5.2.9.RELEASE.jar包里的,引入servlet类型web场景后,肯定存在DispatcherServlet类,所以该配置是生效的。

@AutoConfigureAfter指明在什么类之后执行。本类将在ServletWebServerFactoryAutoConfiguration类执行后,才能执行

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
    ...
}
image-20220907150633011
image-20220907150633011

DispatcherServletAutoConfiguration的内部类DispatcherServletConfiguration为例子:

DefaultDispatcherServletCondition就是本类(DispatcherServletAutoConfiguration)的内部类,如果DispatcherServletAutoConfiguration类生效了,DefaultDispatcherServletCondition肯定也存在。

ServletRegistrationjavax.servlet包下的,配置了tomcat,当然也会有该类

DispatcherServletConfiguration类又与WebMvcPropertie配置文件绑定

在内部类DispatcherServletConfiguration里,把DispatcherServlet加入到容器里,并指定name=DEFAULT_DISPATCHER_SERVLET_BEAN_NAME (dispatcherServlet)

由于本项目没有文件上传,所以容器中没有MultipartResolver类型的组件

@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)容器中也没有名字叫multipartResolver的组件时才生效

@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {

   @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
   public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
      DispatcherServlet dispatcherServlet = new DispatcherServlet();
      dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
      dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
      dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
      dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
      dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
      return dispatcherServlet;
   }

   @Bean
   @ConditionalOnBean(MultipartResolver.class)
   @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
   public MultipartResolver multipartResolver(MultipartResolver resolver) {
      // Detect if the user has created a MultipartResolver but named it incorrectly
      return resolver;
   }

}

该方法的意思是当容器中有MultipartResolver类,但该类放入到容器的名字不叫multipartResolver时,将用户放入到容器中的不叫multipartResolver名字的MultipartResolver类传进来,强制改名为multipartResolver (方法的名字叫multipartResolver)

@Bean
@ConditionalOnBean(MultipartResolver.class)  //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
	//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
	//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
	// Detect if the user has created a MultipartResolver but named it incorrectly
	return resolver;//给容器中加入了文件上传解析器;
}

CharacterEncodingFilter类是org.springframework.web.filter包下的,如果是servlet类型的、使用springweb应用,项目里肯定会该类。如果容器中没有characterEncodingFilterBean在这里也会帮你配置,并且如果配置文件中有配置,也会依照你的配置

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

   private final Encoding properties;

   public HttpEncodingAutoConfiguration(ServerProperties properties) {
      this.properties = properties.getServlet().getEncoding();
   }

   @Bean
   @ConditionalOnMissingBean
   public CharacterEncodingFilter characterEncodingFilter() {
      CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
      filter.setEncoding(this.properties.getCharset().name());
      filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
      filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
      return filter;
   }
    ...
}
image-20220907155720070
image-20220907155720070

SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先

总结

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxxProperties里面读取,xxxProperties和配置文件进行了绑定)
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 定制化配置
    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxxxAutoConfiguration ---> 组件 ---> xxxxProperties里面拿值 ----> application.properties

4、最佳实践

1、SpringBoot应用如何编写

  • 引入场景依赖

  • 查看自动配置了哪些(选做)

    • 自己分析,引入场景对应的自动配置一般都生效了
    • 配置文件中使用debug=true开启自动配置报告(可以看到哪些配置生效了,哪些配置没生效)。
      • Positive matches(生效)
      • Negative matches(不生效)
      • Exclusions 排除
  • 是否需要修改

    • 参照文档修改配置项

      • application-propertiesopen in new window,比如下面这些属性,修改启动时输出图片 或 输出的文本(或者在resources资源文件里,用默认的文件名命名,如文件叫banner.gif,spring也会优先使用用户的配置)

        | spring.banner.image.locationopen in new window | Banner image file location (jpg or png can also be used). | classpath:banner.gif | | ------------------------------------------------------------ | --------------------------------------------------------- | ---------------------- | | spring.banner.locationopen in new window | Banner text resource location. | classpath:banner.txt |

      • 自己分析。xxxxProperties绑定了配置文件的哪些。

    • 自定义加入或者替换组件

      • @Bean、@Component...
    • 自定义器 XXXXXCustomizer;(比如spring-boot-autoconfigure-2.3.4.RELEASE.jar包的org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryCustomizer类)

      image-20220907165312887
      image-20220907165312887
    • ......

2、Lombok简化开发

Lombok用标签方式代替构造器、getter/setter、toString()等鸡肋代码(我这里持有不同的看法,虽然Lombok可以快速生成实体类的常用方法,但是该工具侵入性太强,合作开发时必须全部安装该插件,并添加lombok依赖,而且IDEA也能快速生成这些方法,因此是否使用该工具需自行斟酌)。

spring boot已经对Lombok进行了版本管理。所以只需引入依赖,不需要指定版本号

 <dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
</dependency>

IDEA中File->Settings->Plugins,搜索安装Lombok插件,新版本的IDEA已经默认安装该插件了

@NoArgsConstructor
//@AllArgsConstructor
@Data
@ToString
@EqualsAndHashCode
public class User {

    private String name;
    private Integer age;

    private Pet pet;

    public User(String name,Integer age){
        this.name = name;
        this.age = age;
    }
}

简化日志开发

@Slf4j
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String handle01(@RequestParam("name") String name){
        log.info("请求进来了....");
        return "Hello, Spring Boot 2!"+"你好:"+name;
    }
}

常用注解:(注:对这些注解所做的解释是常用场景,并不是全部。比如@Setter @Getter可以作用在TYPE:类, 接口 (包括注解类型), 枚举声明、FIELD:字段声明 (包括枚举常量))

  • @Dataopen in new window :作用在类上,相当于@Getter+@Setter+@ToString+@EqualsAndHashCode+@RequiredArgsConstructor
  • @Setter @Getteropen in new window:作用于类或属性上,自动生成gettersetter方法
  • @ToStringopen in new window:作用在类上,生成toString()方法,常用属性:
    • ncludeFieldNames:是否包含字段名称,默认为true
    • exclude:需要排除的字段
    • callSuper:表示输出父类的toString,默认为false
  • @NoArgsConstructoropen in new window:作用在类上,自动生成无参构造器。(不过这个url貌似写错了,和源代码给的url不一致)
  • @AllArgsConstructoropen in new window:自动生成全参数构造函数。
  • @EqualsAndHashCodeopen in new window:作用在类上,生成equalshashCode方法(默认使用非静态,非瞬态的字段)。瞬态字段指的是使用transient关键字标注的字段,使用此关键字的字段不会被序列化(static标注的字段也不会被序列化),要想对transient关键字标注的字段序列化需要实现Externalizable接口。常用属性:
    • exclude:需要排除的字段
    • callSuper:是否将父类的equalshashCode方法加到该子类的计算字段的前面;

3、dev-tools

using.devtoolsopen in new window

image-20220907213115871
image-20220907213115871

Spring Boot includes an additional set of tools that can make the application development experience a little more pleasant. The spring-boot-devtools module can be included in any project to provide additional development-time features.——linkopen in new window

Applications that use spring-boot-devtools automatically restart whenever files on the classpath change. This can be a useful feature when working in an IDE, as it gives a very fast feedback loop for code changes. By default, any entry on the classpath that points to a directory is monitored for changes. Note that certain resources, such as static assets and view templates, do not need to restart the applicationopen in new window.——linkopen in new window

Triggering a restart

As DevTools monitors classpath resources, the only way to trigger a restart is to update the classpath. The way in which you cause the classpath to be updated depends on the IDE that you are using:

  • In Eclipse, saving a modified file causes the classpath to be updated and triggers a restart.
  • In IntelliJ IDEA, building the project (Build -> Build Project)(shortcut: Ctrl+F9) has the same effect.

添加依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

在IDEA中,项目或者页面修改以后:或者点击Build Project或使用ctrl+F9快捷键即可重新编译项目(不过这其实是重启了项目,并不是热更新)、点击Recompile 'User.java'或使用ctrl+shift+F9快捷键即可编译当前文件

image-20220907213619072
image-20220907213619072

如果想要使用重加载,可以使用付费的jrebelopen in new window。(Spring给的网址为jrebelopen in new window)

using.devtools.restartopen in new window

image-20220907213914213
image-20220907213914213

4、Spring Initailizr

Spring Initailizropen in new window是创建Spring Boot的初始化向导,默认通过https://start.spring.io创建

在IDEA中,菜单栏New ->Project -> Spring Initailizr

image-20220907215332952
image-20220907215332952

5、配置文件

5.1、yaml的用法

YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。

非常适合用来做以数据为中心的配置文件

基本语法

  • key: value;kv之间有空格

  • 大小写敏感

  • 使用缩进表示层级关系

  • 缩进不允许使用tab,只允许空格(不过使用IDEA不用担心这个问题,会自动转化为4个空格,放心大胆用就行了)

  • 缩进的空格数不重要,只要相同层级的元素左对齐即可

  • #表示注释

  • 字符串无需加引号,也可以加引号

    • 单引号'zhang \n san'(内容会被转义,输出'zhang \n san')

    • 双引号"zhang \n san"(内容不会被转义,输出zhang+换行+san)

      zhang
      san
      

数据类型

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
  • 对象:键值对的集合。map、hash、object
#行内写法:  

k: {k1:v1,k2:v2,k3:v3}

#或

k: 
  k1: v1
  k2: v2
  k3: v3
  • 数组:一组按次序排列的值。array、list、set、queue
#行内写法:  

k: [v1,v2,v3]

#或者

k:
 - v1
 - v2
 - v3

实例

@Data
public class Person {
    private String userName;
    private Boolean boss;
    private Date birth;
    private Integer 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;
}

@Data
public class Pet {
    private String name;
    private Double weight;
}

用yaml表示以上对象

person:
  userName: zhangsan
  boss: false
  birth: 2019/12/12 20:12:33
  age: 18
  pet: 
    name: tomcat
    weight: 23.4
  interests: [篮球,游泳]
  animal: 
    - jerry
    - mario
  score:
    english: 
      first: 30
      second: 40
      third: 50
    math: [131,140,148]
    chinese: {first: 128,second: 136}
  salarys: [3999,4999.98,5999.99]
  allPets:
    sick:
      - {name: tom}
      - {name: jerry,weight: 47}
      - name: bigWhite
        weight: 57
    health: [{name: mario,weight: 47}]

5.2、自定义类绑定的配置提示

You can easily generate your own configuration metadata file from items annotated with @ConfigurationProperties by using the spring-boot-configuration-processor jar. The jar includes a Java annotation processor which is invoked as your project is compiled.——linkopen in new window

自定义的类和配置文件绑定一般没有提示。若要提示,添加如下依赖:(需要重启项目才能有提示,2.5版本后的没有了)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

<!-- 下面插件作用是工程打包时,不将spring-boot-configuration-processor打进包内,让其只在编码的时候有用 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-configuration-processor</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

6、web场景

6.1 web开发简介

Spring Boot provides auto-configuration for Spring MVC that **works well with most applicationsopen in new window:大多场景我们都无需自定义配置

The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

    • 内容协商视图解析器和BeanName视图解析器
  • Support for serving static resources, including support for WebJars (covered later in this documentopen in new window)).

    • 静态资源(包括webjars)
  • Automatic registration of Converter, GenericConverter, and Formatter beans.

    • 自动注册 Converter,GenericConverter,Formatter
  • Support for HttpMessageConverters (covered later in this documentopen in new window).

    • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  • Automatic registration of MessageCodesResolver (covered later in this documentopen in new window).

    • 自动注册 MessageCodesResolver (国际化用)
  • Static index.html support.

    • 静态index.html 页支持
  • Custom Favicon support (covered later in this documentopen in new window).

    • 自定义 Favicon (页面的小图标)
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this documentopen in new window).

    • 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

If you want to keep those Spring Boot MVC customizations and make more MVC customizationsopen in new window (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

声明 WebMvcRegistrations 改变默认底层组件

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.

使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

6.2 静态资源规则与定制化

静态资源目录open in new window

只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources

访问 : 当前项目根路径/ + 静态资源名(如果第一次能访问,修改文件后重新启动项目,不能访问,可以查看target->classes里是否有该资源,如果没有,可以删除classes文件,重新运行) 使用url访问资源,尤其是静态资源,如遇无法访问,可以查看编译后的classes文件里是否有该资源

image-20220908200822681
image-20220908200822681
localhost:8080/1.gif
localhost:8080/2.gif
localhost:8080/3.gif
localhost:8080/image/4.gif
localhost:8080/5.gif

请求进来,先去找Controller看能不能处理。如果不能处理,由于静态资源配置的是静态映射/**,所以所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面。

也可以改变默认的静态资源路径,修改后/static/public,/resources, /META-INF/resources这些路径将无法访问

(不过评论说该配置好像已经过时了)

单个路径

spring:
  resources:
    static-locations: classpath:/image

多个路径

spring:
  resources:
    static-locations: ["classpath:/image","classpath:/css"]

spring:
  resources:
    static-locations:
      - classpath:/image
      - classpath:/css
image-20220908205814539
image-20220908205814539
#以下路径可以正常访问
localhost:8080/6.gif
localhost:8080/7.gif
localhost:8080/other/8.gif
#前面添加的资源将无法访问
localhost:8080/1.gif
localhost:8080/2.gif
localhost:8080/3.gif
localhost:8080/image/4.gif
localhost:8080/5.gif

静态资源访问前缀

spring:
  mvc:
    static-path-pattern: /res/**

访问路径:当前项目路径 + static-path-pattern + 静态资源名

localhost:8080/res/6.gif
localhost:8080/res/7.gif
localhost:8080/res/other/8.gif

完整示例

spring:
  resources:
    static-locations:
      - classpath:/image
      - classpath:/css
  mvc:
    static-path-pattern: /res/**
server:
  servlet:
    context-path: /demo

访问路径

localhost:8080/demo/res/6.gif
localhost:8080/demo/res/7.gif
localhost:8080/demo/res/other/8.gif

webjars

可用jar方式添加css,js等资源文件,访问路径默认以/webjars/**开始,不需要配置(以后基本用不到)

官方文档:https://www.webjars.org/open in new window

跳转到源码分析:资源处理的默认规则

例如,添加jquery

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
</dependency>
image-20220908211416552
image-20220908211416552

访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js (其实就是jar包里resources后面的路径)

(我这里配了项目路径,因此可以访问 http://localhost:8080/demo/webjars/jquery/3.5.1/jquery.js )

6.3 welcome与favicon功能

官方文档open in new window

欢迎页支持

Spring Boot supports both static and templated welcome pages. It first looks for an index.html file in the configured static content locations. If one is not found, it then looks for an index template. If either is found, it is automatically used as the welcome page of the application.

  • 静态资源路径下 index.html。

    • 可以配置静态资源路径
    • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring:
##  mvc:
##    static-path-pattern: /res/**   这个会导致welcome page功能失效
  resources:
    static-locations: [classpath:/haha/]
  • controller能处理/index请求。

index.html页面放在src/main/resources/image里(静态资源的测试建议删除target目录里的文件,再重启项目)

`spring.resources.static-locations` `spring.mvc.static-path-pattern` `server.servlet.context-path`全部配置
#不能访问
http://localhost:8080/
http://localhost:8080/demo
http://localhost:8080/demo/res
#可以访问
http://localhost:8080/demo/res/index.html

配置`spring.resources.static-locations` `spring.mvc.static-path-pattern`,不配置`server.servlet.context-path`
#不能访问
http://localhost:8080/
http://localhost:8080/res
#可以访问
http://localhost:8080/res/index.html

配置`spring.resources.static-locations` `server.servlet.context-path` 不配置 `spring.mvc.static-path-pattern`
#不能访问
http://localhost:8080/                 #tomcat默认页
#可以访问
http://localhost:8080/demo
http://localhost:8080/demo/index.html

总结,如果只配置spring.resources.static-locations或使用默认配置则主机+端口就能访问欢迎页,如果还配置了项目路径server.servlet.context-path则访问欢迎页需要访问主机+端口+项目路径,如果还配置了静态资源前缀spring.mvc.static-path-pattern则不能使用简写路径快速访问欢迎页,必须写全路径,即主机+端口+项目路径+静态资源前缀+欢迎页路径

spring.mvc.servlet.pathspring.resources.static-locations很类似,只不过spring.mvc.servlet.path是访问请求的,而spring.resources.static-locations是访问静态资源的

controller添加/index请求映射

@GetMapping("/index")
public String hello(){
    //return "res/hello.html"; 也可以
    return "/res/hello.html";
}

结果

配置`server.servlet.context-path`
#不能访问
http://localhost:8080/          #tomcat默认404页
http://localhost:8080/demo/     #spring默认404页
#可以访问
http://localhost:8080/demo/index

不配置`server.servlet.context-path`
#不能访问
http://localhost:8080/           #spring默认404页
#可以访问
http://localhost:8080/index

spring.resources.static-locations spring.mvc.static-path-pattern server.servlet.context-path全部配置,并添加

@GetMapping("/")
public String hello1(){
    //return "res/hello.html"; 也可以
    return "/res/hello.html";
}

结果

#不能访问
http://localhost:8080/    #tomcat默认404页
#可以访问
http://localhost:8080/demo/

总结:

controller能处理/index请求,并不能快速访问欢迎页。让controller处理/请求,才能最快速访问,是通过controller访问欢迎页的最佳写法。

如果设置了项目路径server.servlet.context-path为其他的路径,则访问 http://localhost:8080/ 会来到tomcat默认的404页面(Spring只管项目路径里面的,外面的归原生的tomcat管),访问项目路径下的未找到的路径,会来到spring的默认404页面

自定义Favicon

指网页标签上的小图标。

favicon.ico 放在静态资源目录下即可。(配置项目路径静态路径,快速访问欢迎页和小图标都会失效)这结果很让人费解,按理说根据上面的测试,配置静态路径会无法访问我能理解,但是配置项目路径竟然也无法访问,按理说只要在项目路径里面应该不影响啊,不应该是从项目根路径开始吗?

spring:
##  mvc:
##    static-path-pattern: /res/**  会导致 Favicon 功能失效
#server:
##  servlet:
##    context-path: /demo            会导致 Favicon 功能失效

总结:只有小图标路径为主机+端口+favicon.ico (http://localhost:8080/favicon.ico) 时才会生效,配置项目路径server.servlet.context-path后,主机+端口+项目路径+favicon.ico(http://localhost:8080/demo/favicon.ico)小图标不会生效,配置静态资源前缀spring.mvc.static-path-pattern后,主机+端口+静态资源前缀+favicon.ico(http://localhost:8080/res/favicon.ico)小图标也不会生效

6.4 web场景-源码分析

1. 静态资源配置(WebMvcAutoConfiguration)

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类WebMvcAutoConfiguration,生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    ...
}
image-20220909111510246
image-20220909111510246

配置了OrderedHiddenHttpMethodFilter,该类是为了兼容Restful风格

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
   return new OrderedHiddenHttpMethodFilter();
}
image-20220909111847936
image-20220909111847936

表单内容过滤器

@Bean
@ConditionalOnMissingBean(FormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
public OrderedFormContentFilter formContentFilter() {
   return new OrderedFormContentFilter();
}
image-20220909111957422
image-20220909111957422

给容器中配置的内容:WebMvcAutoConfiguration类的内部类 WebMvcAutoConfigurationAdapter

配置文件的相关属性的绑定:WebMvcProperties==spring.mvcResourceProperties==spring.resources

@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
    ...
}
image-20220909114347193
image-20220909114347193

配置类只有参构造器,所有参数的值都会从容器中获取

WebMvcAutoConfiguration类的内部类 WebMvcAutoConfigurationAdapter的构造器

  • ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
  • WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
  • ListableBeanFactory beanFactory Spring的bean工厂(ioc容器)
  • HttpMessageConverters 找到所有的HttpMessageConverters
  • ResourceHandlerRegistrationCustomizer 找到资源处理器的自定义器。
  • DispatcherServletPath
  • ServletRegistrationBean 给应用注册原生的Servlet、Filter、Listener....
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
		ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
		ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
		ObjectProvider<DispatcherServletPath> dispatcherServletPath,
		ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
	this.mvcProperties = mvcProperties;
	this.beanFactory = beanFactory;
	this.messageConvertersProvider = messageConvertersProvider;
	this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
	this.dispatcherServletPath = dispatcherServletPath;
	this.servletRegistrations = servletRegistrations;
	this.mvcProperties.checkConfiguration();
}
image-20220909190647341
image-20220909190647341

把所有的HttpMessageConverter拿过来

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
   this.messageConvertersProvider
         .ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}
image-20220909191146420
image-20220909191146420

视图解析器

@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
   InternalResourceViewResolver resolver = new InternalResourceViewResolver();
   resolver.setPrefix(this.mvcProperties.getView().getPrefix());
   resolver.setSuffix(this.mvcProperties.getView().getSuffix());
   return resolver;
}
image-20220909191451899
image-20220909191451899

国际化支持

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
   if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
      return new FixedLocaleResolver(this.mvcProperties.getLocale());
   }
   AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
   localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
   return localeResolver;
}
image-20220909191715083
image-20220909191715083

消息代码解析器(可以配置指定类型或不在指定类型内的错误的返回结果)

@Override
public MessageCodesResolver getMessageCodesResolver() {
   if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
      DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
      resolver.setMessageCodeFormatter(this.mvcProperties.getMessageCodesResolverFormat());
      return resolver;
   }
   return null;
}
image-20220909191830050
image-20220909191830050

格式化货币、日期等

@Override
public void addFormatters(FormatterRegistry registry) {
   ApplicationConversionService.addBeans(registry, this.beanFactory);
}
image-20220909193428461
image-20220909193428461
资源处理的默认规则

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addResourceHandlers

跳转到如何使用:webjars

...
public class WebMvcAutoConfiguration {
    ...
    @Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
        ...
		@Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
            //绑定的ResourceProperties配置类(与spring.resources绑定)的addMappings属性如果设置false,则会禁用静态资源配置(直接return了,下面的语句根本不会执行)
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
            //缓存存放时间,以秒为单位,默认为null
			Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
			CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
            //  registry访问规则里注册
            //  /webjar/**的访问规则
			if (!registry.hasMappingForPattern("/webjars/**")) {
                //访问 /webjars/** 这个请求,都去 classpath:/META-INF/resources/webjars/ 这里找
				customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
						.addResourceLocations("classpath:/META-INF/resources/webjars/")
                        //设置刚刚从配置文件里读取到的缓存配置
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
			}
            //静态资源路径的访问规则
            //WebMvcProperties配置类(与spring.mvc绑定)的静态资源路径(默认/** )
			String staticPathPattern = this.mvcProperties.getStaticPathPattern();
			if (!registry.hasMappingForPattern(staticPathPattern)) {
                //在`staticLocations`字段的属性值CLASSPATH_RESOURCE_LOCATIONS,默认:{ "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" }
				customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
						.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
			}
		}
        ...
    }
    ...
}
image-20220909202640702
image-20220909202640702

根据上述代码,我们可以同过配置禁止所有静态资源规则。

spring:
  resources:
    add-mappings: false   #禁用所有静态资源规则

静态资源规则:

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
            "classpath:/resources/", "classpath:/static/", "classpath:/public/" };

    /**
     * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
     * /resources/, /static/, /public/].
     */
    private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
    ...
}

欢迎页的处理规则

HandlerMapping处理器映射,保存了每一个Handler能处理哪些请求

...
public class WebMvcAutoConfiguration {
    ...
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
        ...
		@Bean
		public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
				FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
			WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
					new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
					this.mvcProperties.getStaticPathPattern());
			welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
			welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
			return welcomePageHandlerMapping;
		}
        ...
    }
    ...
}
image-20220909210353318
image-20220909210353318

WelcomePageHandlerMapping的构造方法如下:

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
                          ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
    if (welcomePage != null && "/**".equals(staticPathPattern)) {
        //要用欢迎页功能,静态资源路径必须是/**
        logger.info("Adding welcome page: " + welcomePage);
        setRootViewName("forward:index.html");
    }
    else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
        //调用Controller /index
        logger.info("Adding welcome page template: index");
        setRootViewName("index");
    }
}
image-20220909204027182
image-20220909204027182

这构造方法内的代码也解释了web场景-welcome与favicon功能中配置static-path-pattern了,welcome页面和小图标失效的问题。

(小图标的枚举在org.springframework.boot.autoconfigure.security.StaticResourceLocation类的FAVICON("/**/favicon.ico");这,但是好像用到该类的类并没有引入)

7、请求处理-【源码分析】

1 Rest映射及源码解析

2 请求映射

  • @xxxMapping;

    • @GetMapping
    • @PostMapping
    • @PutMapping
    • @DeleteMapping
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)

    以前现在: /user
    /getUser 获取用户GET 获取用户
    /deleteUser 删除用户DELETE 删除用户
    /editUser 修改用户PUT 修改用户
    /saveUser 保存用户POST 保存用户
    • 核心Filter;HiddenHttpMethodFilter
  • 用法

    • 开启页面表单的Rest功能
    • 页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)
    • 编写请求映射
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启页面表单的Rest功能

(完整的页面在后面用到的时候会给出)

<form action="/user" method="get">
    <input value="REST-GET提交" type="submit"/>
</form>

<form action="/user" method="post">
    <input value="REST-POST提交" type="submit"/>
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="DELETE"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT"/>
    <input value="REST-PUT提交" type="submit"/>
</form>
@GetMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
    return "GET-张三";
}

@PostMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
    return "POST-张三";
}

@PutMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
    return "PUT-张三";
}

@DeleteMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
    return "DELETE-张三";
}
  • Rest原理(表单提交要使用REST的时候)
    • 表单会提交带_method=PUT参数的POST请求 (表单只能发送getpost请求)
    • 请求过来被HiddenHttpMethodFilter拦截
      • 请求是否正常,并且是POST
        • 获取到\_method的值。
        • 兼容以下请求;PUTDELETEPATCH
        • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
        • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
  • Rest使用客户端工具。
    • 如PostMan可直接发送put、delete等方式请求,不需要包装getMethod(),直接使用原生的HttpServletRequest

想要开启Rest功能,必须配置spring.mvc.hiddenmethod.filter.enabled=true,如果没配置则不会生效

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
   return new OrderedHiddenHttpMethodFilter();
}
image-20220910145245307
image-20220910145245307

public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter

OrderedHiddenHttpMethodFilter类继承HiddenHttpMethodFilter

public class HiddenHttpMethodFilter extends OncePerRequestFilter {

	private static final List<String> ALLOWED_METHODS =
			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));

	/** Default method parameter: {@code _method}. */
    //需要在表单里带一个隐藏的请求方式:<input name="_method" type="hidden" value="DELETE"/>
	public static final String DEFAULT_METHOD_PARAM = "_method";

	private String methodParam = DEFAULT_METHOD_PARAM;


	/**
	 * Set the parameter name to look for HTTP methods.
	 * @see #DEFAULT_METHOD_PARAM
	 */
	public void setMethodParam(String methodParam) {
		Assert.hasText(methodParam, "'methodParam' must not be empty");
		this.methodParam = methodParam;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		//拿到原生的请求
		HttpServletRequest requestToUse = request;
        //如果原生的请求是Post请求方式,并且没有javax.servlet.error.exception错误,才解析隐藏的请求方式
		// WebUtils.ERROR_EXCEPTION_ATTRIBUTE = "javax.servlet.error.exception"
		if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
            //获得key为默认的_method的值,用来判断隐藏的登陆方式
			String paramValue = request.getParameter(this.methodParam);
            //获得默认的_method的值后,转为大写,然后重新设置请求方式
			if (StringUtils.hasLength(paramValue)) {
				String method = paramValue.toUpperCase(Locale.ENGLISH);
                //如果该隐藏的请求方式为(put、delete、patch),就将原生的请求方式包装为为该隐藏的请求方式
				if (ALLOWED_METHODS.contains(method)) {
                    //将原生的HttpServletRequest替换为重写了getMethod()方法的HttpMethodRequestWrapper(装饰器模式)
                    //PostMan可直接发送put、delete等方式的请求,不需要包装`getMethod()`,直接使用原生的`HttpServletRequest`,由于请求方式不为POST,所以也进不来,因此也没被包装
					requestToUse = new HttpMethodRequestWrapper(request, method);
				}
			}
		}
		//过滤器链放行的时候用的是重写了getMethod()方法的HttpMethodRequestWrapper,后面获取到的HttpServletRequest就是被包装的HttpMethodRequestWrapper,后面调用getMethod()方法就变为被包装的getMethod()方法
		filterChain.doFilter(requestToUse, response);
	}


	/**
	 * Simple {@link HttpServletRequest} wrapper that returns the supplied method for
	 * {@link HttpServletRequest#getMethod()}.
	 * 包装HttpServletRequest
	 */
	private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {

		private final String method;

		public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
			super(request);
			this.method = method;
		}
		//重写HttpServletRequest类的getMethod
		@Override
		public String getMethod() {
			return this.method;
		}
	}

}
image-20220910151914905
image-20220910151914905
改变默认的_method(p27)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

    ...
    
    @Bean
    @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
    @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }
    
    ...
}
    

@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)意味着在没有HiddenHttpMethodFilter时,才执行hiddenHttpMethodFilter()。因此,我们可以自定义filter,修改默认的methodParam字段的值(_method)。例如:

@Configuration(proxyBeanMethods = false)
public class WebConfig{
    //自定义filter
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }    
}

_method改成_m

<form action="/user" method="post">
    <input name="_m" type="hidden" value="DELETE"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>

3 请求映射原理(p28)

DispatcherServlet类的继承关系

public class DispatcherServlet extends FrameworkServlet
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware
public abstract class HttpServlet extends GenericServlet
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable
image-20220912094540168
image-20220912094540168

HttpServletBean类没有重写doGet()doPost()

image-20220912095505408
image-20220912095505408

FrameworkServlet类重写了doGet()doPost()

image-20220912094239196
image-20220912094239196

FrameworkServlet类的doGetdoPostdoPutdoDelete都是调用processRequest(request, response);

image-20220912100415648

processRequest(HttpServletRequest request, HttpServletResponse response)方法的核心逻辑是调用的doService(request, response);方法,DispatcherServlet类对doService(request, response);做了实现

/**
 * Process this request, publishing an event regardless of the outcome.
 * <p>The actual event handling is performed by the abstract
 * {@link #doService} template method.
 */
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
   
   long startTime = System.currentTimeMillis();
   Throwable failureCause = null;
   //这些都是初始化过程
   LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
   LocaleContext localeContext = buildLocaleContext(request);

   RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
   ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
   asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

   initContextHolders(request, localeContext, requestAttributes);

   try {
      //核心方法(该核心方法在前面讲过)
      doService(request, response);
   }
   catch (ServletException | IOException ex) {
      failureCause = ex;
      throw ex;
   }
   catch (Throwable ex) {
      failureCause = ex;
      throw new NestedServletException("Request processing failed", ex);
   }

   finally {
      resetContextHolders(request, previousLocaleContext, previousAttributes);
      if (requestAttributes != null) {
         requestAttributes.requestCompleted();
      }
      logResult(request, response, failureCause, asyncManager);
      publishRequestHandledEvent(request, response, startTime, failureCause);
   }
}

DispatcherServlet类的doService(request, response);具体实现代码:(其核心逻辑是调用doDispatch(request, response);)

/**
 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
 * for the actual dispatching.
 */
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
   logRequest(request);
   //初始化过程
   // Keep a snapshot of the request attributes in case of an include,
   // to be able to restore the original attributes after the include.
   Map<String, Object> attributesSnapshot = null;
   if (WebUtils.isIncludeRequest(request)) {
      attributesSnapshot = new HashMap<>();
      Enumeration<?> attrNames = request.getAttributeNames();
      while (attrNames.hasMoreElements()) {
         String attrName = (String) attrNames.nextElement();
         if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
            attributesSnapshot.put(attrName, request.getAttribute(attrName));
         }
      }
   }
   //初始化过程
   // Make framework objects available to handlers and view objects.
   request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
   request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
   request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
   request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

   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);
   }

   try {
      //核心方法
      doDispatch(request, response);
   }
   finally {
      if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
         // Restore the original attribute snapshot, in case of an include.
         if (attributesSnapshot != null) {
            restoreAttributesAfterInclude(request, attributesSnapshot);
         }
      }
   }
}
核心

在这里插入图片描述 SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet -> doDispatch()

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    //Handler的执行链
    HandlerExecutionChain mappedHandler = null;
    //是不是文件上传
    boolean multipartRequestParsed = false;
	//是否使用异步,如果是异步使用异步管理器
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            //检查是不是文件上传
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);
            // 决定使用哪个Handler(Controller的方法)来处理当前请求
            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);

            //HandlerMapping:处理器映射。/xxx->>xxxx
    ...
}
处理器映射核心

HandlerExecutionChain

getHandler(processedRequest)方法可以精准的知道GET方法的/user请求要交给com.atguigu.boot.controller.UserController#getUser()来处理,因此getHandler(processedRequest)就是处理器映射的核心

image-20220912110016545
image-20220912110016545

getHandler()方法如下:该方法会遍历所有的HandlerMapping

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}
image-20220912120241972
image-20220912120241972

this.handlerMappings会保存所有的处理器映射(此时有5个处理器映射)

image-20220912110654998 其中RequestMappingHandlerMapping类保存了所有@RequestMappinghandler的映射规则,这些映射规则保存在该类的handlerMappings->mappingRegistry里。(这些映射规则是在ioc容器初始化时加载的)

image-20220912111900244
image-20220912111900244

mapping.getHandler(request);是调用org.springframework.web.servlet.handler.AbstractHandlerMapping类的getHandler方法

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
		implements HandlerMapping, Ordered, BeanNameAware {
	@Override
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		...
	}
}
image-20220912112930517
image-20220912112930517

其又调用了getHandlerInternal(request);方法,其为org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping类的getHandlerInternal(HttpServletRequest request) throws Exception方法

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
   //移除HandlerMapping.producibleMediaTypes
   request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
   try {
      return super.getHandlerInternal(request);
   }
   finally {
      ProducesRequestCondition.clearMediaTypesAttribute(request);
   }
}
image-20220912113524816
image-20220912113524816

其又调用父类的getHandlerInternal(request),即为org.springframework.web.servlet.handler.AbstractHandlerMethodMapping类的getHandlerInternal(HttpServletRequest request)

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
   // 获取请求的路径 /user
   String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
   request.setAttribute(LOOKUP_PATH, lookupPath);
   //获取一把读锁
   this.mappingRegistry.acquireReadLock();
   try {
      HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
      return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
   }
   finally {
      this.mappingRegistry.releaseReadLock();
   }
}
image-20220912114117305
image-20220912114117305

其又调用lookupHandlerMethod(lookupPath, request),其为

先根据/user找,此时找到4个,然后调用addMatchingMappings(directPathMatches, matches, request);方法,把这4个中最佳匹配放到类型为List<Match>matches里。由于此时就只剩下一个了。

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
   List<Match> matches = new ArrayList<>();
   //获取请求路径匹配的集合 (请求方式可以不匹配)
   List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
   if (directPathMatches != null) {
      //将 请求方式 和 请求路径 都匹配的放入matches里 
      addMatchingMappings(directPathMatches, matches, request);
   }
   if (matches.isEmpty()) {
      // No choice but to go through all mappings...
      addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
   }

   if (!matches.isEmpty()) {
      Match bestMatch = matches.get(0);
      //如果最佳匹配的数量大于一(例如:GET-/**、GET-/user都可以匹配)
      if (matches.size() > 1) {
         //对最佳匹配的结果进行排序 (0:GET-/user, 1:GET-/**)
         Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
         matches.sort(comparator);
         bestMatch = matches.get(0);
         if (logger.isTraceEnabled()) {
            logger.trace(matches.size() + " matching mappings: " + matches);
         }
         if (CorsUtils.isPreFlightRequest(request)) {
            return PREFLIGHT_AMBIGUOUS_MATCH;
         }
         Match secondBestMatch = matches.get(1);
         //最佳匹配和次最佳匹配对比结果一样(例如都是 GET-/user,此时就会抛出IllegalStateException异常)
         if (comparator.compare(bestMatch, secondBestMatch) == 0) {
            Method m1 = bestMatch.handlerMethod.getMethod();
            Method m2 = secondBestMatch.handlerMethod.getMethod();
            String uri = request.getRequestURI();
            throw new IllegalStateException(
                  "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
         }
      }
      request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
      handleMatch(bestMatch.mapping, lookupPath, request);
      return bestMatch.handlerMethod;
   }
   else {
      return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
   }
}
image-20220912114811540
image-20220912114811540

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#addMatchingMappings方法又会调用getMatchingMapping(mapping, request);方法

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
   for (T mapping : mappings) {
      T match = getMatchingMapping(mapping, request);
      if (match != null) {
         matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
      }
   }
}
image-20220912142323180
image-20220912142323180

getMatchingMapping(mapping, request);方法即为org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#getMatchingMapping,该方法会匹配请求方式和请求路径

@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
   return info.getMatchingCondition(request);
}
image-20220912121313912
image-20220912121313912
@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
   RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
   if (methods == null) {
      return null;
   }
   ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
   if (params == null) {
      return null;
   }
   HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
   if (headers == null) {
      return null;
   }
   ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
   if (consumes == null) {
      return null;
   }
   ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
   if (produces == null) {
      return null;
   }
   PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
   if (patterns == null) {
      return null;
   }
   RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
   if (custom == null) {
      return null;
   }

   return new RequestMappingInfo(this.name, patterns,
         methods, params, headers, consumes, produces, custom.getCondition());
}
image-20220915193314280
image-20220915193314280
@Override
@Nullable
public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) {
   if (CorsUtils.isPreFlightRequest(request)) {
      return matchPreFlight(request);
   }

   if (getMethods().isEmpty()) {
      if (RequestMethod.OPTIONS.name().equals(request.getMethod()) &&
            !DispatcherType.ERROR.equals(request.getDispatcherType())) {

         return null; // We handle OPTIONS transparently, so don't match if no explicit declarations
      }
      return this;
   }

   return matchRequestMethod(request.getMethod());
}
image-20220915193523283
image-20220915193523283

在这里判断的请求方式

@Nullable
private RequestMethodsRequestCondition matchRequestMethod(String httpMethodValue) {
   RequestMethod requestMethod;
   try {
      requestMethod = RequestMethod.valueOf(httpMethodValue);
      if (getMethods().contains(requestMethod)) {
         return requestMethodConditionCache.get(httpMethodValue);
      }
      if (requestMethod.equals(RequestMethod.HEAD) && getMethods().contains(RequestMethod.GET)) {
         return requestMethodConditionCache.get(HttpMethod.GET.name());
      }
   }
   catch (IllegalArgumentException ex) {
      // Custom request method
   }
   return null;
}
image-20220915193802817
image-20220915193802817

所有的请求映射都在HandlerMapping中:

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;

  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping

  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。

    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping


IDEA快捷键:

  • Ctrl + Alt + U : 以UML的类图展现类有哪些继承类,派生类以及实现哪些接口。
  • Crtl + Alt + Shift + U : 同上,区别在于上条快捷键结果在新页展现,而本条快捷键结果在弹窗展现。
  • Ctrl + H : 以树形方式展现类层次结构图。

4 普通参数与基本注解

1.1、注解:

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody

1.2、Servlet API:

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

1.3、复杂参数: Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

1.4、自定义对象参数:

可以自动类型转换与格式化,可以级联封装。

1.5、完整index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>atguigu,欢迎您</h1>
测试REST风格;
<form action="/user" method="get">
    <input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="delete"/>
    <input name="_m" type="hidden" value="delete"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT"/>
    <input value="REST-PUT 提交" type="submit"/>
</form>
<hr/>
测试基本注解:
<ul>
    <a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a>
    <li>@PathVariable(路径变量)</li>
    <li>@RequestHeader(获取请求头)</li>
    <li>@RequestParam(获取请求参数)</li>
    <li>@CookieValue(获取cookie值)</li>
    <li>@RequestBody(获取请求体[POST])</li>

    <li>@RequestAttribute(获取request域属性)</li>
    <li>@MatrixVariable(矩阵变量)</li>
</ul>

/cars/{path}?xxx=xxx&aaa=ccc queryString 查询字符串。@RequestParam;<br/>
/cars/sell;low=34;brand=byd,audi,yd  ;矩阵变量 <br/>
页面开发,cookie禁用了,session里面的内容怎么使用;
session.set(a,b)---> jsessionid ---> cookie ----> 每次发请求携带。
url重写:/abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递.

/boss/1/2

/boss/1;age=20/2;age=20

<a href="/cars/sell;low=34;brand=byd,audi,yd">@MatrixVariable(矩阵变量)</a>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariable(矩阵变量)</a>
<a href="/boss/1;age=20/2;age=10">@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}</a>
<br/>
<form action="/save" method="post">
    测试@RequestBody获取数据 <br/>
    用户名:<input name="userName"/> <br>
    邮箱:<input name="email"/>
    <input type="submit" value="提交"/>
</form>
<ol>
    <li>矩阵变量需要在SpringBoot中手动开启</li>
    <li>根据RFC3986的规范,矩阵变量应当绑定在路径变量中!</li>
    <li>若是有多个矩阵变量,应当使用英文符号;进行分隔。</li>
    <li>若是一个矩阵变量有多个值,应当使用英文符号,进行分隔,或之命名多个重复的key即可。</li>
    <li>如:/cars/sell;low=34;brand=byd,audi,yd</li>
</ol>
<hr/>
测试原生API:
<a href="/testapi">测试原生API</a>
<hr/>
测试复杂类型:<hr/>
测试封装POJO;
<form action="/saveuser" method="post">
    姓名: <input name="userName" value="zhangsan"/> <br/>
    年龄: <input name="age" value="18"/> <br/>
    生日: <input name="birth" value="2019/12/10"/> <br/>
<!--    宠物姓名:<input name="pet.name" value="阿猫"/><br/>-->
<!--    宠物年龄:<input name="pet.age" value="5"/>-->
    宠物: <input name="pet" value="啊猫,3"/>
    <input type="submit" value="保存"/>
</form>

<br>
</body>
</html>

5. 常用参数注解使用(29)

注解:

  • @PathVariable 路径变量
  • @RequestHeader 获取请求头
  • @RequestParam 获取请求参数(指问号后的参数,url?a=1&b=2)
  • @CookieValue 获取Cookie值
  • @RequestAttribute 获取request域属性
  • @RequestBody 获取请求体[POST]
  • @MatrixVariable 矩阵变量
  • @ModelAttribute

使用用例:

@RestController
public class ParameterTestController {


    //  car/2/owner/zhangsan
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     //获取所有的路径变量,将其封装到k,v中
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     //获取所有请求头信息
                                     @RequestHeader Map<String,String> header,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     //获取所有请求参数信息
                                     @RequestParam Map<String,String> params,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie){

        Map<String,Object> map = new HashMap<>();

//        map.put("id",id);
//        map.put("name",name);
//        map.put("pv",pv);
//        map.put("userAgent",userAgent);
//        map.put("headers",header);
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        map.put("_ga",_ga);
        System.out.println(cookie.getName()+"===>"+cookie.getValue());
        return map;
    }


    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }
}
@RequestAttribute

用例:

@Controller
public class RequestController {

    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){

        request.setAttribute("msg","成功了...");
        request.setAttribute("code",200);
        return "forward:/success";  //转发到  /success请求
    }

    @GetMapping("/params")
    public String testParam(Map<String,Object> map,
                            Model model,
                            HttpServletRequest request,
                            HttpServletResponse response){
        map.put("hello","world666");
        model.addAttribute("world","hello666");
        //Object message = request.getAttribute("message");
        request.setAttribute("message","HelloWorld");
        
		//final Cookie[] cookies = request.getCookies();
        Cookie cookie = new Cookie("c1","v1");
        response.addCookie(cookie);
        return "forward:/success";
    }

    ///<-----------------主角@RequestAttribute在这个方法
    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute(value = "msg",required = false) String msg,
                       @RequestAttribute(value = "code",required = false)Integer code,
                       HttpServletRequest request){
        Object msg1 = request.getAttribute("msg");

        Map<String,Object> map = new HashMap<>();
        Object hello = request.getAttribute("hello");
        Object world = request.getAttribute("world");
        Object message = request.getAttribute("message");

        map.put("reqMethod_msg",msg1);
        map.put("annotation_msg",msg);
        map.put("hello",hello);
        map.put("world",world);
        map.put("message",message);

        return map;
    }
}
@MatrixVariable与UrlPathHelper

cars/{path}xxx=xxx&aaa=ccc querystrig 查询字符串。@RequestParam:获取请求参数的形式去获取

/cars/{path;low=34;brand=byd,udi,yd} (寻找汽车中大于34万,品牌在byd,udi,yd中的)使用 ;表示使用矩阵变量形式获取

  1. 语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd

  2. SpringBoot默认是禁用了矩阵变量的功能

    • 手动开启:原理。对于路径的处理。UrlPathHelperremoveSemicolonContent设置为false,让其支持矩阵变量的。
  3. 矩阵变量必须有url路径变量才能被解析

页面开发中, cookie禁用了, session里面的内容怎么使用?

  • 通常情况下的流程: session.set(a,b) —> jsessionid —> cookie —>每次发请求携带
  • ur1重写: /abc; jsesssionid=xxxxcookie的值使用矩阵变量的方式进行传递

手动开启矩阵变量

  • 实现WebMvcConfigurer接口:
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {

        UrlPathHelper urlPathHelper = new UrlPathHelper();
        // 不移除;(分号)后面的内容。矩阵变量功能就可以生效
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}
  • 创建返回WebMvcConfigurerBean:
@Configuration(proxyBeanMethods = false)
public class WebConfig{
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
                        @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;(分号)后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        }
    }
}

@MatrixVariable的用例

@RestController
public class ParameterTestController {

    // /cars/sell;low=34;brand=byd,audi,yd
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand,
                        @PathVariable("path") String path){
        Map<String,Object> map = new HashMap<>();

        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);
        return map;
    }

    // /boss/1;age=20/2;age=10
    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
        Map<String,Object> map = new HashMap<>();

        map.put("bossAge",bossAge);
        map.put("empAge",empAge);
        return map;

    }

}

6. 各种类型参数解析原理

  • HandlerMapping中找到能处理请求的Handler(Controller.method())。
  • 为当前Handler 找一个适配器 HandlerAdapter,用的最多的是RequestMappingHandlerAdapter
  • 适配器执行目标方法并确定方法参数的每一个值。

还是要从DispatcherServletdoDispatch开始说起:

public class DispatcherServlet extends FrameworkServlet {
    
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
				//决定哪个handler处理当前请求 (通过url判断执行哪个方法)
                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
				//决定哪个handler的适配器处理当前请求 (`执行目标方法`并`填装该方法的参数数据`)
                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                ...
image-20220912204658716
image-20220912204658716
HandlerAdapter
package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.lang.Nullable;

public interface HandlerAdapter {
   //支持处理哪种handler
   boolean supports(Object handler);
   //如果支持,就调用真正的方法来处理
   @Nullable
   ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

   long getLastModified(HttpServletRequest request, Object handler);

}
image-20220912205258358
image-20220912205258358

默认会加载所有HandlerAdapter

public class DispatcherServlet extends FrameworkServlet {

    /** Detect all HandlerAdapters or just expect "handlerAdapter" bean?. */
    private boolean detectAllHandlerAdapters = true;
    ...
    private void initHandlerAdapters(ApplicationContext context) {
        this.handlerAdapters = null;

        if (this.detectAllHandlerAdapters) {
            // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
            Map<String, HandlerAdapter> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerAdapters = new ArrayList<>(matchingBeans.values());
                // We keep HandlerAdapters in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerAdapters);
            }
        }
     ...

getHandlerAdapter(mappedHandler.getHandler());方法调用的是org.springframework.web.servlet.DispatcherServletgetHandlerAdapter(Object handler)方法

此时传过来的handlerHandlerMethod类型,有这些HandlerAdapter

  1. RequestMappingHandlerAdapter:支持方法上标注@RequestMapping

  2. HandlerFunctionAdapter:支持函数式编程的

  3. HttpRequestHandlerAdapter ...

  4. SimpleControllerHandlerAdapter...

image-20220912210358237
image-20220912210358237

遍历第一个适配器RequestMappingHandlerAdapter,执行adapter.supports(handler),调用org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter类的supports(Object handler)方法,handler由于是HandlerMethod类型,所有第一个适配器RequestMappingHandlerAdapter就匹配

@Override
public final boolean supports(Object handler) {
   return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
image-20220912210622298
image-20220912210622298
执行目标方法

执行到ha.handle(processedRequest, response, mappedHandler.getHandler());

public class DispatcherServlet extends FrameworkServlet {
    ...
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		...
        // 决定哪个handler处理当前请求 (通过url判断执行哪个方法)
        // Determine handler for the current request.
        mappedHandler = getHandler(processedRequest);
        ...
        //决定哪个handler的适配器处理当前请求 (`执行目标方法`并`填装该方法的参数数据`)
		// Determine handler adapter for the current request.
		HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

		// Process last-modified header, if supported by the handler.
		String method = request.getMethod();
		boolean isGet = "GET".equals(method);
	    //HEAD不是服务器真正处理的,相当于一个打头兵,如果有浏览器缓存,还进行缓存的
		if (isGet || "HEAD".equals(method)) {
		   long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
		   if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
		      return;
		   }
		}

		if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		   return;
		}
		//真正执行handler (传入 request,response,目标方法)
		// Actually invoke the handler.
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

		if (asyncManager.isConcurrentHandlingStarted()) {
		   return;
		}
		...
	}
}
image-20220913092537541
image-20220913092537541

调用的ha.handle(processedRequest, response, mappedHandler.getHandler());其为org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter类的handle(HttpServletRequest request, HttpServletResponse response, Object handler)方法

@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {

   return handleInternal(request, response, (HandlerMethod) handler);
}
image-20220912212536936
image-20220912212536936

执行目标方法

调用的handleInternal(request, response, (HandlerMethod) handler);方法为org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter类的handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod)方法

 */
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
      implements BeanFactoryAware, InitializingBean {
    ...
	@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// No synchronization on session demanded at all...
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}
		...
	}
}
image-20220913093001221
image-20220913093001221
参数解析器

确定将要执行的目标方法的每一个参数的值是什么;

SpringMVC目标方法能写多少种参数类型。取决于参数解析器argumentResolvers

SpringMVC目标方法能写多少种返回值。取决于返回值处理器returnValueHandlers

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                       HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
		//目标方法
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        //参数解析器 (取决于可以传哪些参数)
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        //返回值处理器 (取决于可以返回哪些返回值)
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        ...
        //执行并处理
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}
image-20220913101033148
image-20220913101033148

此时有26个参数解析器

  1. RequestParamMethodArgumentResolver -> @RequestParam("age") Integer age@RequestParam("inters") List<String> inters
  2. RequestParamMapMethodArgumentResolver -> @RequestParam Map<String, String> params
  3. PathVariableMethodArgumentResolver -> @PathVariable("id") Integer id
  4. PathVariableMapMethodArgumentResolver -> @PathVariable Map<String, String> pv
  5. MatrixVariableMethodArgumentResolver -> @MatrixVariable("low") Integer low@MatrixVariable("brand") List<String> brand (请求为/cars/sell;low=34;brand=byd,audi,yd)、@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge(请求为/boss/1;age=20/2;age=10,请求映射为@GetMapping("/boss/{bossId}/{empId}"))
  6. MatrixVariableMapMethodArgumentResolver -> @MatrixVariable Map<String, String> matrix
image-20220913101140652

15个返回值处理器

image-20220913101243571
image-20220913101243571

HandlerMethodArgumentResolver接口有两个方法

public interface HandlerMethodArgumentResolver {
   //这个MethodParameter是否支持解析
   boolean supportsParameter(MethodParameter parameter);
   //如果支持就调用该方法进行解析
   @Nullable
   Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
         NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
image-20220913100408971
image-20220913100408971

26个参数解析器和15个返回值处理器都封装到目标方法的ServletInvocableHandlerMethod类里

image-20220913101806200
image-20220913101806200
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                       HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
		//目标方法
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        //参数解析器 (取决于可以传哪些参数)
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        //返回值处理器 (取决于可以返回哪些返回值)
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        ...
        //执行并处理
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}
image-20220913102259818
image-20220913102259818

invocableMethod.invokeAndHandle(webRequest, mavContainer);方法,其为org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod类的invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs)方法

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
	//如果给controller上的对应方法打上断点,并将这一行放行,会执行handler (自己写的controller方法)
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);
    ...
}
image-20220913102703792
image-20220913102703792

invokeForRequest(webRequest, mavContainer, providedArgs)即为org.springframework.web.method.support.InvocableHandlerMethod类的invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs)方法

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
   //如果放行这一行,该方法的所有参数都会封装成功
   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
   if (logger.isTraceEnabled()) {
      logger.trace("Arguments: " + Arrays.toString(args));
   }
   //执行目标方法
   return doInvoke(args);
}
image-20220913103522000
image-20220913103522000
确定方法参数的值

getMethodArgumentValues(request, mavContainer, providedArgs)调用本类的该方法

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
   //获取方法参数的声明 (所有参数标注的注解、参数索引、参数类型等)
   MethodParameter[] parameters = getMethodParameters();
   if (ObjectUtils.isEmpty(parameters)) {
      return EMPTY_ARGS;
   }

   Object[] args = new Object[parameters.length];
   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) {
         continue;
      }
      //当前解析器是否支持该参数
      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 ex) {
         // Leave stack trace for later, exception may actually be resolved and handled...
         if (logger.isDebugEnabled()) {
            String exMsg = ex.getMessage();
            if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
               logger.debug(formatArgumentError(parameter, exMsg));
            }
         }
         throw ex;
      }
   }
   return args;
}
image-20220913104224230
image-20220913104224230

getMethodParameters()方法会获取方法参数的声明

image-20220913104837302
image-20220913104837302
判断谁可以解析参数
image-20220913113802382
image-20220913113802382

this.resolvers.supportsParameter(parameter)方法会解析是否支持该参数,其为org.springframework.web.method.support.HandlerMethodArgumentResolverComposite类的supportsParameter(MethodParameter parameter)方法

@Override
public boolean supportsParameter(MethodParameter parameter) {
   return getArgumentResolver(parameter) != null;
}
image-20220913105806539
image-20220913105806539

getArgumentResolver(parameter)方法为本类的getArgumentResolver(MethodParameter parameter)方法

其会遍历所有的参数解析器,判断能不能解析(我感觉这双重for循环效率有点低呀)

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
   HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
   if (result == null) {
      for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
         if (resolver.supportsParameter(parameter)) {
            result = resolver;
            this.argumentResolverCache.put(parameter, result);
            break;
         }
      }
   }
   return result;
}
image-20220913110517805
image-20220913110517805

调用if (resolver.supportsParameter(parameter)),首先判断RequestParamMethodArgumentResolver能不能处理

@Override
public boolean supportsParameter(MethodParameter parameter) {
   //是否标了@RequestPart注解
   if (parameter.hasParameterAnnotation(RequestParam.class)) {
       //如果标了@RequestPart注解,看参数是不是
       if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
         RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
         return (requestParam != null && StringUtils.hasText(requestParam.name()));
      }
      else {
         return true;
      }
   }
   else {
      if (parameter.hasParameterAnnotation(RequestPart.class)) {
         return false;
      }
      parameter = parameter.nestedIfOptional();
      if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
         return true;
      }
      else if (this.useDefaultResolution) {
         return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
      }
      else {
         return false;
      }
   }
}
image-20220913111644781
image-20220913111644781

找到后会将parameterresult放入缓存,因此下次查询就很快

image-20220913113219928
image-20220913113219928
解析这个参数
image-20220913113704684
image-20220913113704684

this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);会调用org.springframework.web.method.support.HandlerMethodArgumentResolverComposite类的resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)方法

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   //拿到这个能处理这个参数的HandlerMethodArgumentResolver参数解析器
   HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
   if (resolver == null) {
      throw new IllegalArgumentException("Unsupported parameter type [" +
            parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
   }
   return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
image-20220913114327346
image-20220913114327346
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

   NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
   MethodParameter nestedParameter = parameter.nestedIfOptional();
   //拿到参数的名字进行解析
   Object resolvedName = resolveStringValue(namedValueInfo.name);
   if (resolvedName == null) {
      throw new IllegalArgumentException(
            "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
   }

   Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
   ...
}
image-20220913114716973
image-20220913114716973
@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   Map<String, MultiValueMap<String, String>> pathParameters = (Map<String, MultiValueMap<String, String>>)
         request.getAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
   if (CollectionUtils.isEmpty(pathParameters)) {
      return null;
   }

   MatrixVariable ann = parameter.getParameterAnnotation(MatrixVariable.class);
   Assert.state(ann != null, "No MatrixVariable annotation");
   String pathVar = ann.pathVar();
   List<String> paramValues = null;

   if (!pathVar.equals(ValueConstants.DEFAULT_NONE)) {
      if (pathParameters.containsKey(pathVar)) {
         paramValues = pathParameters.get(pathVar).get(name);
      }
   }
   else {
      boolean found = false;
      paramValues = new ArrayList<>();
      for (MultiValueMap<String, String> params : pathParameters.values()) {
         if (params.containsKey(name)) {
            if (found) {
               String paramType = parameter.getNestedParameterType().getName();
               throw new ServletRequestBindingException(
                     "Found more than one match for URI path parameter '" + name +
                     "' for parameter type [" + paramType + "]. Use 'pathVar' attribute to disambiguate.");
            }
            paramValues.addAll(params.get(name));
            found = true;
         }
      }
   }

   if (CollectionUtils.isEmpty(paramValues)) {
      return null;
   }
   else if (paramValues.size() == 1) {
      return paramValues.get(0);
   }
   else {
      return paramValues;
   }
}
image-20220913140232316
image-20220913140232316

这里解析出的url参数,是通过前面的UrlPathHelper类的decodeMatrixVariables方法解析的,解析成功会放到请求域中。

public MultiValueMap<String, String> decodeMatrixVariables(
      HttpServletRequest request, MultiValueMap<String, String> vars) {

   if (this.urlDecode) {
      return vars;
   }
   else {
      MultiValueMap<String, String> decodedVars = new LinkedMultiValueMap<>(vars.size());
      vars.forEach((key, values) -> {
         for (String value : values) {
            decodedVars.add(key, decodeInternal(request, value));
         }
      });
      return decodedVars;
   }
}
image-20220913140737411
image-20220913140737411
image-20220913142808966
image-20220913142808966

此时已解析到值

image-20220913150001179
image-20220913150001179
换一个访问

禁用所有断点,访问localhost:8080/car/2/owner/zhangsan?age=18&inters=打篮球&inters=打游戏&inters=woman,给该域名添加一个名为_gaCookies(双击显示Cookie区域的地方即可添加),再次访问

image-20220913144733297
image-20220913144733297

1、@PathVariable

判断@PathVariable("id") Integer id能否解析,只需使用了@PathVariable并且不是Map就行

@Override
public boolean supportsParameter(MethodParameter parameter) {
   if (!parameter.hasParameterAnnotation(PathVariable.class)) {
      return false;
   }
   if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
      PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
      return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
   }
   return true;
}
image-20220913152519632
image-20220913152519632

@PathVariable("id") Integer id就比@MatrixVariable("low") Integer low好获取的多,直接去经过UrlPathHelper类的decodeMatrixVariables方法解析后的请求域中拿就行了,不需要额外的处理。

@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
         HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
   return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}
image-20220913150318243
image-20220913150318243
image-20220913145515287
image-20220913145515287

2、@RequestHeader

@RequestHeader("User-Agent") String userAgent判断哪个参数解析器能够解析就比较简单,有@RequestHeader注解并且类型不是Map就行

@Override
public boolean supportsParameter(MethodParameter parameter) {
   return (parameter.hasParameterAnnotation(RequestHeader.class) &&
         !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
}
image-20220913151319321
image-20220913151319321

获取到值也非常简单,直接request.getHeaderValues(name);就行了

@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   String[] headerValues = request.getHeaderValues(name);
   if (headerValues != null) {
      return (headerValues.length == 1 ? headerValues[0] : headerValues);
   }
   else {
      return null;
   }
}
image-20220913151609672
image-20220913151609672
image-20220913152026189
image-20220913152026189

3 、@RequestParam

判断@RequestParam("age") Integer age能否解析,使用了@RequestParam注解并且不为Map类型就可以

RequestParamMethodArgumentResolver除了能处理@RequestParam注解外,还可以处理@RequestPart注解open in new window

@RequestParam和@RequestPart的区别是:

@RequestParam适用于name-valueString类型的请求域

@RequestPart适用于复杂的请求域(像JSON,XML)

当处理@RequestParam注解时,需在某些条件成立的情况下才会使用此类进行解析:

① 方法参数由@RequestParam注解注释。

② 方法参数若是Map类型时,@RequestParam的name属性不能为空。

③ 方法参数若不是Map类型时,都可以处理。

当处理@RequestPart注解(文件上传)时,需在某些条件成立的情况下才会使用此类进行解析:

① 方法参数不可由@RequestPart注解注释。

② 方法参数类型为org.springframework.web.multipart.MultipartFile、org.springframework.web.multipart.MultipartFile集合、org.springframework.web.multipart.MultipartFile数组、javax.servlet.http.Part、javax.servlet.http.Part集合或javax.servlet.http.Part数组。

③ 一个简单类型的方法参数,包括:boolean、byte、char、short、int、long、float、double、Enum.class、CharSequence.class、Number.class、Date.class、URI.class、URL.class、Locale.class或Class.class。

@Override
public boolean supportsParameter(MethodParameter parameter) {
   if (parameter.hasParameterAnnotation(RequestParam.class)) {
      if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
         RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
         return (requestParam != null && StringUtils.hasText(requestParam.name()));
      }
      else {
         return true;
      }
   }
   else {
      if (parameter.hasParameterAnnotation(RequestPart.class)) {
         return false;
      }
      parameter = parameter.nestedIfOptional();
      if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
         return true;
      }
      else if (this.useDefaultResolution) {
         return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
      }
      else {
         return false;
      }
   }
}
image-20220913153043284
image-20220913153043284

有点不能理解为什么先判断是不是文件上传,再判断是不是普通请求参数,命名普通的请求参数更常用

@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

   if (servletRequest != null) {
      Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
      if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
         return mpArg;
      }
   }

   Object arg = null;
   MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
   if (multipartRequest != null) {
      List<MultipartFile> files = multipartRequest.getFiles(name);
      if (!files.isEmpty()) {
         arg = (files.size() == 1 ? files.get(0) : files);
      }
   }
   if (arg == null) {
      String[] paramValues = request.getParameterValues(name);
      if (paramValues != null) {
         arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
      }
   }
   return arg;
}
image-20220913160611721
image-20220913160611721
image-20220913161358999
image-20220913161358999

4、@CookieValue

@CookieValue("_ga") String _ga的参数解析器应该是最简单的,只要标注了@CookieValue就行了,其他什么都不用判断

@Override
public boolean supportsParameter(MethodParameter parameter) {
   return parameter.hasParameterAnnotation(CookieValue.class);
}
image-20220913162121321
image-20220913162121321
@Override
@Nullable
protected Object resolveName(String cookieName, MethodParameter parameter,
      NativeWebRequest webRequest) throws Exception {

   HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
   Assert.state(servletRequest != null, "No HttpServletRequest");

   Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
   if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
      return cookieValue;
   }
   else if (cookieValue != null) {
      return this.urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
   }
   else {
      return null;
   }
}
image-20220913162526292
image-20220913162526292

其调用了WebUtils.getCookie(servletRequest, cookieName),这个方法比较简单,遍历所有Cookie,找到指定名字的Cookie

@Nullable
public static Cookie getCookie(HttpServletRequest request, String name) {
   Assert.notNull(request, "Request must not be null");
   Cookie[] cookies = request.getCookies();
   if (cookies != null) {
      for (Cookie cookie : cookies) {
         if (name.equals(cookie.getName())) {
            return cookie;
         }
      }
   }
   return null;
}
image-20220913162634328
image-20220913162634328
image-20220913162947700
image-20220913162947700

5、@RequestBody

基础知识

@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);GET方式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST方式进行提交。

在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。

注意:

1、一个请求,只有一个RequestBody;一个请求,可以有多个RequestParam。

2、当同时使用@RequestParam()和@RequestBody时,@RequestParam()指定的参数可以是普通元素、数组、集合、对象等等(即:当,@RequestBody 与@RequestParam()可以同时使用时。

3、原SpringMVC接收参数的机制不变,只不过RequestBody 接收的是请求体里面的数据;而RequestParam接收的是key-value里面的参数,所以它会被切面进行处理从而可以用普通元素、数组、集合、对象等接收)。

4、如果参数时放在请求体中,application/json传入后台的话,那么后台要用@RequestBody才能接收到;如果不是放在请求体中的话,那么后台接收前台传过来的参数时,要用@RequestParam来接收,或则形参前 什么也不写也能接收。

5、如果参数前写了@RequestParam(xxx),那么前端必须有对应的xxx名字才行(不管其是否有值,当然可以通过设置该注解的required属性来调节是否必须传),如果没有xxx名的话,那么请求会出错,报400。

6、如果参数前不写@RequestParam(xxx)的话,那么就前端可以有可以没有对应的xxx名字才行,如果有xxx名的话,那么就会自动匹配;没有的话,请求也能正确发送。

7、这里与feign消费服务时不同;feign消费服务时,如果参数前什么也不写,那么会被默认是@RequestBody的。

8、如果后端参数是一个对象,且该参数前是以@RequestBody修饰的,那么前端传递json参数时,必须满足以下要求:

(1)后端@RequestBody注解对应的类在将HTTP的输入流(含请求体)装配到目标类(即:@RequestBody后面的类)时,会根据json字符串中的key来匹配对应实体类的属性,

​ 如果匹配一致且json中的该key对应的值符合(或可转换为),实体类的对应属性的类型要求时,会调用实体类的setter方法将值赋给该属性。

(2)json字符串中,如果value为""的话,后端对应属性如果是String类型的,那么接受到的就是"",如果是后端属性的类型是Integer、Double等类型,那么接收到的就是null。

(3)json字符串中,如果value为null的话,后端对应收到的就是null。

(4)如果某个参数没有value的话,在传json字符串给后端时,要么干脆就不把该字段写到json字符串中;要么写value时, 必须有值,null 或""都行。

@PostMapping("/save")
public Map postMethod(@RequestBody User user) {
    Map<String, Object> map = new HashMap<>();
    map.put("userName", user.getUserName());
    map.put("email", user.getEmail());
    return map;
}

@Data
public static class User {
    private String userName;
    private String email;
}
POST http://localhost:8080/save
Content-Type: application/json

{
    "userName":"张三",
    "email":"[email protected]"
}
image-20220913192155233
image-20220913192155233

或使用IDEA自带的HTTP工具

image-20220913192655120
image-20220913192655120

@RequestBody就比较简单,直接判断有没有@RequestBody注解就行了

@Override
public boolean supportsParameter(MethodParameter parameter) {
   return parameter.hasParameterAnnotation(RequestBody.class);
}
image-20220913163454799
image-20220913163454799

readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());获取的是json数据,Conventions.getVariableNameForParameter(parameter);获取的是目标参数的参数名

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

   parameter = parameter.nestedIfOptional();
   Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
   String name = Conventions.getVariableNameForParameter(parameter);

   if (binderFactory != null) {
      WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
      if (arg != null) {
         validateIfApplicable(binder, parameter);
         if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
            throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
         }
      }
      if (mavContainer != null) {
         mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
      }
   }

   return adaptArgumentIfNecessary(arg, parameter);
}
image-20220913201436971
image-20220913201436971

readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());调用了readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,Type paramType)方法,此方法把ServletWebRequest类转换为ServletServerHttpRequest然后再调用AbstractMessageConverterMethodArgumentResolver类相同方法名,不同参数类型的方法

protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
      Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

   HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
   Assert.state(servletRequest != null, "No HttpServletRequest");
   ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);

   Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
   if (arg == null && checkRequired(parameter)) {
      throw new HttpMessageNotReadableException("Required request body is missing: " +
            parameter.getExecutable().toGenericString(), inputMessage);
   }
   return arg;
}
image-20220913201837511
image-20220913201837511
@SuppressWarnings("unchecked")
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
      Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

   MediaType contentType;
   boolean noContentType = false;
   try {
      // application/json;charset=UTF-8
      contentType = inputMessage.getHeaders().getContentType();
   }
   catch (InvalidMediaTypeException ex) {
      throw new HttpMediaTypeNotSupportedException(ex.getMessage());
   }
   if (contentType == null) {
      noContentType = true;
      contentType = MediaType.APPLICATION_OCTET_STREAM;
   }
   //上下文类(要被调用方法的类)
   // class com.atguigu.boot.controller.ParameterTestController
   Class<?> contextClass = parameter.getContainingClass();
   // 目标类(要封装的类)
   // class com.atguigu.boot.controller.ParameterTestController$User 
   Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
   if (targetClass == null) {
      ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
      targetClass = (Class<T>) resolvableType.resolve();
   }
   // POST
   HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
   // Object body = new Object()
   Object body = NO_VALUE;

   EmptyBodyCheckingHttpInputMessage message;
   try {
      message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
	  // 遍历 messageConverters 消息转换器
      for (HttpMessageConverter<?> converter : this.messageConverters) {
         Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
         GenericHttpMessageConverter<?> genericConverter =
               (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
         // 支持 application/json 和 application/*+json
         if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
               (targetClass != null && converter.canRead(targetClass, contentType))) {
             // 只有MappingJackson2HttpMessageConverter能够满足条件(因为传的数据是json类型)
             if (message.hasBody()) {
               // advice 包含 requestBodyAdvice 和 responseBodyAdvice
               // 读请求体前
               HttpInputMessage msgToUse =
                     getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
               // genericConverter.read(User,ParameterTestController, AbstractMessageConverterMethodArgumentResolver$EmptyBodyCheckingHttpInputMessage)
               body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) 
                     :((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
               // 读请求体后
               body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
            }
            else {
               body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
            }
            break;
         }
      }
   }
   catch (IOException ex) {
      throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
   }

   if (body == NO_VALUE) {
      if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
            (noContentType && !message.hasBody())) {
         return null;
      }
      throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
   }
   // application/json;charset=UTF-8
   MediaType selectedContentType = contentType;
   Object theBody = body;
   LogFormatUtils.traceDebug(logger, traceOn -> {
      String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
      return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
   });

   return body;
}

消息转换器

image-20220913212816341
image-20220913212816341

读请求体前

image-20220913214703141
image-20220913214703141
@Override
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
      throws IOException, HttpMessageNotReadableException {
   //  (User,ParameterTestController)
   JavaType javaType = getJavaType(type, contextClass);
   return readJavaType(javaType, inputMessage);
}
image-20220914145001048
image-20220914145001048
image-20220914145526264
image-20220914145526264
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
   // application/json;charset=UTF-8
   MediaType contentType = inputMessage.getHeaders().getContentType();
   // UTF-8
   Charset charset = getCharset(contentType);

   boolean isUnicode = ENCODINGS.containsKey(charset.name());
   try {
      if (inputMessage instanceof MappingJacksonInputMessage) {
         Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
         if (deserializationView != null) {
            ObjectReader objectReader = this.objectMapper.readerWithView(deserializationView).forType(javaType);
            if (isUnicode) {
               return objectReader.readValue(inputMessage.getBody());
            }
            else {
               Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
               return objectReader.readValue(reader);
            }
         }
      }
      if (isUnicode) {
         // 调用jackson的readValue方法,把json数据转为User
         return this.objectMapper.readValue(inputMessage.getBody(), javaType);
      }
      else {
         Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
         return this.objectMapper.readValue(reader, javaType);
      }
   }
   catch (InvalidDefinitionException ex) {
      throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
   }
   catch (JsonProcessingException ex) {
      throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
   }
}
image-20220914145736661
image-20220914145736661
返回值处理器

ValueHandler

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                                           HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {//<---关注点
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
     ...

this.returnValueHandlersafterPropertiesSet()方法内初始化

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
	
	@Nullable
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
    
	@Override
	public void afterPropertiesSet() {

        ...
        
		if (this.returnValueHandlers == null) {
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}
    
    //初始化了一堆的实现HandlerMethodReturnValueHandler接口的
    private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
		List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);

		// Single-purpose return value types
		handlers.add(new ModelAndViewMethodReturnValueHandler());
		handlers.add(new ModelMethodProcessor());
		handlers.add(new ViewMethodReturnValueHandler());
		handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
				this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
		handlers.add(new StreamingResponseBodyReturnValueHandler());
		handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
				this.contentNegotiationManager, this.requestResponseBodyAdvice));
		handlers.add(new HttpHeadersReturnValueHandler());
		handlers.add(new CallableMethodReturnValueHandler());
		handlers.add(new DeferredResultMethodReturnValueHandler());
		handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

		// Annotation-based return value types
		handlers.add(new ServletModelAttributeMethodProcessor(false));
		handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
				this.contentNegotiationManager, this.requestResponseBodyAdvice));

		// Multi-purpose return value types
		handlers.add(new ViewNameMethodReturnValueHandler());
		handlers.add(new MapMethodProcessor());

		// Custom return value types
		if (getCustomReturnValueHandlers() != null) {
			handlers.addAll(getCustomReturnValueHandlers());
		}

		// Catch-all
		if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
			handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
		}
		else {
			handlers.add(new ServletModelAttributeMethodProcessor(true));
		}

		return handlers;
	}
}

HandlerMethodReturnValueHandlerComposite类如下:

public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {

	private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();

    ...
    
	public HandlerMethodReturnValueHandlerComposite addHandlers(
			@Nullable List<? extends HandlerMethodReturnValueHandler> handlers) {

		if (handlers != null) {
			this.returnValueHandlers.addAll(handlers);
		}
		return this;
	}

}

HandlerMethodReturnValueHandler接口:

public interface HandlerMethodReturnValueHandler {

	boolean supportsReturnType(MethodParameter returnType);

	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}
回顾执行目标方法
public class DispatcherServlet extends FrameworkServlet {
    ...
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mv = null;
		...
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

RequestMappingHandlerAdapterhandle()方法:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {

    ...
    
    //AbstractHandlerMethodAdapter类的方法,RequestMappingHandlerAdapter继承AbstractHandlerMethodAdapter
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

        return handleInternal(request, response, (HandlerMethod) handler);
    }

	@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    	ModelAndView mav;
        //handleInternal的核心
        mav = invokeHandlerMethod(request, response, handlerMethod);//解释看下节
		//...
		return mav;
    }
}

RequestMappingHandlerAdapterinvokeHandlerMethod()方法:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
    
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			...
            
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			...

            //关注点:执行目标方法
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}

invokeAndHandle()方法如下:

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {

	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

        ...
        
		try {
            //returnValue存储起来
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			...
		}
	}
    
    @Nullable//InvocableHandlerMethod类的,ServletInvocableHandlerMethod类继承InvocableHandlerMethod类
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

        ////获取方法的参数值
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

        ...
       
		return doInvoke(args);
	}

    @Nullable
	protected Object doInvoke(Object... args) throws Exception {
		Method method = getBridgedMethod();//@RequestMapping的方法
		ReflectionUtils.makeAccessible(method);
		try {
			if (KotlinDetector.isSuspendingFunction(method)) {
				return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
			}
            //通过反射调用
			return method.invoke(getBean(), args);//getBean()指@RequestMapping的方法所在类的对象。
		}
		catch (IllegalArgumentException ex) {
			...
		}
		catch (InvocationTargetException ex) {
			...
		}
	}
    
}   
如何确定目标方法每一个参数的值

重点分析ServletInvocableHandlerMethodgetMethodArgumentValues方法

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
    ...

	@Nullable//InvocableHandlerMethod类的,ServletInvocableHandlerMethod类继承InvocableHandlerMethod类
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

        ////获取方法的参数值
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

        ...
       
		return doInvoke(args);
	}
 
    //本节重点,获取方法的参数值
	protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

		Object[] args = new Object[parameters.length];
		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) {
				continue;
			}
            //查看resolvers是否有支持
			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 ex) {
				....
			}
		}
		return args;
	}
    
}

this.resolvers的类型为HandlerMethodArgumentResolverComposite(在参数解析器章节提及)

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
    
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return getArgumentResolver(parameter) != null;
	}

	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
		if (resolver == null) {
			throw new IllegalArgumentException("Unsupported parameter type [" +
					parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
		}
		return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
	}
    
    
    @Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
            //挨个判断所有参数解析器那个支持解析这个参数
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
					this.argumentResolverCache.put(parameter, result);//找到了,resolver就缓存起来,方便稍后resolveArgument()方法使用
					break;
				}
			}
		}
		return result;
	}
}
小结

本节描述,一个请求发送到DispatcherServlet后的具体处理流程,也就是SpringMVC的主要原理。

本节内容较多且硬核,对日后编程很有帮助,需耐心对待。

可以运行一个示例,打断点,在Debug模式下,查看程序流程。

7、Servlet API参数解析原理

  • WebRequest
  • ServletRequest
  • MultipartRequest
  • HttpSession
  • javax.servlet.http.PushBuilder
  • Principal
  • InputStream
  • Reader
  • HttpMethod
  • Locale
  • TimeZone
  • ZoneId

用例

@GetMapping("/goto")
public String goToPage(HttpServletRequest request){

    request.setAttribute("msg","成功了...");
    request.setAttribute("code",200);
    return "forward:/success";  //转发到  /success请求
}

@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute(value = "msg",required = false) String msg,
                   @RequestAttribute(value = "code",required = false)Integer code,
                   HttpServletRequest request){
    Map<String,Object> map = new HashMap<>();
    
    Object msg1 = request.getAttribute("msg");
    map.put("reqMethod_msg",msg1);
    map.put("annotation_msg",msg);
    return map;
}

是否支持该处理器代码

interface javax.servlet.http.HttpServletRequest属于ServletRequest并且是Reader,所以ServletRequestMethodArgumentResolver能够处理

@Override
public boolean supportsParameter(MethodParameter parameter) {
   Class<?> paramType = parameter.getParameterType();
   return (WebRequest.class.isAssignableFrom(paramType) ||
         ServletRequest.class.isAssignableFrom(paramType) ||
         MultipartRequest.class.isAssignableFrom(paramType) ||
         HttpSession.class.isAssignableFrom(paramType) ||
         (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
         Principal.class.isAssignableFrom(paramType) ||
         InputStream.class.isAssignableFrom(paramType) ||
         Reader.class.isAssignableFrom(paramType) ||
         HttpMethod.class == paramType ||
         Locale.class == paramType ||
         TimeZone.class == paramType ||
         ZoneId.class == paramType);
}
image-20220914153655385
image-20220914153655385
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

   Class<?> paramType = parameter.getParameterType();

   // WebRequest / NativeWebRequest / ServletWebRequest
   if (WebRequest.class.isAssignableFrom(paramType)) {
      if (!paramType.isInstance(webRequest)) {
         throw new IllegalStateException(
               "Current request is not of type [" + paramType.getName() + "]: " + webRequest);
      }
      return webRequest;
   }

   // ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
   if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
      return resolveNativeRequest(webRequest, paramType);
   }

   // HttpServletRequest required for all further argument types
   return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
}
image-20220914160548289
image-20220914160548289

拿到原生的request请求,直接返回就行了

private <T> T resolveNativeRequest(NativeWebRequest webRequest, Class<T> requiredType) {
   T nativeRequest = webRequest.getNativeRequest(requiredType);
   if (nativeRequest == null) {
      throw new IllegalStateException(
            "Current request is not of type [" + requiredType.getName() + "]: " + webRequest);
   }
   return nativeRequest;
}
image-20220914161735056
image-20220914161735056

ServletRequestMethodArgumentResolver用来处理以上的参数

8、Model、Map原理

复杂参数:

  • Map

  • Model(map、model里面的数据会被放在request的请求域 request.setAttribute)

  • Errors/BindingResult

  • RedirectAttributes( 重定向携带数据)

  • ServletResponse(response)

  • SessionStatus

  • UriComponentsBuilder

  • ServletUriComponentsBuilder

用例:

@GetMapping("/params")
public String testParam(Map<String,Object> map,Model model,
                        HttpServletRequest request,HttpServletResponse response){
    map.put("hello","world666");
    model.addAttribute("world","hello666");
    request.setAttribute("message","HelloWorld");
    Cookie cookie = new Cookie("c1","v1");
    response.addCookie(cookie);
    return "forward:/success";
}

@ResponseBody
@GetMapping("/success")
public Map<String,Object> success(HttpServletRequest request){
    Map<String,Object> map = new HashMap<>();

    Object hello = request.getAttribute("hello");
    Object world = request.getAttribute("world");
    Object message = request.getAttribute("message");
    final Cookie[] cookies = request.getCookies();

    map.put("hello",hello);
    map.put("world",world);
    map.put("message",message);
    map.put("cookies",cookies);

    return map;
}

发送请求后的返回数据 (要访问两次,让浏览器保存完cookie后才能获取到cookie)

http://localhost:8080/params
{
    "world":"hello666",
    "hello":"world666",
    "message":"HelloWorld",
    "cookies":[{"name":"c1","value":"v1","version":0,"comment":null,"domain":null,"maxAge":-1,"path":null,"secure":false,"httpOnly":false}]
}
image-20220914164402354
image-20220914164402354
  • Map<String,Object> map

  • Model model

  • HttpServletRequest request

上面三个都是可以给request域中放数据,用request.getAttribute()获取

接下来我们看看,Map<String,Object> mapModel model用什么参数处理器。


Map<String,Object> map参数用MapMethodProcessor处理:

public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
				parameter.getParameterAnnotations().length == 0);
	}

	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
		return mavContainer.getModel();
	}
    
    ...
    
}
image-20220914192608620
image-20220914192608620

mavContainer.getModel()如下:

public class ModelAndViewContainer {
    ...
	private final ModelMap defaultModel = new BindingAwareModelMap();
	@Nullable
	private ModelMap redirectModel;
    ...
	public ModelMap getModel() {
		if (useDefaultModel()) {
			return this.defaultModel;
		}
		else {
			if (this.redirectModel == null) {
				this.redirectModel = new ModelMap();
			}
			return this.redirectModel;
		}
	}
    
    private boolean useDefaultModel() {
		return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
	}
    ...
    
}
image-20220914192333685
image-20220914192333685

Model modelModelMethodProcessor处理:

public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return Model.class.isAssignableFrom(parameter.getParameterType());
	}

	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
        // ModelAndViewContainer
		return mavContainer.getModel();
	}
    ...
}
image-20220914184934608
image-20220914184934608

return mavContainer.getModel();这跟MapMethodProcessor的一致

image-20220914193632207
image-20220914193632207

Model也是另一种意义的Map,处理请求的方法传递参数为MapModel使用的都是同一个对象。(其为BindingAwareModelMap类型)

image-20220914194341782
image-20220914194341782

org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues方法执行完,参数就都封装成功了。

image-20220914195318046
image-20220914195318046

执行doInvoke(args)就会来到我们写的controller

image-20220914195504852
image-20220914195504852
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
   // 执行目标方法
   Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
   // 处理返回结果
   // 设置响应状态
   setResponseStatus(webRequest);

   if (returnValue == null) {
      if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
         disableContentCachingIfNecessary(webRequest);
         mavContainer.setRequestHandled(true);
         return;
      }
   }
   else if (StringUtils.hasText(getResponseStatusReason())) {
      mavContainer.setRequestHandled(true);
      return;
   }

   mavContainer.setRequestHandled(false);
   Assert.state(this.returnValueHandlers != null, "No return value handlers");
   try {
      // 映射返回结果值
      this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
   }
   catch (Exception ex) {
      if (logger.isTraceEnabled()) {
         logger.trace(formatErrorForReturnValue(returnValue), ex);
      }
      throw ex;
   }
}
image-20220914201852009
image-20220914201852009

接下来看看Map<String,Object> mapModel model值是如何做到用request.getAttribute()获取的。

public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

	@Nullable
	private String[] redirectPatterns;
    ...
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		Class<?> paramType = returnType.getParameterType();
		return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
	}

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
		// 如果返回值是字符串
		if (returnValue instanceof CharSequence) {
			String viewName = returnValue.toString();
            // 设置视图名为该返回值
			mavContainer.setViewName(viewName);
			if (isRedirectViewName(viewName)) {
				mavContainer.setRedirectModelScenario(true);
			}
		}
		else if (returnValue != null) {
			// should not happen
			throw new UnsupportedOperationException("Unexpected return type: " +
					returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
		}
	}
    ...
}
image-20220914203807223
image-20220914203807223

mavContainer.setViewName(viewName);后,模型和视图都有了

image-20220914204734044
image-20220914204734044

然后把ModelAndViewContainer转换为ModelAndView

@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
      ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

   modelFactory.updateModel(webRequest, mavContainer);
   if (mavContainer.isRequestHandled()) {
      return null;
   }
   ModelMap model = mavContainer.getModel();
   // new ModelAndView( forward:/success, {"hello":"world666","world":"hello666"} , null )
   ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
   if (!mavContainer.isViewReference()) {
      mav.setView((View) mavContainer.getView());
   }
   // 如果是重定向携带数据
   if (model instanceof RedirectAttributes) {
      Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
      HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
      if (request != null) {
         RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
      }
   }
   return mav;
}
image-20220914205744775
image-20220914205744775

众所周知,所有的数据都放在 ModelAndView包含要去的页面地址View,还包含Model数据。

public class DispatcherServlet extends FrameworkServlet {
    ...
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		...
		try {
			ModelAndView mv = null;
			Exception dispatchException = null;
			try {
				...
				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				...
				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
            catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
            //处理最终的结果
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
            ...
        }
        ...
    }
	...
}
image-20220914210516384
image-20220914210516384

这里是执行一些拦截器

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
      throws Exception {

   HandlerInterceptor[] interceptors = getInterceptors();
   if (!ObjectUtils.isEmpty(interceptors)) {
      for (int i = interceptors.length - 1; i >= 0; i--) {
         HandlerInterceptor interceptor = interceptors[i];
         interceptor.postHandle(request, response, this.handler, mv);
      }
   }
}
image-20220914211022625
image-20220914211022625

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);会处理最终的结果

image-20220914211338458
image-20220914211338458
public class DispatcherServlet extends FrameworkServlet {
    ...
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false;

		if (exception != null) {
			...
		}
		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		...
	}
	...
}
image-20220914212048865
image-20220914212048865
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   // Determine locale for request and apply it to the response.
   // zh_CN
   Locale locale =
         (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
   response.setLocale(locale);

   View view;
   // forward:/success
   String viewName = mv.getViewName();
   if (viewName != null) {
      // We need to resolve the view name.
      view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
	  ...
   }
image-20220914212910381
image-20220914212910381
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
      Locale locale, HttpServletRequest request) throws Exception {

   if (this.viewResolvers != null) {
      for (ViewResolver viewResolver : this.viewResolvers) {
         View view = viewResolver.resolveViewName(viewName, locale);
         if (view != null) {
            return view;
         }
      }
   }
   return null;
}
image-20220914213501790
image-20220914213501790
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   // Determine locale for request and apply it to the response.
   Locale locale =
         (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
   response.setLocale(locale);

   View view;
   String viewName = mv.getViewName();
   if (viewName != null) {
      // We need to resolve the view name.
      view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
      if (view == null) {
         throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
               "' in servlet with name '" + getServletName() + "'");
      }
   }
   ...
   try {
      if (mv.getStatus() != null) {
         response.setStatus(mv.getStatus().value());
      }
      view.render(mv.getModelInternal(), request, response);
   }
   ...
}
image-20220914213858759
image-20220914213858759

核心

@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
      HttpServletResponse response) throws Exception {

   if (logger.isDebugEnabled()) {
      logger.debug("View " + formatViewName() +
            ", model " + (model != null ? model : Collections.emptyMap()) +
            (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
   }
   // 创建一个要合并的输出模型
   Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
   // 准备响应
   prepareResponse(request, response);
   // 渲染合并输出的模型
   renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
image-20220914214151965
image-20220914214151965
protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
      HttpServletRequest request, HttpServletResponse response) {

   @SuppressWarnings("unchecked")
   Map<String, Object> pathVars = (this.exposePathVariables ?
         (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

   // Consolidate static and dynamic model attributes.
   int size = this.staticAttributes.size();
   size += (model != null ? model.size() : 0);
   size += (pathVars != null ? pathVars.size() : 0);

   Map<String, Object> mergedModel = new LinkedHashMap<>(size);
   mergedModel.putAll(this.staticAttributes);
   if (pathVars != null) {
      mergedModel.putAll(pathVars);
   }
   if (model != null) {
      // 将 model里的数据放到
      mergedModel.putAll(model);
   }

   // Expose RequestContext?
   if (this.requestContextAttribute != null) {
      mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
   }

   return mergedModel;
}
image-20220914214610279
image-20220914214610279
image-20220914214827390
image-20220914214827390

核心

InternalResourceView就是视图解析流程了

@Override
protected void renderMergedOutputModel(
      Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

   // Expose the model object as request attributes.
   // 暴露Model作为请求域的属性
   exposeModelAsRequestAttributes(model, request);

   // Expose helpers as request attributes, if any.
   
   exposeHelpers(request);

   // Determine the path for the request dispatcher.
   String dispatcherPath = prepareForRendering(request, response);

   // Obtain a RequestDispatcher for the target resource (typically a JSP).
   RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
   if (rd == null) {
      throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
            "]: Check that the corresponding file exists within your web application archive!");
   }

   // If already included or response already committed, perform include, else forward.
   if (useInclude(request, response)) {
      response.setContentType(getContentType());
      if (logger.isDebugEnabled()) {
         logger.debug("Including [" + getUrl() + "]");
      }
      rd.include(request, response);
   }

   else {
      // Note: The forwarded resource is supposed to determine the content type itself.
      if (logger.isDebugEnabled()) {
         logger.debug("Forwarding to [" + getUrl() + "]");
      }
      rd.forward(request, response);
   }
}
image-20220915090918164
image-20220915090918164

遍历放到请求域中

protected void exposeModelAsRequestAttributes(Map<String, Object> model,
      HttpServletRequest request) throws Exception {

   model.forEach((name, value) -> {
      if (value != null) {
         request.setAttribute(name, value);
      }
      else {
         request.removeAttribute(name);
      }
   });
}
image-20220914215426404
image-20220914215426404

在Debug模式下,view属为InternalResourceView类。

exposeModelAsRequestAttributes方法看出,Map<String,Object> mapModel model这两种类型数据可以给request域中放数据,用request.getAttribute()获取。

9、自定义参数绑定原理

@RestController
public class ParameterTestController {

    /**
     * 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
     * @param person
     * @return
     */
    @PostMapping("/saveuser")
    public Person saveuser(Person person){
        return person;
    }
}
/**
 *     姓名: <input name="userName"/> <br/>
 *     年龄: <input name="age"/> <br/>
 *     生日: <input name="birth"/> <br/>
 *     宠物姓名:<input name="pet.name"/><br/>
 *     宠物年龄:<input name="pet.age"/>
 */
@Data
public class Person {
    
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
    
}

@Data
public class Pet {

    private String name;
    private String age;

}

6个和第25个的全包名一样,都是为org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor类,但第25个支持,第6个不支持

image-20220915204007139
image-20220915204007139

6个,跳转到的是org.springframework.web.method.annotation.ModelAttributeMethodProcessor类,这个@ModelAttribute注解是必须的

@Override
public boolean supportsParameter(MethodParameter parameter) {
   return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
         (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
image-20220915204342340
image-20220915204342340

25给跳转到的也是org.springframework.web.method.annotation.ModelAttributeMethodProcessor类,但是@ModelAttribute注解不是必须的

@Override
public boolean supportsParameter(MethodParameter parameter) {
   // this.annotationNotRequired=true 注解不是必须的
   return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
         (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
image-20220915202124556
image-20220915202124556
public static boolean isSimpleProperty(Class<?> type) {
   Assert.notNull(type, "'type' must not be null");
   //isSimpleValueType(type) 和 (type.isArray() 都为false,所以结果为false
   return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}
image-20220915202344001
image-20220915202344001

是否是简单类型 (自己写的pojo不是简单类型)

public static boolean isSimpleValueType(Class<?> type) {
   return (Void.class != type && void.class != type &&
         (ClassUtils.isPrimitiveOrWrapper(type) ||
         Enum.class.isAssignableFrom(type) ||
         CharSequence.class.isAssignableFrom(type) ||
         Number.class.isAssignableFrom(type) ||
         Date.class.isAssignableFrom(type) ||
         Temporal.class.isAssignableFrom(type) ||
         URI.class == type ||
         URL.class == type ||
         Locale.class == type ||
         Class.class == type));
}
image-20220915205649497
image-20220915205649497

由于BeanUtils.isSimpleProperty(parameter.getParameterType())false,所以!BeanUtils.isSimpleProperty(parameter.getParameterType())true,而this.annotationNotRequiredtrue,所以this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))true,所以表达式为true

@Override
public boolean supportsParameter(MethodParameter parameter) {
   return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
         (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
image-20220915203542149
image-20220915203542149
image-20220915205422979
image-20220915205422979

找到解析器后进行解析

image-20220915210322300
image-20220915210322300
image-20220915210432927
image-20220915210432927
image-20220915210538075
image-20220915210538075
@Override
protected final Object createAttribute(String attributeName, MethodParameter parameter,
      WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {

   String value = getRequestValueForAttribute(attributeName, request);
   if (value != null) {
      Object attribute = createAttributeFromRequestValue(
            value, attributeName, parameter, binderFactory, request);
      if (attribute != null) {
         return attribute;
      }
   }

   return super.createAttribute(attributeName, parameter, binderFactory, request);
}
image-20220915210843603
image-20220915210843603

构造空对象

protected Object createAttribute(String attributeName, MethodParameter parameter,
      WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {

   MethodParameter nestedParameter = parameter.nestedIfOptional();
   Class<?> clazz = nestedParameter.getNestedParameterType();

   Constructor<?> ctor = BeanUtils.findPrimaryConstructor(clazz);
   if (ctor == null) {
      Constructor<?>[] ctors = clazz.getConstructors();
      if (ctors.length == 1) {
         ctor = ctors[0];
      }
      else {
         try {
            ctor = clazz.getDeclaredConstructor();
         }
         catch (NoSuchMethodException ex) {
            throw new IllegalStateException("No primary or default constructor found for " + clazz, ex);
         }
      }
   }

   Object attribute = constructAttribute(ctor, attributeName, parameter, binderFactory, webRequest);
   if (parameter != nestedParameter) {
      attribute = Optional.of(attribute);
   }
   return attribute;
}
image-20220915211143344
image-20220915211143344

核心

public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
   ...
   @Override
   @Nullable
   public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	  ...
      if (mavContainer.containsAttribute(name)) {
         attribute = mavContainer.getModel().get(name);
      }
      else {
         // Create attribute instance
         try {
            attribute = createAttribute(name, parameter, binderFactory, webRequest);
         }
         catch (BindException ex) {
		 ...
         }
      }

      if (bindingResult == null) {
         // Web数据绑定器(将请求参数的值放到指定的javaBean)
         // Bean property binding and validation;
         // skipped in case of binding failure on construction.
         WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
         if (binder.getTarget() != null) {
             if (!mavContainer.isBindingDisabled(name)) {
                 bindRequestParameters(binder, webRequest);
             }
             validateIfApplicable(binder, parameter);
             if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                 throw new BindException(binder.getBindingResult());
             }
         }
      return attribute;
   }
   ...
}
image-20220915211652679
image-20220915211652679

这个binder里不仅有目标对象,在conversionService(转换服务)里有124converters(转换器)。由于HTTP超文本传输协议(Hyper Text Transfer Protocol)因此,数据传输都是超文本,因此需要将String转换为各种类型的转换器

image-20220915212719087
image-20220915212719087

bindRequestParameters(binder, webRequest);放行,数据就转换成功了,因此该方法就是将数据封装到

image-20220915215551218
image-20220915215551218
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
   // 拿到原生的请求
   ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
   Assert.state(servletRequest != null, "No ServletRequest");
   ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
   servletBinder.bind(servletRequest);
}
image-20220916110149283
image-20220916110149283
public void bind(ServletRequest request) {
   // 拿到所有键值对
   MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
   MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
   if (multipartRequest != null) {
      bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
   }
   addBindValues(mpvs, request);
   doBind(mpvs);
}
image-20220916110626835
image-20220916110626835
public ServletRequestParameterPropertyValues(ServletRequest request) {
   this(request, null, null);
}

public ServletRequestParameterPropertyValues(
      ServletRequest request, @Nullable String prefix, @Nullable String prefixSeparator) {

   super(WebUtils.getParametersStartingWith(
         request, (prefix != null ? prefix + prefixSeparator : null)));
}
image-20220916111401681
image-20220916111401681

new ServletRequestParameterPropertyValues(request);方法实际调用的方法,该方法遍历所有参数

public void bind(ServletRequest request) {
   MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
   MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
   if (multipartRequest != null) {
      bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
   }
   addBindValues(mpvs, request);
   doBind(mpvs);
}
image-20220916111112538
image-20220916111112538
image-20220916110516441
@Override
protected void doBind(MutablePropertyValues mpvs) {
   checkFieldDefaults(mpvs);
   checkFieldMarkers(mpvs);
   super.doBind(mpvs);
}
image-20220916111831290
image-20220916111831290
protected void doBind(MutablePropertyValues mpvs) {
   checkAllowedFields(mpvs);
   checkRequiredFields(mpvs);
   applyPropertyValues(mpvs);
}
image-20220916112052218
image-20220916112052218
protected void applyPropertyValues(MutablePropertyValues mpvs) {
   try {
      // Bind request parameters onto target object.
      getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
   }
   catch (PropertyBatchUpdateException ex) {
      // Use bind error processor to create FieldErrors.
      for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
         getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
      }
   }
}
image-20220916112156564
image-20220916112156564
@Override
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
      throws BeansException {

   List<PropertyAccessException> propertyAccessExceptions = null;
   List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
         ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
   for (PropertyValue pv : propertyValues) {
      try {
         // This method may throw any BeansException, which won't be caught
         // here, if there is a critical failure such as no matching field.
         // We can attempt to deal only with less serious exceptions.
         setPropertyValue(pv);
      }
      ...
   }
   ...
}
image-20220916112340179
image-20220916112340179
@Override
public void setPropertyValue(PropertyValue pv) throws BeansException {
   PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
   if (tokens == null) {
      String propertyName = pv.getName();
      AbstractNestablePropertyAccessor nestedPa;
      try {
         // 获取属性路径的属性访问器 (拿到有要封装对象的wrapper)
         nestedPa = getPropertyAccessorForPropertyPath(propertyName);
      }
      catch (NotReadablePropertyException ex) {
         throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
               "Nested property in path '" + propertyName + "' does not exist", ex);
      }
      tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
      if (nestedPa == this) {
         pv.getOriginalPropertyValue().resolvedTokens = tokens;
      }
      // 通过反射,对wrapper设置属性
      nestedPa.setPropertyValue(tokens, pv);
   }
   else {
      setPropertyValue(tokens, pv);
   }
}
image-20220916112810415
image-20220916112810415

getPropertyAccessorForPropertyPath(propertyName);方法会返回BeanWrapperImpl

@SuppressWarnings("unchecked")  // avoid nested generic
protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
   int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
   // Handle nested properties recursively.
   if (pos > -1) {
      String nestedProperty = propertyPath.substring(0, pos);
      String nestedPath = propertyPath.substring(pos + 1);
      AbstractNestablePropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty);
      return nestedPa.getPropertyAccessorForPropertyPath(nestedPath);
   }
   else {
      return this;
   }
}
image-20220916185200644
image-20220916185200644
protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
   if (tokens.keys != null) {
      processKeyedProperty(tokens, pv);
   }
   else {
      processLocalProperty(tokens, pv);
   }
}
image-20220916113239010
image-20220916113239010
private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
   PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);

   try {
      Object originalValue = pv.getValue();
      Object valueToApply = originalValue;
      if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
         if (pv.isConverted()) {
            valueToApply = pv.getConvertedValue();
         }
         else {
			...
            }
            valueToApply = convertForProperty(
                  tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
         }
         pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
      }
      ph.setValue(valueToApply);
   }
   ...
}
image-20220916113815328
image-20220916113815328
@Nullable
protected Object convertForProperty(
      String propertyName, @Nullable Object oldValue, @Nullable Object newValue, TypeDescriptor td)
      throws TypeMismatchException {

   return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
}
image-20220916114311334
image-20220916114311334
@Nullable
private Object convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue,
      @Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable TypeDescriptor td)
      throws TypeMismatchException {

   Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
   try {
      return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
   }
   catch (ConverterNotFoundException | IllegalStateException ex) {
      PropertyChangeEvent pce =
            new PropertyChangeEvent(getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
      throw new ConversionNotSupportedException(pce, requiredType, ex);
   }
   catch (ConversionException | IllegalArgumentException ex) {
      PropertyChangeEvent pce =
            new PropertyChangeEvent(getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
      throw new TypeMismatchException(pce, requiredType, ex);
   }
}
image-20220916114348698
image-20220916114348698
@SuppressWarnings("unchecked")
@Nullable
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
      @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {

   // Custom editor for this type?
   PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

   ConversionFailedException conversionAttemptEx = null;

   // No custom editor but custom ConversionService specified?
   ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
   if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
      TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
      if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
         try {
            return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
         }
         catch (ConversionFailedException ex) {
            // fallback to default conversion logic below
            conversionAttemptEx = ex;
         }
      }
   }
   ...
}
image-20220916143624640
image-20220916143624640
@Override
public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
   Assert.notNull(targetType, "Target type to convert to cannot be null");
   if (sourceType == null) {
      return true;
   }
   GenericConverter converter = getConverter(sourceType, targetType);
   return (converter != null);
}
image-20220916115043602
image-20220916115043602
@Nullable
protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
   ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
   GenericConverter converter = this.converterCache.get(key);
   if (converter != null) {
      return (converter != NO_MATCH ? converter : null);
   }

   converter = this.converters.find(sourceType, targetType);
   if (converter == null) {
      converter = getDefaultConverter(sourceType, targetType);
   }

   if (converter != null) {
      this.converterCache.put(key, converter);
      return converter;
   }

   this.converterCache.put(key, NO_MATCH);
   return null;
}
image-20220916135910765
image-20220916135910765
@Nullable
public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
   // Search the full type hierarchy
   List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
   List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
   for (Class<?> sourceCandidate : sourceCandidates) {
      for (Class<?> targetCandidate : targetCandidates) {
         ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
         // 遍历 converter 查看哪个converter能够处理
         GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair);
         if (converter != null) {
            return converter;
         }
      }
   }
   return null;
}
image-20220916140545059
image-20220916140545059
image-20220916141454420
image-20220916141454420
@Override
@Nullable
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
   Assert.notNull(targetType, "Target type to convert to cannot be null");
   if (sourceType == null) {
      Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
      return handleResult(null, targetType, convertNullSource(null, targetType));
   }
   if (source != null && !sourceType.getObjectType().isInstance(source)) {
      throw new IllegalArgumentException("Source to convert from must be an instance of [" +
            sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
   }
   //这里的`getConverter(sourceType, targetType);`就是`conversionService.canConvert(sourceTypeDesc, typeDescriptor)`,获取哪个转换器能够处理的那个方法
   GenericConverter converter = getConverter(sourceType, targetType);
   if (converter != null) {
      Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
      return handleResult(sourceType, targetType, result);
   }
   return handleConverterNotFound(source, sourceType, targetType);
}
image-20220916144846741
image-20220916144846741
@Nullable
public static Object invokeConverter(GenericConverter converter, @Nullable Object source,
      TypeDescriptor sourceType, TypeDescriptor targetType) {

   try {
      return converter.convert(source, sourceType, targetType);
   }
   catch (ConversionFailedException ex) {
      throw ex;
   }
   catch (Throwable ex) {
      throw new ConversionFailedException(sourceType, targetType, source, ex);
   }
}
image-20220916145046420
image-20220916145046420
@Override
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
   if (source == null) {
      return convertNullSource(sourceType, targetType);
   }
   return this.converterFactory.getConverter(targetType.getObjectType()).convert(source);
}
image-20220916155433428
image-20220916155433428

这个类就比较简单

final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {

   @Override
   public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
      return new StringToNumber<>(targetType);
   }


   private static final class StringToNumber<T extends Number> implements Converter<String, T> {

      private final Class<T> targetType;

      public StringToNumber(Class<T> targetType) {
         this.targetType = targetType;
      }

      @Override
      public T convert(String source) {
         if (source.isEmpty()) {
            return null;
         }
         return NumberUtils.parseNumber(source, this.targetType);
      }
   }

}
image-20220916162717536
image-20220916162717536
image-20220916163247666
image-20220916163247666
image-20220916164300034
image-20220916164300034

valueToApply的值转为Integer后,才设置为age的值

image-20220916164044329
image-20220916164044329

重点

查看pet.age如何封装

image-20220916175458997
image-20220916175458997

生成com.atguigu.boot.entry.Person$Pet对象

image-20220916175501206
image-20220916175501206
@SuppressWarnings("unchecked")  // avoid nested generic
protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
   // 获取"."的索引
   int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
   // Handle nested properties recursively.
   if (pos > -1) {
      // pet
      String nestedProperty = propertyPath.substring(0, pos);
      // age
      String nestedPath = propertyPath.substring(pos + 1);
      AbstractNestablePropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty);
      return nestedPa.getPropertyAccessorForPropertyPath(nestedPath);
   }
   else {
      return this;
   }
}
image-20220916180215029
image-20220916180215029
private AbstractNestablePropertyAccessor getNestedPropertyAccessor(String nestedProperty) {
   if (this.nestedPropertyAccessors == null) {
      this.nestedPropertyAccessors = new HashMap<>();
   }
   // Get value of bean property.
   PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
   String canonicalName = tokens.canonicalName;
   Object value = getPropertyValue(tokens);
   if (value == null || (value instanceof Optional && !((Optional<?>) value).isPresent())) {
      if (isAutoGrowNestedPaths()) {
         value = setDefaultValue(tokens);
      }
      else {
         throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
      }
   }

   // Lookup cached sub-PropertyAccessor, create new one if not found.
   AbstractNestablePropertyAccessor nestedPa = this.nestedPropertyAccessors.get(canonicalName);
   if (nestedPa == null || nestedPa.getWrappedInstance() != ObjectUtils.unwrapOptional(value)) {
      if (logger.isTraceEnabled()) {
         logger.trace("Creating new nested " + getClass().getSimpleName() + " for property '" + canonicalName + "'");
      }
      nestedPa = newNestedPropertyAccessor(value, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR);
      // Inherit all type-specific PropertyEditors.
      copyDefaultEditorsTo(nestedPa);
      copyCustomEditorsTo(nestedPa, canonicalName);
      this.nestedPropertyAccessors.put(canonicalName, nestedPa);
   }
   else {
      if (logger.isTraceEnabled()) {
         logger.trace("Using cached nested property accessor for property '" + canonicalName + "'");
      }
   }
   return nestedPa;
}
image-20220916180845711
image-20220916180845711

createDefaultPropertyValue(tokens)可以根据类名生成对应的对象

private Object setDefaultValue(PropertyTokenHolder tokens) {
   PropertyValue pv = createDefaultPropertyValue(tokens);
   setPropertyValue(tokens, pv);
   Object defaultValue = getPropertyValue(tokens);
   Assert.state(defaultValue != null, "Default value must not be null");
   return defaultValue;
}
image-20220916181141952
image-20220916181141952

getPropertyTypeDescriptor(tokens.canonicalName)可以根据类名生成对应的对象

private PropertyValue createDefaultPropertyValue(PropertyTokenHolder tokens) {
   TypeDescriptor desc = getPropertyTypeDescriptor(tokens.canonicalName);
   if (desc == null) {
      throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + tokens.canonicalName,
            "Could not determine property type for auto-growing a default value");
   }
   Object defaultValue = newValue(desc.getType(), desc, tokens.canonicalName);
   return new PropertyValue(tokens.canonicalName, defaultValue);
}
image-20220916181531213
image-20220916181531213

getPropertyAccessorForPropertyPath(propertyName)可以获取com.atguigu.boot.entry.Person

nestedPa.getLocalPropertyHandler(tokens.actualName)可以获取com.atguigu.boot.entry.Person$Pet

@Override
@Nullable
public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException {
   try {
      AbstractNestablePropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName);
      String finalPath = getFinalPath(nestedPa, propertyName);
      PropertyTokenHolder tokens = getPropertyNameTokens(finalPath);
      PropertyHandler ph = nestedPa.getLocalPropertyHandler(tokens.actualName);
      if (ph != null) {
         if (tokens.keys != null) {
            if (ph.isReadable() || ph.isWritable()) {
               return ph.nested(tokens.keys.length);
            }
         }
         else {
            if (ph.isReadable() || ph.isWritable()) {
               return ph.toTypeDescriptor();
            }
         }
      }
   }
   catch (InvalidPropertyException ex) {
      // Consider as not determinable.
   }
   return null;
}
image-20220916182009932
image-20220916182009932

getPropertyAccessorForPropertyPath(propertyName)就是前面封装agesetPropertyValue(pv);方法调用的nestedPa = getPropertyAccessorForPropertyPath(propertyName);方法,如果不存在.[]就返回当前这个BeanWrapperImpl

image-20220916182701823
image-20220916182701823
@Override
@Nullable
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
   PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
   return (pd != null ? new BeanPropertyHandler(pd) : null);
}
image-20220916183032229
image-20220916183032229

所以BeanWrapperImpl里面其实封装的都有

private CachedIntrospectionResults getCachedIntrospectionResults() {
   if (this.cachedIntrospectionResults == null) {
      this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
   }
   return this.cachedIntrospectionResults;
}
image-20220916184231409
image-20220916184231409

WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

在过程当中,用到GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型

10、自定义Converter原理

未来我们可以给WebDataBinder里面放自己的Converter;

测试封装POJO;
<form action="/saveuser" method="post">
    姓名: <input name="userName" value="zhangsan"/> <br/>
    年龄: <input name="age" value="18"/> <br/>
    生日: <input name="birth" value="2019/12/10"/> <br/>
    <!--    宠物姓名:<input name="pet.name" value="阿猫"/><br/>-->
    <!--    宠物年龄:<input name="pet.age" value="5"/>-->
    宠物: <input name="pet" value="啊猫,3"/>
    <input type="submit" value="保存"/>
</form>

下面演示将字符串“啊猫,3”转换成Pet对象。

根绝提示要想定制转换器,需要重写addFormatters(FormatterRegistry registry)方法

/**
 * Add {@link Converter Converters} and {@link Formatter Formatters} in addition to the ones
 * registered by default.
 */
default void addFormatters(FormatterRegistry registry) {
}
image-20220916190938119
image-20220916190938119

registry对象有一个addConverter(Converter<?, ?> converter)方法,可以添加一个转换器。其中S为原类型,T为目标类型

@FunctionalInterface
public interface Converter<S, T> {

   /**
    * Convert the source object of type {@code S} to target type {@code T}.
    * @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
    * @return the converted object, which must be an instance of {@code T} (potentially {@code null})
    * @throws IllegalArgumentException if the source cannot be converted to the desired target type
    */
   @Nullable
   T convert(S source);

}
image-20220916191932977
image-20220916191932977
    //1、WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {

                    @Override
                    public Pet convert(String source) {
                        // 啊猫,3
                        if(!StringUtils.isEmpty(source)){
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }

启动后就可以看到转换服务的转换器有125个,第125就是我们刚刚新增的StringPet的转换器

image-20220916193810668
image-20220916193810668

11、ReturnValueHandler原理

在这里插入图片描述
在这里插入图片描述

假设给前端自动返回json数据,需要引入相关的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- web场景自动引入了json场景 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.3.4.RELEASE</version>
    <scope>compile</scope>
</dependency>
<!-- json场景引入jackson -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jdk8</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.module</groupId>
  <artifactId>jackson-module-parameter-names</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>

控制层代码如下:

@Controller
public class ResponseTestController {
    
	@ResponseBody  //利用返回值处理器里面的消息转换器进行处理
    @GetMapping(value = "/test/person")
    public Person getPerson(){
        Person person = new Person();
        person.setAge(28);
        person.setBirth(new Date());
        person.setUserName("zhangsan");
        return person;
    }

}

返回值处理器有讨论ReturnValueHandler

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   ...
   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // Determine handler for the current request.
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // Determine handler adapter for the current request.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
		 ...
         // Actually invoke the handler.
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
      ...
      }
   ...
}
image-20220916195817854
image-20220916195817854

现在直接看看重点,和之前类似,这里有很多返回处理器

然后进入invocableMethod.invokeAndHandle(webRequest, mavContainer);

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {

    ...
    
	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			
            ...
            
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
                
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {//返回值处理器
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}

            ...
			// 进入到这里面
			invocableMethod.invokeAndHandle(webRequest, mavContainer);//看下块代码
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}
image-20220916200222267
image-20220916200222267

HandlerMethodReturnValueHandlerHandlerMethodArgumentResolver类似,也是有两个方法,如果支持就调用处理方法

public interface HandlerMethodReturnValueHandler {
   //这个MethodParameter是否支持该返回类型 (和HandlerMethodArgumentResolver类的supportsParameter方法类似)
   boolean supportsReturnType(MethodParameter returnType);
   //如果支持该返回类型就调用该方法 (和HandlerMethodArgumentResolver类的resolveArgument方法类似)
   void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
         ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}
image-20220916201724793
image-20220916201724793
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
    
	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
        //执行目标方法,获得返回值
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        setResponseStatus(webRequest);
        ...
		try {
            //处理返回值
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			...
		}
	}
image-20220916200901839
image-20220916200901839
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
    
    ...
    
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
		// 查看那个hamdler能处理
        // selectHandler()实现在下面
		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
        //使用该处理器进行处理
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}
    
   	@Nullable
	private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
				continue;
			}
			if (handler.supportsReturnType(returnType)) {
				return handler;
			}
		}
		return null;
	}
}
image-20220916204044766
image-20220916204044766

需要标注@ResponseBody注解

@Override
public boolean supportsReturnType(MethodParameter returnType) {
   return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
         returnType.hasMethodAnnotation(ResponseBody.class));
}
image-20220916203654761
image-20220916203654761

执行该处理器会使用MessageConverters消息转换器,下一节再说

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
      throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

   mavContainer.setRequestHandled(true);
   ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
   ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
   // 使用消息转换器进行写操作
   // Try even with null return value. ResponseBodyAdvice could get involved.
   writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
image-20220916204301123
image-20220916204301123

12、HTTPMessageConverter原理(38)

返回值处理器ReturnValueHandler原理:

  1. 返回值处理器判断是否支持这种类型返回值 supportsReturnType
  2. 返回值处理器调用 handleReturnValue 进行处理
  3. RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
    • 利用 MessageConverters 进行处理 将数据写为json
      1. 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
      2. 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
      3. SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
        1. 得到MappingJackson2HttpMessageConverter可以将对象写为json
        2. 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
//RequestResponseBodyMethodProcessor继承这类
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
		implements HandlerMethodReturnValueHandler {
    ...
    @SuppressWarnings({"rawtypes", "unchecked"})
	protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
		...
		//判断是不是资源类型
		if (isResourceType(value, returnType)) {
			...
		}
		// 内容协商
		MediaType selectedMediaType = null;
        // 获得内容类型,如果第一次处理结果为null
		MediaType contentType = outputMessage.getHeaders().getContentType();
		boolean isContentTypePreset = contentType != null && contentType.isConcrete();
		if (isContentTypePreset) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found 'Content-Type:" + contentType + "' in response");
			}
            // 如果前面已经处理过了,会使用前面处理过的类型
			selectedMediaType = contentType;
		}
		else {
            //拿到request请求
			HttpServletRequest request = inputMessage.getServletRequest();
            // 获得浏览器能接收的媒体类型
			List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
            // 获得我们服务器能返回的媒体类型
			List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

			if (body != null && producibleTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
			List<MediaType> mediaTypesToUse = new ArrayList<>();
            // 双重for循环,匹配 浏览器可以接收的数据类型 和 我们服务器能返回的数据类型
			for (MediaType requestedType : acceptableTypes) {
				for (MediaType producibleType : producibleTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			if (mediaTypesToUse.isEmpty()) {
				...
			}
			// 按照权重进行排序
			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
			...
		}

		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
            // 判断所有的messageConverters (接口为 HttpMessageConverter<T> 类型)
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
                // 该媒体类型是否可以写指定类型
				if (genericConverter != null ?((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) {
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
						}
					}
					else {
						if (logger.isDebugEnabled()) {
							logger.debug("Nothing to write: null body");
						}
					}
					return;
				}
			}
		}

		if (body != null) {
			Set<MediaType> producibleMediaTypes =
					(Set<MediaType>) inputMessage.getServletRequest()
							.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

			if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
				throw new HttpMessageNotWritableException(
						"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
			}
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}
   
	...
}
image-20220916210221902
image-20220916210221902

isResourceType(value, returnType)方法判断是不是InputStreamResourceResource类型

protected boolean isResourceType(@Nullable Object value, MethodParameter returnType) {
   Class<?> clazz = getReturnValueType(value, returnType);
   return clazz != InputStreamResource.class && Resource.class.isAssignableFrom(clazz);
}
image-20220916210311235
image-20220916210311235

MediaType媒体类型牵扯到内容协商

image-20220916210939842
image-20220916210939842

内容协商就是浏览器在请求头里告诉服务器,我能接收什么类型?(内容协商后面还会详细说明)

能接收text/html,application/xhtml+xml,application/xml权重为0.9 (q为权重)

也能接收image/avif,image/webp,image/apng,*/*权重为0.8 (*/*表示所有)

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
image-20220916211140267
image-20220916211140267

requestedType浏览器接收*/*类型,producibleType服务器返回application/json类型才匹配 (由于浏览器接收任意类型,因此我们服务器后面的所有类型都能接收,总共有3个,但我感觉应该有4)

MediaType selectedMediaType = null;
MediaType contentType = outputMessage.getHeaders().getContentType();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
if (isContentTypePreset) {
   if (logger.isDebugEnabled()) {
      logger.debug("Found 'Content-Type:" + contentType + "' in response");
   }
   selectedMediaType = contentType;
}
else {
   HttpServletRequest request = inputMessage.getServletRequest();
   // 获得浏览器能接收的媒体类型
   List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
   // 获得我们服务器能返回的媒体类型
   List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

   if (body != null && producibleTypes.isEmpty()) {
      throw new HttpMessageNotWritableException(
            "No converter found for return value of type: " + valueType);
   }
   List<MediaType> mediaTypesToUse = new ArrayList<>();
   // 双重for循环,匹配 浏览器可以接收的数据类型 和 我们服务器能返回的数据类型
   for (MediaType requestedType : acceptableTypes) {
      for (MediaType producibleType : producibleTypes) {
         if (requestedType.isCompatibleWith(producibleType)) {
            mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
         }
      }
   }
   ...
   // 按照权重进行排序
   MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
image-20220916213303069
image-20220916213303069

这双重for循环,效率好像有点低啊

无标题
无标题

遍历所有的消息转换器

public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
		implements HandlerMethodReturnValueHandler {
    ...
    @SuppressWarnings({"rawtypes", "unchecked"})
	protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
		...
			// 按照权重进行排序
			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
			...
		}

		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
            // 判断所有的messageConverters (接口为 HttpMessageConverter<T> 类型)
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
						(GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ?
						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
			}
            ...
		}
	}
	...
}
image-20220917094815109
image-20220917094815109

messageConverters List<HttpMessageConverter<?>>类型,HttpMessageConverter<T>5个方法

public interface HttpMessageConverter<T> {
   // 能从浏览器中读取指定的媒体类型
   boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
   // 能使用指定的媒体类型给浏览器写数据
   boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

   List<MediaType> getSupportedMediaTypes();

   T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
         throws IOException, HttpMessageNotReadableException;

   void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
         throws IOException, HttpMessageNotWritableException;
}
image-20220916215429103
image-20220916215429103

有10个

image-20220916221549979
image-20220916221549979

0 - 只支持Byte类型的

1 - String

2 - String

3 - Resource

4 - ResourceRegion

5 - DOMSource.class \ SAXSource.class \ StAXSource.class \ StreamSource.class \ Source.class

6 - MultiValueMap

7 - true

8 - true

9 - 支持注解方式xml处理的。

MappingJackson2HttpMessageConverter继承AbstractJackson2HttpMessageConverterAbstractJackson2HttpMessageConverter又继承AbstractGenericHttpMessageConverter<Object>,并且都没重写AbstractGenericHttpMessageConverter<Object>supports方法,所以直接返回true,因此可以处理任意类型。

image-20220917100338882
image-20220917100338882

HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。

例子:Person对象转为JSON,或者 JSON转为Person,这将用到MappingJackson2HttpMessageConverter

MappingJackson2HttpMessageConverter的继承结构

image-20220916215911991
image-20220916215911991

遍历到MappingJackson2HttpMessageConverter并让其执行到converter.canWrite(valueType, selectedMediaType)这里

image-20220917095442227
image-20220917095442227
@Override
public boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) {
   return canWrite(clazz, mediaType);
}
image-20220916222218641
image-20220916222218641
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
   if (!canWrite(mediaType)) {
      return false;
   }
   if (mediaType != null && mediaType.getCharset() != null) {
      Charset charset = mediaType.getCharset();
      if (!ENCODINGS.containsKey(charset.name())) {
         return false;
      }
   }
   AtomicReference<Throwable> causeRef = new AtomicReference<>();
   if (this.objectMapper.canSerialize(clazz, causeRef)) {
      return true;
   }
   logWarningIfNecessary(clazz, causeRef.get());
   return false;
}
image-20220917100449396
image-20220917100449396
protected boolean canWrite(@Nullable MediaType mediaType) {
   // mediaType 即使为null 也支持
   if (mediaType == null || MediaType.ALL.equalsTypeAndSubtype(mediaType)) {
      return true;
   }
   for (MediaType supportedMediaType : getSupportedMediaTypes()) {
      if (supportedMediaType.isCompatibleWith(mediaType)) {
         return true;
      }
   }
   return false;
}
image-20220917101431519
image-20220917101431519

然后jackon来查看是否能处理

image-20220917101855978
image-20220917101855978
image-20220917102239159
image-20220917102239159
@Override
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
      HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

   final HttpHeaders headers = outputMessage.getHeaders();
   // 添加响应头 Content-Type -> application/json
   addDefaultHeaders(headers, t, contentType);

   if (outputMessage instanceof StreamingHttpOutputMessage) {
      StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
      streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
         @Override
         public OutputStream getBody() {
            return outputStream;
         }
         @Override
         public HttpHeaders getHeaders() {
            return headers;
         }
      }));
   }
   else {
      writeInternal(t, type, outputMessage);
      outputMessage.getBody().flush();
   }
}
image-20220917102428881
image-20220917102428881

然后就是jackson写数据

image-20220917103024989
image-20220917103024989

此时outputMessage已经写好数据了

image-20220917103427649
image-20220917103427649

关于MappingJackson2HttpMessageConverter的实例化请看下节。

关于HttpMessageConverters的初始化

DispatcherServlet的初始化时会调用initHandlerAdapters(ApplicationContext context)

public class DispatcherServlet extends FrameworkServlet {
    
    ...
    
	private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;

		if (this.detectAllHandlerAdapters) {
			// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerAdapters = new ArrayList<>(matchingBeans.values());
				// We keep HandlerAdapters in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerAdapters);
			}
		}
      ...

上述代码会加载ApplicationContext的所有HandlerAdapter,用来处理@RequestMappingRequestMappingHandlerAdapter实现HandlerAdapter接口,RequestMappingHandlerAdapter也被实例化。

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
    
    ...

    private List<HttpMessageConverter<?>> messageConverters;
    
    ...
    
	public RequestMappingHandlerAdapter() {
		this.messageConverters = new ArrayList<>(4);
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		this.messageConverters.add(new StringHttpMessageConverter());
		if (!shouldIgnoreXml) {
			try {
				this.messageConverters.add(new SourceHttpMessageConverter<>());
			}
			catch (Error err) {
				// Ignore when no TransformerFactory implementation is available
			}
		}
		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
	}

在构造器中看到一堆HttpMessageConverter。接着,重点查看AllEncompassingFormHttpMessageConverter类:

public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter {

	/**
	 * Boolean flag controlled by a {@code spring.xml.ignore} system property that instructs Spring to
	 * ignore XML, i.e. to not initialize the XML-related infrastructure.
	 * <p>The default is "false".
	 */
	private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore");

	private static final boolean jaxb2Present;

	private static final boolean jackson2Present;

	private static final boolean jackson2XmlPresent;

	private static final boolean jackson2SmilePresent;

	private static final boolean gsonPresent;

	private static final boolean jsonbPresent;

	private static final boolean kotlinSerializationJsonPresent;

	static {
		ClassLoader classLoader = AllEncompassingFormHttpMessageConverter.class.getClassLoader();
		jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
		jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
						ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
		jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
		jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
		gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
		jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
		kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader);
	}


	public AllEncompassingFormHttpMessageConverter() {
		if (!shouldIgnoreXml) {
			try {
				addPartConverter(new SourceHttpMessageConverter<>());
			}
			catch (Error err) {
				// Ignore when no TransformerFactory implementation is available
			}

			if (jaxb2Present && !jackson2XmlPresent) {
				addPartConverter(new Jaxb2RootElementHttpMessageConverter());
			}
		}

		if (jackson2Present) {
			addPartConverter(new MappingJackson2HttpMessageConverter());//<----重点看这里
		}
		else if (gsonPresent) {
			addPartConverter(new GsonHttpMessageConverter());
		}
		else if (jsonbPresent) {
			addPartConverter(new JsonbHttpMessageConverter());
		}
		else if (kotlinSerializationJsonPresent) {
			addPartConverter(new KotlinSerializationJsonHttpMessageConverter());
		}

		if (jackson2XmlPresent && !shouldIgnoreXml) {
			addPartConverter(new MappingJackson2XmlHttpMessageConverter());
		}

		if (jackson2SmilePresent) {
			addPartConverter(new MappingJackson2SmileHttpMessageConverter());
		}
	}

}

public class FormHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> {
    
    ...
        
    private List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
    
    ...
        
    public void addPartConverter(HttpMessageConverter<?> partConverter) {
		Assert.notNull(partConverter, "'partConverter' must not be null");
		this.partConverters.add(partConverter);
	}
    
    ...
}

AllEncompassingFormHttpMessageConverter类构造器看到MappingJackson2HttpMessageConverter类的实例化,AllEncompassingFormHttpMessageConverter包含MappingJackson2HttpMessageConverter

ReturnValueHandler是怎么与MappingJackson2HttpMessageConverter关联起来?请看下节。

ReturnValueHandler与MappingJackson2HttpMessageConverter关联

再次回顾RequestMappingHandlerAdapter

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
    
    ...
    @Nullable
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers;//我们关注的returnValueHandlers
    
   	
    @Override
	@Nullable//本方法在AbstractHandlerMethodAdapter
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return handleInternal(request, response, (HandlerMethod) handler);
	}
        
    @Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
		ModelAndView mav;
        ...
        mav = invokeHandlerMethod(request, response, handlerMethod);
        ...
		return mav;
	}
    
    @Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {//<---我们关注的returnValueHandlers
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
            
            ...
            
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}
    
   @Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBody advice beans
		
        ...
        
		if (this.returnValueHandlers == null) {//赋值returnValueHandlers
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}
    
    private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
		List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);

		...
		// Annotation-based return value types
        //这里就是 ReturnValueHandler与 MappingJackson2HttpMessageConverter关联 的关键点
		handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),//<---MessageConverters也就传参传进来的
				this.contentNegotiationManager, this.requestResponseBodyAdvice));//
        ...

		return handlers;
	}
    
    //------
    
    public List<HttpMessageConverter<?>> getMessageConverters() {
		return this.messageConverters;
	}
    
    //RequestMappingHandlerAdapter构造器已初始化部分messageConverters
   	public RequestMappingHandlerAdapter() {
		this.messageConverters = new ArrayList<>(4);
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		this.messageConverters.add(new StringHttpMessageConverter());
		if (!shouldIgnoreXml) {
			try {
				this.messageConverters.add(new SourceHttpMessageConverter<>());
			}
			catch (Error err) {
				// Ignore when no TransformerFactory implementation is available
			}
		}
		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
	}

    ...
              
}

应用中WebMvcAutoConfiguration(底层是WebMvcConfigurationSupport实现)传入更多messageConverters,其中就包含MappingJackson2HttpMessageConverter

13、内容协商原理

根据客户端接收能力不同,返回不同媒体类型的数据。

引入XML依赖:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

可用Postman软件分别测试返回json和xml:只需要改变请求头中Accept字段(application/json、application/xml)。

Http协议中规定的,Accept字段告诉服务器本客户端可以接收的数据类型。

内容协商原理

  1. 判断当前响应头中是否已经有确定的媒体类型MediaType
  2. 获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)(这一步在下一节有详细介绍)
    • contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
    • HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
  3. 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
  4. 找到支持操作Person的converter,把converter支持的媒体类型统计出来。
  5. 客户端需要application/xml,服务端有10种MediaType。
  6. 进行内容协商的最佳匹配媒体类型
  7. 用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。

Acceptapplication/json时返回json

{
    "userName": "zhangsan",
    "age": 28,
    "birth": "2022-09-17T02:48:14.482+00:00",
    "pet": null
}
image-20220917104859949

Acceptapplication/xml时返回xml

<Person>
    <userName>zhangsan</userName>
    <age>28</age>
    <birth>2022-09-17T02:49:38.932+00:00</birth>
    <pet/>
</Person>
image-20220917105010642

org.springframework.web.servlet.DispatcherServlet#doDispatch方法的这一行打断点

image-20220917143343069
image-20220917143343069
image-20220917143500914
image-20220917143500914

然后到org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod

image-20220917143617137
image-20220917143617137
image-20220917110159840
image-20220917110159840
image-20220917110457309
image-20220917110457309
image-20220917110545372
image-20220917110545372
1
image-20220917110820190
image-20220917110820190

获取浏览器可接收的媒体类型,这是通过内容协商管理器解析出来的,默认使用基于请求头的策略

private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
      throws HttpMediaTypeNotAcceptableException {

   return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}
image-20220917110852870
image-20220917110852870

遍历所有策略 (默认只有一个基于请求头的策略)

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
   for (ContentNegotiationStrategy strategy : this.strategies) {
      List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
      if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
         continue;
      }
      return mediaTypes;
   }
   return MEDIA_TYPE_ALL_LIST;
}
image-20220917111028750
image-20220917111028750

其实非常简单,就是调用原生的 request.getHeaderValues(HttpHeaders.ACCEPT)方法,获取请求头中Accept的值

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
      throws HttpMediaTypeNotAcceptableException {

   String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
   if (headerValueArray == null) {
      return MEDIA_TYPE_ALL_LIST;
   }

   List<String> headerValues = Arrays.asList(headerValueArray);
   try {
      List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
      MediaType.sortBySpecificityAndQuality(mediaTypes);
      return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
   }
   catch (InvalidMediaTypeException ex) {
      throw new HttpMediaTypeNotAcceptableException(
            "Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
   }
}
image-20220917111223141
image-20220917111223141
2

获取我们服务端可以返回的媒体类型

image-20220917111506445
image-20220917111506445

遍历messageConverters,查找到10给可以转换Person类型的消息转换器

image-20220917112500418
image-20220917112500418

遍历循环,匹配 浏览器能接收的 和 服务器能提供的

image-20220917113043748
image-20220917113043748

然后按权重进行排序

image-20220917113211850
image-20220917113211850

调用mediaTypes.sort()方法,传一个比较器,按照mediaType.getQualityValue()进行排序

public static void sortBySpecificityAndQuality(List<MediaType> mediaTypes) {
   Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
   if (mediaTypes.size() > 1) {
      mediaTypes.sort(MediaType.SPECIFICITY_COMPARATOR.thenComparing(MediaType.QUALITY_VALUE_COMPARATOR));
   }
}

public static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = (mediaType1, mediaType2) -> {
   double quality1 = mediaType1.getQualityValue();
   double quality2 = mediaType2.getQualityValue();
   int qualityComparison = Double.compare(quality2, quality1);
   if (qualityComparison != 0) {
      return qualityComparison;  // audio/*;q=0.7 < audio/*;q=0.3
   }
   ...
}
image-20220917113709182
image-20220917113709182

mediaTypes.sort()方法就是调用Arrays.sort()方法,传一个比较器

image-20220917113953875
image-20220917113953875

遍历排序后的已匹配的媒体类型,如果找到一个具体的MIME类型的媒体类型就退出循环,所以选中的媒体类型肯定只有一个

image-20220917114459919
image-20220917114459919

然后就查看谁能转换为xml,找到后进行写数据

image-20220917143017214
image-20220917143017214
image-20220917143226471
image-20220917143226471
image-20220917143839651
image-20220917143839651

此时已转为xml了

image-20220917143952002
image-20220917143952002

//RequestResponseBodyMethodProcessor继承这类
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
		implements HandlerMethodReturnValueHandler {

    ...
    
    //跟上一节的代码一致
    protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
                ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
                throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

            Object body;
            Class<?> valueType;
            Type targetType;

            if (value instanceof CharSequence) {
                body = value.toString();
                valueType = String.class;
                targetType = String.class;
            }
            else {
                body = value;
                valueType = getReturnValueType(body, returnType);
                targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
            }

			...

            //本节重点
            //内容协商(浏览器默认会以请求头(参数Accept)的方式告诉服务器他能接受什么样的内容类型)
            MediaType selectedMediaType = null;
            MediaType contentType = outputMessage.getHeaders().getContentType();
            boolean isContentTypePreset = contentType != null && contentType.isConcrete();
            if (isContentTypePreset) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Found 'Content-Type:" + contentType + "' in response");
                }
                selectedMediaType = contentType;
            }
            else {
                HttpServletRequest request = inputMessage.getServletRequest();
                List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
                //服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
                List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

                if (body != null && producibleTypes.isEmpty()) {
                    throw new HttpMessageNotWritableException(
                            "No converter found for return value of type: " + valueType);
                }
                List<MediaType> mediaTypesToUse = new ArrayList<>();
                for (MediaType requestedType : acceptableTypes) {
                    for (MediaType producibleType : producibleTypes) {
                        if (requestedType.isCompatibleWith(producibleType)) {
                            mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
                        }
                    }
                }
                if (mediaTypesToUse.isEmpty()) {
                    if (body != null) {
                        throw new HttpMediaTypeNotAcceptableException(producibleTypes);
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
                    }
                    return;
                }

                MediaType.sortBySpecificityAndQuality(mediaTypesToUse);

                //选择一个MediaType
                for (MediaType mediaType : mediaTypesToUse) {
                    if (mediaType.isConcrete()) {
                        selectedMediaType = mediaType;
                        break;
                    }
                    else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
                        selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                        break;
                    }
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("Using '" + selectedMediaType + "', given " +
                            acceptableTypes + " and supported " + producibleTypes);
                }
            }

        	
            if (selectedMediaType != null) {
                selectedMediaType = selectedMediaType.removeQualityValue();
                //本节主角:HttpMessageConverter
                for (HttpMessageConverter<?> converter : this.messageConverters) {
                    GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                            (GenericHttpMessageConverter<?>) converter : null);
                    
                    //判断是否可写
                    if (genericConverter != null ?
                            ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
                            converter.canWrite(valueType, selectedMediaType)) {
                        body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                                (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                                inputMessage, outputMessage);
                        if (body != null) {
                            Object theBody = body;
                            LogFormatUtils.traceDebug(logger, traceOn ->
                                    "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
                            addContentDispositionHeader(inputMessage, outputMessage);
							//开始写入
                            if (genericConverter != null) {
                                genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                            }
                            else {
                                ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                            }
                        }
                        else {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Nothing to write: null body");
                            }
                        }
                        return;
                    }
                }
            }
			...
        }

14、基于请求参数的内容协商原理(p40)

上一节内容协商原理的第二步:

获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)

  • contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
  • HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
//RequestResponseBodyMethodProcessor继承这类
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
		implements HandlerMethodReturnValueHandler {

    ...
    
    //跟上一节的代码一致
    protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
                ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
                throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

            Object body;
            Class<?> valueType;
            Type targetType;
        
        	...
        
                    //本节重点
            //内容协商(浏览器默认会以请求头(参数Accept)的方式告诉服务器他能接受什么样的内容类型)
            MediaType selectedMediaType = null;
            MediaType contentType = outputMessage.getHeaders().getContentType();
            boolean isContentTypePreset = contentType != null && contentType.isConcrete();
            if (isContentTypePreset) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Found 'Content-Type:" + contentType + "' in response");
                }
                selectedMediaType = contentType;
            }
            else {
                HttpServletRequest request = inputMessage.getServletRequest();
                List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
                //服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
                List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
            ...
            
	}
    
    //在AbstractMessageConverterMethodArgumentResolver类内
   	private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
			throws HttpMediaTypeNotAcceptableException {

        //内容协商管理器 默认使用基于请求头的策略
		return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
	}
        
}
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
	
    ...
    
    public ContentNegotiationManager() {
		this(new HeaderContentNegotiationStrategy());//内容协商管理器 默认使用基于请求头的策略
	}
    
    @Override
	public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
		for (ContentNegotiationStrategy strategy : this.strategies) {
			List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
			if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
				continue;
			}
			return mediaTypes;
		}
		return MEDIA_TYPE_ALL_LIST;
	}
    ...
    
}
//基于请求头的策略
public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {

	/**
	 * {@inheritDoc}
	 * @throws HttpMediaTypeNotAcceptableException if the 'Accept' header cannot be parsed
	 */
	@Override
	public List<MediaType> resolveMediaTypes(NativeWebRequest request)
			throws HttpMediaTypeNotAcceptableException {

		String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
		if (headerValueArray == null) {
			return MEDIA_TYPE_ALL_LIST;
		}

		List<String> headerValues = Arrays.asList(headerValueArray);
		try {
			List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
			MediaType.sortBySpecificityAndQuality(mediaTypes);
			return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
		}
		catch (InvalidMediaTypeException ex) {
			throw new HttpMediaTypeNotAcceptableException(
					"Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
		}
	}

}

开启浏览器参数方式内容协商功能

为了方便内容协商,开启基于请求参数的内容协商功能。

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

然后,浏览器地址输入带format参数的URL:

这样,后端会根据参数format的值,返回对应json或xml格式的数据。

http://localhost:8080/test/person?format=json
image-20220917145543151
image-20220917145543151
http://localhost:8080/test/person?format=xml
image-20220917145609773
image-20220917145609773

内容协商管理器,就会多了一个ParameterContentNegotiationStrategy,基于参数的(由Spring容器注入)

image-20220917145943036
image-20220917145943036

支持jsonxml

image-20220917150336140
image-20220917150336140

那为什么ParameterContentNegotiationStrategy在前面,而HeaderContentNegotiationStrategy在后面呢?

org.springframework.web.accept.ContentNegotiationManagerFactoryBean#build方法里已经指定顺序了

package org.springframework.web.accept;

public class ContentNegotiationManagerFactoryBean
		implements FactoryBean<ContentNegotiationManager>, ServletContextAware, InitializingBean {

	@SuppressWarnings("deprecation")
	public ContentNegotiationManager build() {
		List<ContentNegotiationStrategy> strategies = new ArrayList<>();

		if (this.strategies != null) {
			strategies.addAll(this.strategies);
		}
		else {
			...
			if (this.favorParameter) {
				ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
				strategy.setParameterName(this.parameterName);
				if (this.useRegisteredExtensionsOnly != null) {
					strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
				}
				else {
					strategy.setUseRegisteredExtensionsOnly(true);  // backwards compatibility
				}
				strategies.add(strategy);
			}
			if (!this.ignoreAcceptHeader) {
				strategies.add(new HeaderContentNegotiationStrategy());
			}
			if (this.defaultNegotiationStrategy != null) {
				strategies.add(this.defaultNegotiationStrategy);
			}
		}
		...
		return this.contentNegotiationManager;
	}
	...
}

image-20220917210857120
image-20220917210857120
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
   for (ContentNegotiationStrategy strategy : this.strategies) {
      List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
      // 如果mediaTypes为`*/*`继续遍历
      if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
         continue;
      }
      // 如果mediaTypes不为`*/*`,直接返回
      return mediaTypes;
   }
   return MEDIA_TYPE_ALL_LIST;
}
image-20220917150850386
image-20220917150850386
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
      throws HttpMediaTypeNotAcceptableException {

   return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
}
image-20220917151053616
image-20220917151053616

其会调用request.getParameter("format")

public String getParameterName() {
   return this.parameterName;
}


@Override
@Nullable
protected String getMediaTypeKey(NativeWebRequest request) {
   return request.getParameter(getParameterName());
}
image-20220917151841200
image-20220917151841200

然后根据json找到application/json类型的MediaType

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
      throws HttpMediaTypeNotAcceptableException {

   return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
}

public List<MediaType> resolveMediaTypeKey(NativeWebRequest webRequest, @Nullable String key)
      throws HttpMediaTypeNotAcceptableException {

   if (StringUtils.hasText(key)) {
      MediaType mediaType = lookupMediaType(key);
      if (mediaType != null) {
         handleMatch(key, mediaType);
         return Collections.singletonList(mediaType);
      }
      mediaType = handleNoMatch(webRequest, key);
      if (mediaType != null) {
         addMapping(key, mediaType);
         return Collections.singletonList(mediaType);
      }
   }
   return MEDIA_TYPE_ALL_LIST;
}
image-20220917152338348
protected MediaType lookupMediaType(String extension) {
   return this.mediaTypes.get(extension.toLowerCase(Locale.ENGLISH));
}
image-20220917152159911
image-20220917152159911
public class ParameterContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {

	private String parameterName = "format";//


	/**
	 * Create an instance with the given map of file extensions and media types.
	 */
	public ParameterContentNegotiationStrategy(Map<String, MediaType> mediaTypes) {
		super(mediaTypes);
	}


	/**
	 * Set the name of the parameter to use to determine requested media types.
	 * <p>By default this is set to {@code "format"}.
	 */
	public void setParameterName(String parameterName) {
		Assert.notNull(parameterName, "'parameterName' is required");
		this.parameterName = parameterName;
	}

	public String getParameterName() {
		return this.parameterName;
	}


	@Override
	@Nullable
	protected String getMediaTypeKey(NativeWebRequest request) {
		return request.getParameter(getParameterName());
	}
    
    //---以下方法在AbstractMappingContentNegotiationStrategy类
    
    @Override
	public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
			throws HttpMediaTypeNotAcceptableException {

		return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
	}

	/**
	 * An alternative to {@link #resolveMediaTypes(NativeWebRequest)} that accepts
	 * an already extracted key.
	 * @since 3.2.16
	 */
	public List<MediaType> resolveMediaTypeKey(NativeWebRequest webRequest, @Nullable String key)
			throws HttpMediaTypeNotAcceptableException {

		if (StringUtils.hasText(key)) {
			MediaType mediaType = lookupMediaType(key);
			if (mediaType != null) {
				handleMatch(key, mediaType);
				return Collections.singletonList(mediaType);
			}
			mediaType = handleNoMatch(webRequest, key);
			if (mediaType != null) {
				addMapping(key, mediaType);
				return Collections.singletonList(mediaType);
			}
		}
		return MEDIA_TYPE_ALL_LIST;
	}
    

}

自定义MessageConverter

需求:

序号请求方式媒体类型消息转换器
1浏览器发请求直接返回xml[application/xmL]jacksonXmLConverter
2如果是ajax请求返回json[application/json]jacksonJsonConverter
3如果硅谷app发请求,返回自定义协议数据[ appliaction/x-guigu]XXxxConverter

实现多协议数据兼容。json、xml、x-guigu(这个是自创的)

  1. @ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理

  2. Processor 处理方法返回值。通过 MessageConverter处理

  3. 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)

  4. 内容协商找到最终的 messageConverter

默认配置了很多messageConverter

![GIF 2022-9-17 15-53-46](https://gitlab.com/apzs/image/-/raw/master/image/GIF 2022-9-17 15-53-46.gif)

protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
   messageConverters.add(new ByteArrayHttpMessageConverter());
   messageConverters.add(new StringHttpMessageConverter());
   messageConverters.add(new ResourceHttpMessageConverter());
   messageConverters.add(new ResourceRegionHttpMessageConverter());
   try {
      messageConverters.add(new SourceHttpMessageConverter<>());
   }
   catch (Throwable ex) {
      // Ignore when no TransformerFactory implementation is available...
   }
   messageConverters.add(new AllEncompassingFormHttpMessageConverter());
   ...
}

SpringMVC的什么功能,一个入口给容器中添加一个 WebMvcConfigurer

@Configuration(proxyBeanMethods = false)
public class WebConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
			/* 扩展消息转换 */
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new GuiguMessageConverter());
            }
        }
    }
}

/**
 * 自定义的Converter
 */
public class GuiguMessageConverter implements HttpMessageConverter<Person> {

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(Person.class);
    }

    /**
     * 服务器要统计所有MessageConverter都能写出哪些内容类型
     *
     * application/x-guigu
     * @return
     */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-guigu");
    }

    @Override
    public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        //自定义协议数据的写出
        String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();


        //写出去
        OutputStream body = outputMessage.getBody();
        body.write(data.getBytes());
    }
}
import java.util.Date;

@Controller
public class ResponseTestController {

    /**
     * 1、浏览器发请求直接返回 xml    [application/xml]        jacksonXmlConverter
     * 2、如果是ajax请求 返回 json   [application/json]      jacksonJsonConverter
     * 3、如果硅谷app发请求,返回自定义协议数据  [appliaction/x-guigu]   xxxxConverter
     *          属性值1;属性值2;
     *
     * 步骤:
     * 1、添加自定义的MessageConverter进系统底层
     * 2、系统底层就会统计出所有MessageConverter能操作哪些类型
     * 3、客户端内容协商 [guigu--->guigu]
     *
     * 作业:如何以参数的方式进行内容协商
     * @return
     */
    @ResponseBody  //利用返回值处理器里面的消息转换器进行处理
    @GetMapping(value = "/test/person")
    public Person getPerson(){
        Person person = new Person();
        person.setAge(28);
        person.setBirth(new Date());
        person.setUserName("zhangsan");
        return person;
    }

}

用Postman发送/test/person(请求头Accept:application/x-guigu),将返回自定义协议数据的写出。

image-20220917194937653
image-20220917194937653

此时,可以浏览器可以接收的类型为application/x-guigu

image-20220917195201672
image-20220917195201672
image-20220917195539775
image-20220917195539775
image-20220917200248902
image-20220917200248902

我们自定义的messageConverters只有是Person对象才能写

image-20220917195640196
image-20220917195640196
image-20220917200334030
image-20220917200334030
image-20220917200405267
image-20220917200405267

遍历,查找我们能能提供的写Person对象的类型

image-20220917195906443
image-20220917195906443

最后只剩下application/x-guigu

image-20220917200027467
image-20220917200027467

按权重选出浏览器能接收,服务器能返回的类型后,再遍历这些消息转换器,然后判断是否可写,再写出去。(我总就感觉这样效率很低,明明前面已经判断过写Person对象我们能提供的媒体类型了,就可以把媒体类型和这个消息转换器一起返回,得到一个最佳匹配的媒体类型后,直接调用这个消息转换器的写方法就行了。没必要得到最佳匹配后,再遍历这些消息转换器,再判断谁能使用指定的媒体类型写Person对象)

image-20220917200916265
image-20220917200916265

15、浏览器与PostMan内容协商完全适配(42)

假设你想基于自定义请求参数的自定义内容协商功能。

换句话,在地址栏输入http://localhost:8080/test/person?format=gg返回数据,跟http://localhost:8080/test/person且请求头参数Accept:application/x-guigu的返回自定义协议数据的一致。

由于ParameterContentNegotiationStrategy只能解析xmljson,因此我们要在ContentNegotiationManager内容协商管理器里换掉ParameterContentNegotiationStrategy参数内容协商策略,使其能够解析format=gg

image-20220917203101638
image-20220917203101638
@Configuration(proxyBeanMethods = false)
public class WebConfig /*implements WebMvcConfigurer*/ {

    //1、WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {

            /**
             * 自定义内容协商策略
             * @param configurer
             */
            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                //Map<String, MediaType> mediaTypes
                Map<String, MediaType> mediaTypes = new HashMap<>();
                mediaTypes.put("json",MediaType.APPLICATION_JSON);
                mediaTypes.put("xml",MediaType.APPLICATION_XML);
                //自定义媒体类型
                mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));
                //指定支持解析哪些参数对应的哪些媒体类型
                ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
                // localhost:8080/test/person?ff=json
				// parameterStrategy.setParameterName("ff");
                //还需添加请求头处理策略,否则accept:application/json、application/xml则会失效
                HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();

                configurer.strategies(Arrays.asList(parameterStrategy, headeStrategy));
            }
        }
    }
    ...
}

添加format=gg,这样也可以使用自定义的媒体类型了

http://localhost:8080/test/person?format=gg
image-20220917212700064
image-20220917212700064

如果只设置 configurer.strategies(Arrays.asList(parameterStrategy));,这样ContentNegotiationManager内容协商管理器就只有一个ParameterContentNegotiationStrategy参数内容协商策略了

image-20220917212126496
image-20220917212126496

设置configurer.strategies(Arrays.asList(parameterStrategy, headeStrategy));后,ContentNegotiationManager内容协商管理器才有ParameterContentNegotiationStrategyHeaderContentNegotiationStrategy

image-20220917212607788
image-20220917212607788

日后开发要注意,有可能我们添加的自定义的功能会覆盖默认原有功能,导致一些默认的功能失效。

43、视图解析-Thymeleaf初体验

Thymeleaf is a modern server-side Java template engine for both web and standalone environments.

Thymeleaf's main goal is to bring elegant natural templates to your development workflow — HTML that can be correctly displayed in browsers and also work as static prototypes, allowing for stronger collaboration in development teams.

With modules for Spring Framework, a host of integrations with your favourite tools, and the ability to plug in your own functionality, Thymeleaf is ideal for modern-day HTML5 JVM web development — although there is much more it can do.——Linkopen in new window

Thymeleaf官方文档open in new window

thymeleaf使用

引入Starter
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
自动配置好了thymeleaf
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {
    ...
}

自动配好的策略

  1. 所有thymeleaf的配置值都在 ThymeleafProperties

  2. 配置好了 SpringTemplateEngine

  3. 配好了 ThymeleafViewResolver

  4. 我们只需要直接开发页面

public static final String DEFAULT_PREFIX = "classpath:/templates/";//模板放置处
public static final String DEFAULT_SUFFIX = ".html";//文件的后缀名

编写一个控制层:

@Controller
public class ViewTestController {
    @GetMapping("/hello")
    public String hello(Model model){
        //model中的数据会被放在请求域中 request.setAttribute("a",aa)
        model.addAttribute("msg","一定要大力发展工业文化");
        model.addAttribute("link","http://www.baidu.com");
        return "success";
    }
}

/templates/success.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}">nice</h1>
<h2>
    <a href="www.baidu.com" th:href="${link}">去百度</a>  <br/>
    <a href="www.google.com" th:href="@{/link}">去百度</a>
</h2>
</body>
</html>

server:
  servlet:
    context-path: /app #设置应用名

这个设置后,URL要插入/app, 如http://localhost:8080/app/hello.html

基本语法

表达式
表达式名字语法用途
变量取值$获取请求域、session域、对象等值
选择变量*获取上下文对象值
消息#获取国际化等值
链接@生成链接
片段表达式~jsp:include 作用,引入公共页面片段
字面量
  • 文本值: 'one text' , 'Another one!' ,…
  • 数字: 0 , 34 , 3.0 , 12.3 ,…
  • 布尔值: true , false
  • 空值: null
  • 变量: one,two,.... 变量不能有空格
文本操作
  • 字符串拼接: +
  • 变量替换: |The name is ${name}|
数学运算
  • 运算符: + , - , * , / , %
布尔运算
  • 运算符: and , or
  • 一元运算: ! , not
比较运算
  • 比较: > , < , >= , <= ( gt , lt , ge , le )
  • 等式: == , != ( eq , ne )
条件运算
  • If-then: (if) ? (then)
  • If-then-else: (if) ? (then) : (else)
  • Default: (value) ?: (defaultvalue)
特殊操作
  • 无操作: _

设置属性值-th:attr

  • 设置单个值
<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>
  • 设置多个值
<img src="../../images/gtvglogo.png"  
     th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

官方文档 - 5 Setting Attribute Valuesopen in new window

迭代

<tr th:each="prod : ${prods}">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>

条件运算

<a href="comments.html"
	th:href="@{/product/comments(prodId=${prod.id})}"
	th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
      <p th:case="'admin'">User is an administrator</p>
      <p th:case="#{roles.manager}">User is a manager</p>
      <p th:case="*">User is some other thing</p>
</div>

属性优先级

OrderFeatureAttributes
1Fragment inclusionth:insert th:replace
2Fragment iterationth:each
3Conditional evaluationth:if th:unless th:switch th:case
4Local variable definitionth:object th:with
5General attribute modificationth:attr th:attrprepend th:attrappend
6Specific attribute modificationth:value th:href th:src ...
7Text (tag body modification)th:text th:utext
8Fragment specificationth:fragment
9Fragment removalth:remove

官方文档 - 10 Attribute Precedenceopen in new window

44、web实验-后台管理系统基本功能

项目创建

使用IDEA的Spring Initializr。

  • thymeleaf、
  • web-starter、
  • devtools、
  • lombok

登陆页面

  • /static 放置 css,js等静态资源

  • /templates/login.html 登录页

<html lang="en" xmlns:th="http://www.thymeleaf.org"><!-- 要加这玩意thymeleaf才能用 -->

<form class="form-signin" action="index.html" method="post" th:action="@{/login}">

    ...
    
    <!-- 消息提醒 -->
    <label style="color: red" th:text="${msg}"></label>
    
    <input type="text" name="userName" class="form-control" placeholder="User ID" autofocus>
    <input type="password" name="password" class="form-control" placeholder="Password">
    
    <button class="btn btn-lg btn-login btn-block" type="submit">
        <i class="fa fa-check"></i>
    </button>
    
    ...
    
</form>
  • /templates/main.html 主页

thymeleaf内联写法:

<p>Hello, [[${session.user.name}]]!</p>

登录控制层

@Controller
public class IndexController {
    /**
     * 来登录页
     * @return
     */
    @GetMapping(value = {"/","/login"})
    public String loginPage(){

        return "login";
    }

    @PostMapping("/login")
    public String main(User user, HttpSession session, Model model){ //RedirectAttributes

        if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
            //把登陆成功的用户保存起来
            session.setAttribute("loginUser",user);
            //登录成功重定向到main.html;  重定向防止表单重复提交
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","账号密码错误");
            //回到登录页面
            return "login";
        }
    }
    
     /**
     * 去main页面
     * @return
     */
    @GetMapping("/main.html")
    public String mainPage(HttpSession session, Model model){
        
        //最好用拦截器,过滤器
        Object loginUser = session.getAttribute("loginUser");
        if(loginUser != null){
        	return "main";
        }else {
            //session过期,没有登陆过
        	//回到登录页面
	        model.addAttribute("msg","请重新登录");
    	    return "login";
        }
    }
    
}

模型

@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
    private String userName;
    private String password;
}
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.0.0-alpha.8