01、基础入门-SpringBoot2课程介绍

  1. Spring Boot 2核心技术

  2. Spring Boot 2响应式编程

02、基础入门-Spring生态圈

Spring官网

Spring能做什么

Spring的能力

在这里插入图片描述

Spring的生态

覆盖了:

  • web开发
  • 数据访问
  • 安全控制
  • 分布式
  • 消息服务
  • 移动开发
  • 批处理
  • ……

Spring5重大升级

  • 响应式编程

在这里插入图片描述

  • 内部源码设计

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

为什么用SpringBoot

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

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

SpringBoot优点

  • Create stand-alone Spring applications

    • 创建独立Spring应用
  • Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)

    • 内嵌web服务器
  • Provide opinionated ‘starter’ dependencies to simplify your build configuration

    • 自动starter依赖,简化构建配置
  • 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技术栈的快速开发脚手架

SpringBoot缺点

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

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

微服务

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)

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

分布式

在这里插入图片描述

分布式的困难

  • 远程调用
  • 服务发现
  • 负载均衡
  • 服务容错
  • 配置管理
  • 服务监控
  • 链路追踪
  • 日志管理
  • 任务调度
  • ……

分布式的解决

  • SpringBoot + SpringCloud

在这里插入图片描述

云原生

原生应用如何上云。 Cloud Native

上云的困难

  • 服务自愈
  • 弹性伸缩
  • 服务隔离
  • 自动化部署
  • 灰度发布
  • 流量治理
  • ……

上云的解决

在这里插入图片描述

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

官网文档架构

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

查看版本新特性

在这里插入图片描述

05、基础入门-SpringBoot-HelloWorld

系统要求

  • Java 8
  • Maven 3.3+
  • IntelliJ IDEA 2019.1.2

Maven配置文件

新添内容:

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<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>

HelloWorld项目

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

创建maven工程

引入依赖

xml
1
2
3
4
5
6
7
8
9
10
11
12
<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>

创建主程序

java
1
2
3
4
5
6
7
8
9
10
11
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);
}
}

编写业务

java
1
2
3
4
5
6
7
8
9
10
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!";
}
}

运行&测试

  • 运行MainApplication
  • 浏览器输入http://localhost:8888/hello,将会输出Hello, Spring Boot 2!

设置配置

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

properties
1
2
# 设置端口号
server.port=8888

更多配置信息

打包部署

在pom.xml添加

xml
1
2
3
4
5
6
7
8
<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包直接在目标服务器执行即可。

06、基础入门-SpringBoot-依赖管理特性

  • 父项目做依赖管理
xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
依赖管理
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>

上面项目的父项目如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>

它几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
  • 开发导入starter场景启动器
    1. 见到很多 spring-boot-starter-* : *就某种场景
    2. 只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
    3. 更多SpringBoot所有支持的场景
    4. 其他第三方依赖 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
xml
1
2
3
4
5
6
7
所有场景启动器最底层的依赖
<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里面规定当前依赖的版本 用的 key。
    2. 在当前项目里面重写配置,如下面的代码。
xml
1
2
3
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>

IDEA快捷键:

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

07、基础入门-SpringBoot-自动配置特性

  • 自动配好Tomcat
    • 引入Tomcat依赖。
    • 配置Tomcat
xml
1
2
3
4
5
6
<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开发的常见场景
java
1
2
3
4
5
6
7
8
9
10
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(scanBasePackages=”com.lun”)
      • @ComponentScan 指定扫描路径
java
1
2
3
4
5
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.lun")
  • 各种配置拥有默认值

    • 默认配置最终都是映射到某个类上,如:MultipartProperties
    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
  • 按需加载所有自动配置项
    - 非常多的starter
    - 引入了哪些场景这个场景的自动配置才会开启
    - SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

  • ……

08、底层注解-@Configuration(配置类)详解

  • 基本使用
    • Full模式与Lite模式
    • 示例
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)(保证每个@Bean方法被调用多少次返回的组件都是单实例的)(默认),如果组件之间有依赖,使用Full模式。
* Lite(proxyBeanMethods = false)(每个@Bean方法被调用多少次返回的组件都是新创建的),如果组件之间无依赖,各自独立,使用Lite模式,启动更快,减少判断。
*/
@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测试代码如下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@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、从容器中获取组件
Pet tom01 = run.getBean("tom", Pet.class);
Pet tom02 = run.getBean("tom", Pet.class);
System.out.println("组件:"+(tom01 == tom02));

//4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
//获取的是一个有cglib代理的增强代理对象。
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);

//如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中存在,存在则返回该对象,不存在则创建新的,保持单例模式。
//保持组件单实例
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user == user1);

User user01 = run.getBean("user01", User.class);
Pet tom = run.getBean("tom", Pet.class);

System.out.println("用户的宠物:"+(user01.getPet() == tom));
}
}
  • 最佳实战
    • 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
    • 配置 类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)

lite 英 [laɪt] 美 [laɪt]
adj. 低热量的,清淡的(light的一种拼写方法);类似…的劣质品


IDEA快捷键:

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

09、底层注解-@Import导入组件

@Bean、@Component、@Controller、@Service、@Repository,它们是Spring的基本标签,在Spring Boot中并未改变它们原来的功能。

@ComponentScan 在07、基础入门-SpringBoot-自动配置特性有用例。(组件扫描,可以在后面增加()指定扫描的包)

@Import({User.class, DBHelper.class})给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名(更愿意称为导入组件)

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

测试类:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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

//...

//5、获取组件
String[] beanNamesForType = run.getBeanNamesForType(User.class);

for (String s : beanNamesForType) {
System.out.println(s);
}

DBHelper bean1 = run.getBean(DBHelper.class);
System.out.println(bean1);

10、底层注解-@Conditional条件装配

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

在这里插入图片描述

用@ConditionalOnMissingBean举例说明(无此bean,mycongfig下的配置才生效)

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@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

}

11、底层注解-@ImportResource导入Spring配置文件

比如,公司使用bean.xml文件生成配置bean,然而你为了省事,想继续复用bean.xml,@ImportResource粉墨登场。
总结:springboot给予你使用旧式的xml配置bean的方法进行配置bean到IOC容器。
bean.xml:

xml
1
2
3
4
5
6
7
8
9
10
11
12
<?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>

使用方法:

java
1
2
3
4
@ImportResource("classpath:beans.xml")
public class MyConfig {
...
}

测试类:

java
1
2
3
4
5
6
7
8
9
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
}

12、底层注解-@ConfigurationProperties配置绑定

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

传统方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
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

properties
1
2
mycar.brand=BYD
mycar.price=100000

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

java
1
2
3
4
5
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}

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

@EnableConfigurationProperties + @ConfigurationProperties

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

13、自动配置【源码分析】-自动包规则原理

Spring Boot应用的启动类:

java
1
2
3
4
5
6
7
8
@SpringBootApplication
public class MainApplication {

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

}

分析下@SpringBootApplication

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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

@SpringBootConfiguration

java
1
2
3
4
5
6
7
8
9
10
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}

@Configuration代表当前是一个配置类。默认状态下该配置类是单实例的。

@ComponentScan


指定扫描哪些Spring注解。并设置TypeExcludeFilter和AutoConfigurationExcludeFilter过滤器来过滤掉不需要进行扫描的包。

@ComponentScan 在07、基础入门-SpringBoot-自动配置特性有用例。

@EnableAutoConfiguration

java
1
2
3
4
5
6
7
8
9
10
11
12
13
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
//这里可以通过Class[]数组使用该类名来排除掉不想加载的类
Class<?>[] exclude() default {};
//这里可以通过String[]数组使用全类名来排除不想加载的类
String[] excludeName() default {};
}

说明 @EnableAutoConfiguration由以下两个注解合成。
重点分析@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage

标签名直译为:自动配置包,指定了默认的包规则。

java
1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)//给容器中导入一个注册组件
public @interface AutoConfigurationPackage {
//确定basePackages 返回的是String数组意味着可以有多个
String[] basePackages() default {};
//确定basePackages的类 返回的是Class[]数组 意味着可以有多个
Class<?>[] basePackageClasses() default {};
}

研究一下导入的注册组件。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public abstract class AutoConfigurationPackages {
...
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
//静态类,创建无参构造器
Registrar() {
}
//指定AutoConfigurationPackages注册到到哪里去。
//使用注册表registry,并传入注册的包名(此处为spring boot主程序所在的包的全类名),设置当前spring boot主程序所在的包为扫描包,后续要针对当前的包进行扫描
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
} //意思就是在主程序的包中,注册AutoConfigurationPackages包中的组件。

public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
...//上述方法执行到这里
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
//这里会检查注册表中是否包含(BEAN) BEAN=org.springframework.boot.autoconfigure.AutoConfigurationPackages
//由于是第一次加载,判断结果会是false,进入第二条分支
if (registry.containsBeanDefinition(BEAN)) {
addBasePackages(registry.getBeanDefinition(BEAN), packageNames);
} else {
//定义beanDefiniton
RootBeanDefinition beanDefinition = new RootBeanDefinition(BasePackages.class);
beanDefinition.setRole(2);
//将传入的beanDefinition和packageNames添加到basePackages里留着后续扫描
addBasePackages(beanDefinition, packageNames);
//给beanName为Bean的beanDefintion对象进行注册。将其存进Spring的IOC容器(底层也是一个Map)中,beanName为Key,value为beanDefiniton。
registry.registerBeanDefinition(BEAN, beanDefinition);
}

}
...
}
  1. 利用Registrar给容器中注册AutoConfigurationPackages下的一系列组件
  2. 将指定的一个包AutoConfigurationPackages下的所有组件注册进MainApplication所在包下
  3. 结合后面的Import注解将自动配置类加载进主程序所在的包下。

14、自动配置【源码分析】-初始加载自动配置类

@Import(AutoConfigurationImportSelector.class)

研究一下导入的这个AutoConfigurationImportSelector类

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//可以看见这个类实现了很多Aware接口(可以获得前面的类名的对象),和DeferredImportSelector接口(选择性导入相关)和Ordered接口(加载顺序相关)
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
private static final String[] NO_IMPORTS = new String[0];
private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
private ConfigurationClassFilter configurationClassFilter;

public AutoConfigurationImportSelector() {
}
//这个方法是关键。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//isEnabled()方法判断SpringBoot是否开启了自动配置。若开启就通过
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//getAutoConfigurationEntry()来获取需要配置的Bean全限定名数组,否则就直接返回空数组。
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

...//上述第一步判断方法
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
// 若调用该方法的类是AutoConfigurationImportSelector,那么就获取EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY的值,默认为true
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}//String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
//看到这里,我们可以猜到这就是在配置文件application.yml或者application.properties中的配置。因此,我们可以在配置文件中来决定SpringBoot是否开启自动配置。当我们没有配置的时候,默认就是开启自动配置的。
return true;
}
...//上述第二步的自动配置入口方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 判断是否开启自动配置
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
// 获取@EnableAutoConfiguration注解的属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 从spring.factories文件中获取配置类的全限定名数组//关键方法,在这里进行加载自动配置的类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = this.removeDuplicates(configurations);
// 获取注解中exclude或excludeName排除的类集合
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 检查被排除类是否可以实例化,是否被自动配置所使用,否则抛出异常
this.checkExcludedClasses(configurations, exclusions);
// 去除被排除的类
configurations.removeAll(exclusions);
// 使用spring.factories配置文件中配置的过滤器对自动配置类进行过滤
configurations = this.getConfigurationClassFilter().filter(configurations);
// 抛出事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}

...//自动配置入口的关键方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}//`getCandidateConfigurations()`方法通过ImportCandidates.load()方法从所有的META-INF/spring/%s.imports文件中获取需要配置的bean全限定名列表。

...//研究一下上述的load方法
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
//判断注解不为空,此处有interface org.springframework.boot.autoconfigure.AutoConfiguration注解
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
//定位加载地址:LOCATION=META-INF/spring/%s.imports
String location = String.format(LOCATION, annotation.getName());
//在类路径classLoaderToUse下使用location查找所有资源
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
//new一个ArrayList备用
List<String> importCandidates = new ArrayList<>();
//遍历urls文件集合
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
//将遍历到的每一个元素加入到importCandidates中
importCandidates.addAll(readCandidateConfigurations(url));
}
//封装已经读取完的importCandidates成ImportCandidates并返回。
return new ImportCandidates(importCandidates);
}

}
  1. 利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
  2. 调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
  3. 利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件

(新版本是通过List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();)来得到所有组件
4. 从META-INF/spring.factories位置来加载一个文件。
- 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
- spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
- (新版本是扫描”META-INF/spring/%s.imports”位置的注解并加入到Arraylist里导入)

properties
1
2
3
4
5
6
7
# 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
# spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
...

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

AopAutoConfiguration类:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = "auto",
havingValue = "true",
matchIfMissing = true
)
public class AopAutoConfiguration {
public AopAutoConfiguration() {
}
...
}

15、自动配置【源码分析】-自动配置流程

DispatcherServletAutoConfiguration的内部类DispatcherServletConfiguration为例子:

java
1
2
3
4
5
6
7
8
9
@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;//给容器中加入了文件上传解析器;
}

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

总结

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

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

16、最佳实践-SpringBoot应用如何编写

  • 引入场景依赖
  • 查看自动配置了哪些(选做)
    • 自己分析,引入场景对应的自动配置一般都生效了
    • 配置文件中debug=true开启自动配置报告。
      • Negative(不生效)
      • Positive(生效)
  • 是否需要修改
    • 参照文档修改配置项
      • 官方文档
      • 自己分析。xxxxProperties绑定了配置文件的哪些。
    • 自定义加入或者替换组件
      • @Bean、@Component…
    • 自定义器 XXXXXCustomizer;
    • ……

17、最佳实践-Lombok简化开发

Lombok用标签方式代替构造器、getter/setter、toString()等鸡肋代码。

spring boot已经管理Lombok。引入依赖:

xml
1
2
3
4
 <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

IDEA中File->Settings->Plugins,搜索安装Lombok插件。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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;
}
}

简化日志开发

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

18、最佳实践-dev-tools

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.——link

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 application.——link

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.

添加依赖:

xml
1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

在IDEA中,项目或者页面修改以后:Ctrl+F9。

19、最佳实践-Spring Initailizr

Spring Initailizr是创建Spring Boot工程向导。

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

20、配置文件-yaml的用法

同以前的properties用法

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

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

基本语法

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • ‘#’表示注释
  • 字符串无需加引号,如果要加,单引号’’、双引号””表示字符串内容会被 转义、不转义

数据类型

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
yaml
1
k: v
  • 对象:键值对的集合。map、hash、set、object
yaml
1
2
3
4
5
6
7
8
9
10
#行内写法:  

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

#或

k:
k1: v1
k2: v2
k3: v3
  • 数组:一组按次序排列的值。array、list、queue
yaml
1
2
3
4
5
6
7
8
9
10
#行内写法:  

k: [v1,v2,v3]

#或者

k:
- v1
- v2
- v3

实例

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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表示以上对象

yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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}
health: [{name: mario,weight: 47}]

21、配置文件-自定义类绑定的配置提示

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.——link

自定义的类和配置文件绑定一般没有提示。若要提示,添加如下依赖:

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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>

22、web场景-web开发简介

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)

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 document)).

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

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

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

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

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

    • 自定义 Favicon
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

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

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (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

23、web场景-静态资源规则与定制化

静态资源目录

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

访问 : 当前项目根路径/ + 静态资源名

原理: 静态映射/**。

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面。

也可以改变默认的静态资源路径,/static/public,/resources, /META-INF/resources失效

yaml
1
2
resources:
static-locations: [classpath:/haha/]

静态资源访问前缀

yaml
1
2
3
spring:
mvc:
static-path-pattern: /res/**

当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

webjar

可用jar方式添加css,js等资源文件,

https://www.webjars.org/

例如,添加jquery

xml
1
2
3
4
5
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>

访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径。

24、web场景-welcome与favicon功能

官方文档

欢迎页支持

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

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

自定义Favicon

指网页标签上的小图标。

favicon.ico 放在静态资源目录下即可。

yaml
1
2
3
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致 Favicon 功能失效

25、web场景-【源码分析】-静态资源原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类WebMvcAutoConfiguration,生效
java
1
2
3
4
5
6
7
8
9
10
@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 {
...
}
  • 给容器中配置的内容:
  • 通过WebMvcAutoConfiguration类中的 WebMvcAutoConfigurationAdapter方法进行配置绑定
    • 配置文件的相关属性的绑定:WebMvcProperties == spring.mvc、ResourceProperties == spring.resources
java
1
2
3
4
5
6
7
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
...
}

配置类只有一个有参构造器

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
public class WebMvcAutoConfiguration {
////有参构造器所有参数的值都会从容器中确定
...
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();
}
...
}
  • ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
  • WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
  • ListableBeanFactory beanFactory Spring的beanFactory
  • HttpMessageConverters 找到所有的HttpMessageConverters
  • ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。
  • DispatcherServletPath
  • ServletRegistrationBean 给应用注册Servlet、Filter….

资源处理的默认规则(EnableWebMvcConfiguration方法)

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
public class WebMvcAutoConfiguration {
...
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
...
@Override//添加资源处理器
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
//默认addMappings = true;如果改为false,则方法报告日志并返回。静态资源处理被禁止
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
ServletContext servletContext = getServletContext();
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (servletContext != null) {
registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
}
});
}
...

}
...
}

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

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

静态资源规则:
``addResourceHandler(registry, “/webjars/**”, “classpath:/META-INF/resources/webjars/“);
//这句是添加webjar的资源处理规则

``addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {registration.addResourceLocations(this.resourceProperties.getStaticLocations());
//这句是添加spring的资源处理规则
由于resourceProperties.getStaticLocations()方法返回的是staticLocations,
staticLocations = CLASSPATH_RESOURCE_LOCATIONS,
CLASSPATH_RESOURCE_LOCATIONS = new String[]{“classpath:/META-INF/resources/“, “classpath:/resources/“, “classpath:/static/“, “classpath:/public/“};
所以静态资源的类路径可为上面4个。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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;
...
}

欢迎页的处理规则(WelcomePageHandlerMapping()方法)

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
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;
}
}
}

WelcomePageHandlerMapping的构造方法如下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
if (welcomePage != null && "/**".equals(staticPathPattern)) {
//要用欢迎页功能,必须是/** 默认情况下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");
}
}

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

26、请求处理-【源码分析】-Rest映射及源码解析

请求映射

  • @xxxMapping;

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

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

    • 开启页面表单的Rest功能
    • 页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)
    • 编写请求映射
yaml
1
2
3
4
5
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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>
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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
    • 请求过来被HiddenHttpMethodFilter拦截
      • 请求是否正常,并且是POST
        • 获取到\_method的值。
        • 兼容以下请求;PUT.DELETE.PATCH
        • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
        • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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}. */
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
*/
//methodParam有set属性,所以支持自定义methodParam的值。可以不使用默认的。
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且没有异常属性则进入
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
//methodParam=DEFAULT_METHOD_PARAM= "_method"
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
//将_method属性的值转化为大写
String method = paramValue.toUpperCase(Locale.ENGLISH);
//ALLOWED_METHODS是个arraylist,包含PUT、DELETE、PATCH方法
if (ALLOWED_METHODS.contains(method)) {
//requestToUse=request,使用包装类包装了request,并重写了请求方式method,
//将method改为PUT、DELETE、PATCH,并返回。
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
//放行,返回的是requestToUser,所以以后的method调用的就是重写的method。
filterChain.doFilter(requestToUse, response);
}


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

private final String method;

public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}

@Override
public String getMethod() {
return this.method;
}
}

}
  • Rest使用客户端工具。
    • 如PostMan可直接发送put、delete等方式请求。

27、请求处理-【源码分析】-怎么改变默认的_method

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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()。(如果你没配置HiddenHttpMethodFilter,springboot就会帮你配置默认的)。因此,为了不使用默认的_method,我们可以自定义filter,改变默认的_method的值。例如:

java
1
2
3
4
5
6
7
8
9
10
11
@Configuration(proxyBeanMethods = false)
public class WebConfig{
//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
//设置methodParam的值为"_m"
methodFilter.setMethodParam("_m");
return methodFilter;
}
}

_method改成_m

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

28、请求处理-【源码分析】-请求映射原理

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

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//将请求赋值给processedRequest
HttpServletRequest processedRequest = request;
//HandlerExecutionChain有三个关键成员属性:
//处理器handler、拦截器列表interceptorList、拦截器索引interceptorIndex
HandlerExecutionChain mappedHandler = null;
//是否是文件上传解析请求,为否
boolean multipartRequestParsed = false;
//判断是否是异步请求,是的话采用异步管理类。
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
//创建空初始化的modelandview备用
ModelAndView mv = null;
Exception dispatchException = null;

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

// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = getHandler(processedRequest);
//并将handler包装成HandlerExecutionChain mappedHandler


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

mappedHandler=getHandleer(processedRequest)找到能处理当前请求的处理器
getHandler()方法如下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
//遍历所有的handlerMapping,找到能处理请求该请求的handler
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

总结就是使用增强for循环在所有handlerMappings中找到能处理当前请求的handlerMapping
即找到处理当前请求的映射处理器!
this.handlerMappings在Debug模式下展现的内容:

在这里插入图片描述
RequestMappingHandlerMapping处理的是@RequestMapping和handler的映射规则。(RequestMappingHandlerMapping下的MappingRegistry的mappingLookup下有我们自己有我们自己注册的controller方法和系统给我们注册的error处理的一些方法。)
WelcomePageHandlerMapping处理的是根路径访问即”/“和handler的映射规则。一般springboot帮我们配置ParameterizableViewController这个handler来处理。这就是我们访问”/“能访问到index.html的原因。即springboot添加了 springmvc的首页视图控制器方法。

其中,保存了所有@RequestMappinghandler的映射规则。

在这里插入图片描述

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

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

  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping

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

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

上述获取handler补充:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
//遍历所有的handlerMapping,找到能处理请求该请求的handler
//进入该方法
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
	@Nullable  
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//进入该方法
Object handler = this.getHandlerInternal(request);
if (handler == null) {
handler = this.getDefaultHandler();
}

if (handler == null) {
return null;
} else {
if (handler instanceof String) {
String handlerName = (String)handler;
handler = this.obtainApplicationContext().getBean(handlerName);
}

if (!ServletRequestPathUtils.hasCachedPath(request)) {
this.initLookupPath(request);
}

HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Mapped to " + handler);
} else if (this.logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
this.logger.debug("Mapped to " + executionChain.getHandler());
}

if (this.hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = this.getCorsConfiguration(handler, request);
if (this.getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = this.getCorsConfigurationSource().getCorsConfiguration(request);
config = globalConfig != null ? globalConfig.combine(config) : config;
}

if (config != null) {
config.validateAllowCredentials();
}

executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
}

return executionChain;
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
@Nullable  
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

HandlerMethod var2;
try {
//进入该方法
var2 = super.getHandlerInternal(request);
} finally {
ProducesRequestCondition.clearMediaTypesAttribute(request);
}
return var2;
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Nullable  
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = this.initLookupPath(request);
this.mappingRegistry.acquireReadLock();

HandlerMethod var4;
try {
//进入该方法
HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;
} finally {
this.mappingRegistry.releaseReadLock();
}
return var4;
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {  
List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();
//根据请求路径将匹配的所有请求添加到List集合directPathMatches中
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
//将集合directPathMatches添加到addMatchingMappings
this.addMatchingMappings(directPathMatches, matches, request);
}

if (matches.isEmpty()) { this.addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}

if (matches.isEmpty()) {
return this.handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
} else {
//这里matches会根据请求方式筛选出最匹配的的请求处理方法
AbstractHandlerMethodMapping<T>.Match bestMatch = (Match)matches.get(0);
//如果有多个匹配方法,排序
if (matches.size() > 1) {
Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new MatchComparator(this.getMappingComparator(request));
matches.sort(comparator);
bestMatch = (Match)matches.get(0);
if (this.logger.isTraceEnabled()) {
Log var10000 = this.logger;
int var10001 = matches.size();
var10000.trace("" + var10001 + " matching mappings: " + matches);
}

if (CorsUtils.isPreFlightRequest(request)) {
Iterator var7 = matches.iterator();

while(var7.hasNext()) {
AbstractHandlerMethodMapping<T>.Match match = (Match)var7.next();
if (match.hasCorsConfig()) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
}
} else {
AbstractHandlerMethodMapping<T>.Match secondBestMatch = (Match)matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.getHandlerMethod().getMethod();
Method m2 = secondBestMatch.getHandlerMethod().getMethod();
String uri = request.getRequestURI();
//相同路径且相同请求方式有两个匹配方法的话,报错,内部也不知道调用那个请求方法
throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
}

request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
this.handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.getHandlerMethod();
}
}

IDEA快捷键:

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

29、请求处理-常用参数注解使用

注解:

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

使用用例:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@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,
@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;
}
}

30、请求处理-@RequestAttribute

用例:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@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");
request.setAttribute("message","HelloWorld");

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

31、请求处理-@MatrixVariable与UrlPathHelper

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

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

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

手动开启矩阵变量

  • 实现WebMvcConfigurer接口:
java
1
2
3
4
5
6
7
8
9
10
11
@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:
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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的用例

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@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;

}

}

一个完整的前端控制器DispatcherServlet处理客户端请求的完整过程

32、请求处理-【源码分析】-各种类型参数解析原理

这要从DispatcherServlet开始说起:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);

// Determine handler for the current request.
//在handlerMappings里遍历找到处理器并包装成处理器执行链返回
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {  
if (this.handlerAdapters != null) {
//创建迭代器
Iterator var2 = this.handlerAdapters.iterator();

while(var2.hasNext()) {
//遍历所有adapter
HandlerAdapter adapter = (HandlerAdapter)var2.next();
//找到支持该处理方法的adapter
if (adapter.supports(handler)) {
//返回该adapter
return adapter;
}
}
}

throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
  • HandlerMapping中找到能处理请求的Handler(Controller.method())。
  • 为当前Handler 找一个适配器 HandlerAdapter(通过反射来获取请求参数和调用方法),用的最多的是RequestMappingHandlerAdapter。底层也是基于反射确定请求方法的参数值并调用方法。
  • 适配器执行目标方法并确定方法参数的每一个值。

HandlerAdapter

默认会加载所有HandlerAdapter

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
}
}
...

有这些HandlerAdapter

在这里插入图片描述

  1. 支持方法上标注@RequestMapping

  2. 支持函数式编程的

执行目标方法

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DispatcherServlet extends FrameworkServlet {

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

...

// 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());

HandlerAdapter接口实现类RequestMappingHandlerAdapter(主要用来处理@RequestMapping

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 {
//创建mav备用
ModelAndView mav;
//handleInternal的核心 执行处理方法并返回mav
mav = invokeHandlerMethod(request, response, handlerMethod);//解释看下节
//...
return mav;
}
}

参数解析器

确定将要执行的目标方法的每一个参数的值是什么;
参数解析器就是来解析处理请求方法的参数的值

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

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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就是handlerMethod
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}

...

参数解析器具体有27种。。。所以我们能写27中请求参数

this.argumentResolversafterPropertiesSet()方法内初始化

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {

@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;

@Override
public void afterPropertiesSet() {
...
if (this.argumentResolvers == null) {//初始化argumentResolvers
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
...
}

//初始化了一堆的实现HandlerMethodArgumentResolver接口的
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());

// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
if (KotlinDetector.isKotlinPresent()) {
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
}

// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}

// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));

return resolvers;
}

}

HandlerMethodArgumentResolverComposite类如下:(众多参数解析器argumentResolvers的包装类)。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {

private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();

...

public HandlerMethodArgumentResolverComposite addResolvers(
@Nullable HandlerMethodArgumentResolver... resolvers) {

if (resolvers != null) {
Collections.addAll(this.argumentResolvers, resolvers);
}
return this;
}

...
}

我们看看HandlerMethodArgumentResolver的源码:

java
1
2
3
4
5
6
7
8
9
10
11
public interface HandlerMethodArgumentResolver {

//当前解析器是否支持解析这种参数
boolean supportsParameter(MethodParameter parameter);

@Nullable//如果支持,就调用 resolveArgument
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

返回值处理器

ValueHandler

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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);
}
...

返回值处理器有15种。。。所以我们能返回15种返回值
Alt text

this.returnValueHandlersafterPropertiesSet()方法内初始化

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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类如下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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接口:

java
1
2
3
4
5
6
7
8
public interface HandlerMethodReturnValueHandler {

boolean supportsReturnType(MethodParameter returnType);

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

}

回顾执行目标方法

java
1
2
3
4
5
6
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()方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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进行了参数解析器和返回值处理器等其他封装之后
//关注点:执行目标方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}

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

invokeAndHandle()方法如下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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);
}
//通过反射调用controller方法
return method.invoke(getBean(), args);//getBean()指@RequestMapping的方法所在类的对象。
}
catch (IllegalArgumentException ex) {
...
}
catch (InvocationTargetException ex) {
...
}
}

}

如何确定目标方法每一个参数的值

重点分析ServletInvocableHandlerMethodgetMethodArgumentValues方法

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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 {
//获取controller方法上的参数
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
//如果为空,返回EMPTY_ARGS,即无需确定参数的值
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(在参数解析器章节提及)

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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;
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Nullable  
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//获取注解里参数的名字
NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
} else {
//在这一步里解析该参数的值,使用urlPathHelper传入的值与参数对应上,并赋值
//进入该方法
Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
} else if (namedValueInfo.required && !nestedParameter.isOptional()) {
this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}

arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
} else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}

if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);

try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
} catch (ConversionNotSupportedException var11) {
throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
} catch (TypeMismatchException var12) {
throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
}

if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) {
this.handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
}
}

this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
}
java
1
2
3
4
5
6
7
@Nullable  
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
//在urlpathhelper里保存了参数的值
Map<String, String> uriTemplateVars = (Map)request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, 0);
//这是个三元表达式,如果保存参数map的值不为空,根据名字或者参数值,否就返回null.至此,一个参数的值得完整确定到此结束。有多个参数则在InvocableHandlerMethod类中逐个循环参数解析器,逐个解析参数即可。
return uriTemplateVars != null ? uriTemplateVars.get(name) : null;
}

小结

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

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

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

33、请求处理-【源码分析】-Servlet API参数解析原理

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

ServletRequestMethodArgumentResolver用来处理以上的参数

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {

@Nullable
private static Class<?> pushBuilder;

static {
try {
pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder",
ServletRequestMethodArgumentResolver.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// Servlet 4.0 PushBuilder not found - not supported for injection
pushBuilder = null;
}
}


@Override//判断传入的请球参数类型是不是Servlet API参数类型的,有12种
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) && !parameter.hasParameterAnnotations()) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}

@Override//判断Servlet API参数类型
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));
}
//拿到原生的请求,并返回原生的请求
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;
}

@Nullable//判断Servlet API参数类型
private Object resolveArgument(Class<?> paramType, HttpServletRequest request) throws IOException {
if (HttpSession.class.isAssignableFrom(paramType)) {
HttpSession session = request.getSession();
if (session != null && !paramType.isInstance(session)) {
throw new IllegalStateException(
"Current session is not of type [" + paramType.getName() + "]: " + session);
}
return session;
}
else if (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) {
return PushBuilderDelegate.resolvePushBuilder(request, paramType);
}
else if (InputStream.class.isAssignableFrom(paramType)) {
InputStream inputStream = request.getInputStream();
if (inputStream != null && !paramType.isInstance(inputStream)) {
throw new IllegalStateException(
"Request input stream is not of type [" + paramType.getName() + "]: " + inputStream);
}
return inputStream;
}
else if (Reader.class.isAssignableFrom(paramType)) {
Reader reader = request.getReader();
if (reader != null && !paramType.isInstance(reader)) {
throw new IllegalStateException(
"Request body reader is not of type [" + paramType.getName() + "]: " + reader);
}
return reader;
}
else if (Principal.class.isAssignableFrom(paramType)) {
Principal userPrincipal = request.getUserPrincipal();
if (userPrincipal != null && !paramType.isInstance(userPrincipal)) {
throw new IllegalStateException(
"Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal);
}
return userPrincipal;
}
else if (HttpMethod.class == paramType) {
return HttpMethod.resolve(request.getMethod());
}
else if (Locale.class == paramType) {
return RequestContextUtils.getLocale(request);
}
else if (TimeZone.class == paramType) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone : TimeZone.getDefault());
}
else if (ZoneId.class == paramType) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault());
}

// Should never happen...
throw new UnsupportedOperationException("Unknown parameter type: " + paramType.getName());
}


/**
* Inner class to avoid a hard dependency on Servlet API 4.0 at runtime.
*/
private static class PushBuilderDelegate {

@Nullable
public static Object resolvePushBuilder(HttpServletRequest request, Class<?> paramType) {
PushBuilder pushBuilder = request.newPushBuilder();
if (pushBuilder != null && !paramType.isInstance(pushBuilder)) {
throw new IllegalStateException(
"Current push builder is not of type [" + paramType.getName() + "]: " + pushBuilder);
}
return pushBuilder;

}
}
}

用例:

java
1
2
3
4
5
6
7
8
9
10
11
@Controller
public class RequestController {

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

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

34、请求处理-【源码分析】-Model、Map原理

复杂参数:

  • Map

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

  • Errors/BindingResult

  • RedirectAttributes( 重定向携带数据)

  • ServletResponse(response)

  • SessionStatus

  • UriComponentsBuilder

  • ServletUriComponentsBuilder

用例:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@GetMapping("/params")
public String testParam(Map<String,Object> map,
Model model,
HttpServletRequest request,
HttpServletResponse response){
//下面三位都是可以给request域中放数据
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 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");//得出testParam方法赋予的值 world666
Object world = request.getAttribute("world");//得出testParam方法赋予的值 hello666
Object message = request.getAttribute("message");//得出testParam方法赋予的值 HelloWorld

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

return map;
}
  • Map<String,Object> map

  • Model model

  • HttpServletRequest request

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

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


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

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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");
//进入mavContainer.getModel()方法
return mavContainer.getModel();
}

...

}

mavContainer.getModel()如下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ModelAndViewContainer {

...

private final ModelMap defaultModel = new BindingAwareModelMap();

@Nullable
private ModelMap redirectModel;

...

public ModelMap getModel() {
if (useDefaultModel()) {
//这里retreturn this.defaultModel;
//由于defaultModel = new BindingAwareModelMap();
//所以返回的是new BindingAwareModelMap();
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));
}
...

}

Model modelModelMethodProcessor处理:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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");
return mavContainer.getModel();
}
...
}

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

在这里插入图片描述

可以得出model和map返回的都是BindingAwareModelMap对象,BindingAwareModelMap既是model也是map
Model也是另一种意义的Map

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {  
//这一步是执行请求的方法,里面会获取所有请求参数并确定参数的值,然后跳到doinvoke(args)方法——>即controller方法里执行完该方法,获取方法的返回值并赋给returnValue
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
//执行完controller方法后会返回到这里
//这一步是设置响应状态码 200 404 500等来源
this.setResponseStatus(webRequest);
if (returnValue == null) {
if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
this.disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
} else if (StringUtils.hasText(this.getResponseStatusReason())) {

mavContainer.setRequestHandled(true);
return;
}

mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");

try {
//这一步进行处理返回结果,进入该方法
this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
} catch (Exception var6) {
if (logger.isTraceEnabled()) {
logger.trace(this.formatErrorForReturnValue(returnValue), var6);
}

throw var6;
}
}

第一步会先获取返回结果的类型

java
1
2
3
public MethodParameter getReturnValueType(@Nullable Object returnValue) {  
return new ReturnValueMethodParameter(returnValue);
}

第二步

java
1
2
3
4
5
6
7
8
9
10
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {  
//找到返回值得处理器
HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
} else {
//处理返回值的核心步骤 进入该方法
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {  
//只有两种判断,要么转发,要么重定向。
//判断返回值是否是字符串
if (returnValue instanceof CharSequence) {
//返回返回值的tostring值作为视图名称
String viewName = returnValue.toString();
//设置mavContainer的视图名称
mavContainer.setViewName(viewName);
//判断返回值是否是重定向视图名称
if (this.isRedirectViewName(viewName)) {
///设置mavContainer的重定向视图名称
mavContainer.setRedirectModelScenario(true);
}
} else if (returnValue != null) {
String var10002 = returnType.getParameterType().getName();
throw new UnsupportedOperationException("Unexpected return type: " + var10002 + " in method: " + returnType.getMethod());
}

}

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

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

先看ModelAndView接下来是如何处理的?

当controller方法执行完毕后悔返回到invokeHandlerMethod中兵返回mav对象(已经封装成mavContainer)

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@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);
}

...
//这一步是调用并处理请求
//传入封装的请求webRequest和封装的视图模型容器mavContainer
invocableMethod.invokeAndHandle(webRequest, mavContainer);//看下块代码
//三元表达式,异步请求管理,是否是并发处理?是的话返回null.
//由于这里不是异步请求,所以是否,返回getModelAndView()方法
return asyncManager.isConcurrentHandlingStarted() ? null : this.getModelAndView(mavContainer, modelFactory, webRequest);
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class DispatcherServlet extends FrameworkServlet {

...

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...

try {
ModelAndView mv = null;

...

// Actually invoke the handler.
//执行完毕后会返回这里,拿到mav,接下来准备进行mav的结果分发
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

...

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

}

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
...

// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//mav=mv,如果不为空则进行渲染 进入该方法
render(mv, request, response);
...
}
...
}


protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
...

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() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
//这一步进行视图渲染
view.render(mv.getModelInternal(), request, response);

...
}

}

进入该方法第一步获取model模型

java
1
2
3
4
@Nullable  
protected Map<String, Object> getModelInternal() {
return this.model;
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Nullable  
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
//创建迭代器进行迭代
Iterator var5 = this.viewResolvers.iterator();

while(var5.hasNext()) {
//遍历视图解析器
ViewResolver viewResolver = (ViewResolver)var5.next();
//进入该方法
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Nullable  
public View resolveViewName(String viewName, Locale locale) throws Exception {
//在此拿到请求域中的所有属性
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
//获取request可以接收的mediaType媒体类型
List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
//获取候选视图集合
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
//根据requestedMediaTypes获取最佳视图
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
//返回最佳视图
return bestView;
}
}

String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
//媒体类型不支持时报错
return NOT_ACCEPTABLE_VIEW;
} else {
//媒体类型未处理是报日志
this.logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}

到此,视图解析到一段落

回到view.render(mv.getModelInternal(), request, response);这个方法内部,进行视图渲染
就如此方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {  
if (this.logger.isDebugEnabled()) {
Log var10000 = this.logger;
String var10001 = this.formatViewName();
var10000.debug("View " + var10001 + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
//创建一个融合输出模型,整合model资源进入mergedModel,进入该方法!!!
Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response);
//准备响应结果
this.prepareResponse(request, response);
//渲染融合输出模型 关键核心!!!
this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) { 
//三元表达式获取路径变量
Map<String, Object> pathVars = this.exposePathVariables ? (Map)request.getAttribute(View.PATH_VARIABLES) : null;
int size = this.staticAttributes.size();
size += model != null ? model.size() : 0;
size += pathVars != null ? pathVars.size() : 0;
//mergeModel原来是个LinkedHashMap
Map<String, Object> mergedModel = CollectionUtils.newLinkedHashMap(size);
mergedModel.putAll(this.staticAttributes);
if (pathVars != null) {
//转移路径变量进mergeModel里
mergedModel.putAll(pathVars);
}

if (model != null) {
//如果model不为空,将model的资源转移进mergeModel
mergedModel.putAll(model);
}

if (this.requestContextAttribute != null) {
//转移请求上下属性进mergeModel
mergedModel.put(this.requestContextAttribute, this.createRequestContext(request, response, mergedModel));
}

return mergedModel;
}

上述核心渲染步骤!!!进入该方法:

java
1
this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);  

第一步:

java
1
2
3
4
5
6
7
8
9
10
protected HttpServletRequest getRequestToExpose(HttpServletRequest originalRequest) {  
if (!this.exposeContextBeansAsAttributes && this.exposedContextBeanNames == null) {
//返回原生请求对象
return originalRequest;
} else {
WebApplicationContext wac = this.getWebApplicationContext();
Assert.state(wac != null, "No WebApplicationContext");
return new ContextExposingHttpServletRequest(originalRequest, wac, this.exposedContextBeanNames);
}
}

第二步:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {  
//暴露模型作为请求域的属性,里面就是遍历model,逐一进行request.setAttribute
//即也可以理解为将模型和请求绑定
this.exposeModelAsRequestAttributes(model, request);
this.exposeHelpers(request);
String dispatcherPath = this.prepareForRendering(request, response);
RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + this.getUrl() + "]: Check that the corresponding file exists within your web application archive!");
} else {
if (this.useInclude(request, response)) {
response.setContentType(this.getContentType());
if (this.logger.isDebugEnabled()) {
this.logger.debug("Including [" + this.getUrl() + "]");
}

rd.include(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Forwarding to [" + this.getUrl() + "]");
}

rd.forward(request, response);
}

}
}

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

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class InternalResourceView extends AbstractUrlBasedView {

@Override//该方法在AbstractView,AbstractUrlBasedView继承了AbstractView
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {

...

Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);

//看下一个方法实现
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

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

// Expose the model object as request attributes.
// 暴露模型作为请求域属性
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);

...
}

//该方法在AbstractView,AbstractUrlBasedView继承了AbstractView
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);
}
});
}

}

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

35、请求处理-【源码分析】-自定义参数绑定原理

java
1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class ParameterTestController {

/**
* 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
* @param person
* @return
*/
@PostMapping("/saveuser")
public Person saveuser(Person person){
return person;
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 姓名: <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;

}

封装过程用到ServletModelAttributeMethodProcessor

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {

@Override//本方法在ModelAttributeMethodProcessor类,
public boolean supportsParameter(MethodParameter parameter) {
//有没有添加模型属性参数注解标记(无) ||这个注解标记不是必需的(真) && !是否是简单配置(真) 即 假 || 真 && 真==真
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired &&
!BeanUtils.isSimpleProperty(parameter.getParameterType())));
}


@Override//上述方法过后获取到ServletModelAttributeMethodProcessor这个resolver
@Nullable//本方法在ModelAttributeMethodProcessor类,
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

...
//获取传入参数的名字。
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
//如果有ModelAttribute注解标记,将该注解和传入的参数绑定
mavContainer.setBinding(name, ann.binding());
}

Object attribute = null;
BindingResult bindingResult = null;
//如果这个参数不是一手自己创建的,就还会从页面获取参数的属性
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) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
//这一步最关键,标志着将请求携带的数据封装到空属性实例中,使用绑定器binder封装,binder里面有124种转换器converters,可以讲http的文本转换为java的很多类型
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
//即给封装的binder对象的属性赋值,关键点!!!
//web数据绑定器,将请求参数的值绑定到指定的JavaBean里面**
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}

// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);

return attribute;
}
}

converters:124种

我们来研究一下!BeanUtils.isSimpleProperty(parameter.getParameterType())这个判断
加入该方法:
第一步:获取参数类型

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Class<?> getParameterType() {  
Class<?> paramType = this.parameterType;
if (paramType != null) {
//参数不为空即返回参数类型
return paramType;
} else {
if (this.getContainingClass() != this.getDeclaringClass()) {
paramType = ResolvableType.forMethodParameter(this, (Type)null, 1).resolve();
}

if (paramType == null) {
paramType = this.computeParameterType();
}

this.parameterType = paramType;
return paramType;
}
}

第二步:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static boolean isSimpleProperty(Class<?> type) {  
//类型必需不为空
Assert.notNull(type, "'type' must not be null");
//是否是简单数值类型(假) || 是否为数组(假) && 是否是简单数值类型(假)
//都为假,所以参数类型不是以上三种,也就意味着这个判断就是判断是否是简单参数的
return isSimpleValueType(type) || type.isArray() &&
isSimpleValueType(type.getComponentType()); //进入最后这个判断看看
}

//判断传入的类型是否为一下任意一种,结果都不是。所以传入的类型是自定义类型。
public static boolean isSimpleValueType(Class<?> type) {
return Void.class != type && Void.TYPE != 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);
}

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

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

我们在研究一下关键点方法bindRequestParameters(binder, webRequest)
进入该方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
//先获取到原生的servletReques
ServletRequest servletRequest = (ServletRequest)request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
//将封装的binder对象转换为ServletRequestDataBinder类型
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder)binder;
//将原生请求携带的数据和servletBinder对象进行绑定
//进入该方法!
servletBinder.bind(servletRequest);
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ServletRequestDataBinder extends WebDataBinder {
public void bind(ServletRequest request) {
//获取到所有可变属性(获取到的都是键值对,如name="age" value="18")
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
//获取上传文件请求,判断是否是文件上传请求
MultipartRequest multipartRequest = (MultipartRequest)WebUtils.getNativeRequest(request, MultipartRequest.class);
//当前不是文件上传请求跳过这部分
if (multipartRequest != null) {
this.bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
} else if (StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/form-data")) {
HttpServletRequest httpServletRequest = (HttpServletRequest)WebUtils.getNativeRequest(request, HttpServletRequest.class);
if (httpServletRequest != null && HttpMethod.POST.matches(httpServletRequest.getMethod())) {
StandardServletPartUtils.bindParts(httpServletRequest, mpvs, this.isBindEmptyMultipartFiles());
}
}
//添加绑定数值 该方法里面会获取请求路径中的属性变量和属性变量的值封装到mpvs里,如果请求路径中没有传,就无事发生
this.addBindValues(mpvs, request);
//这里才是真正执行绑定 进入该方法!
this.doBind(mpvs);
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class WebDataBinder extends DataBinder { // 此字段意思是:字段标记 比如name -> _name // 这对于HTML复选框和选择选项特别有用。 
public static final String DEFAULT_FIELD_MARKER_PREFIX = "_"; // !符号是处理默认值的,提供一个默认值代替空值~~~
public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";
@Nullable
private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
@Nullable
private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;
...
protected void doBind(MutablePropertyValues mpvs) {
this.checkFieldDefaults(mpvs); //检查对!的处理
this.checkFieldMarkers(mpvs); //检查对_的处理
this.adaptEmptyArrayIndices(mpvs); //检查对空[]的处理
super.doBind(mpvs); //最终调用父类的dobind()方法,进入该方法
}
...
}
java
1
2
3
4
5
6
7
8
9
10
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
...
protected void doBind(MutablePropertyValues mpvs) {
this.checkAllowedFields(mpvs);
this.checkRequiredFields(mpvs);
//将请求参数绑定到目标对象上。进入该方法
this.applyPropertyValues(mpvs);
}
...
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
...
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
//进入该方法 设置属性值的核心方法
this.getPropertyAccessor().setPropertyValues(mpvs, this.isIgnoreUnknownFields(), this.isIgnoreInvalidFields());
} catch (PropertyBatchUpdateException var7) {
PropertyAccessException[] var3 = var7.getPropertyAccessExceptions();
int var4 = var3.length;

for(int var5 = 0; var5 < var4; ++var5) {
PropertyAccessException pae = var3[var5];
this.getBindingErrorProcessor().processPropertyAccessException(pae, this.getInternalBindingResult());
}
}

}
...
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException {  
List<PropertyAccessException> propertyAccessExceptions = null;
List var10000;
//在这里拿到所有的属性值
if (pvs instanceof MutablePropertyValues mpvs) {
var10000 = mpvs.getPropertyValueList();
} else {
var10000 = Arrays.asList(pvs.getPropertyValues());
}
//将属性值封装进propertyValues集合里
List<PropertyValue> propertyValues = var10000;
if (ignoreUnknown) {
this.suppressNotWritablePropertyException = true;
}

try {
//创建迭代器进行遍历属性
Iterator var18 = propertyValues.iterator();
//遍历属性
while(var18.hasNext()) {
PropertyValue pv = (PropertyValue)var18.next();

try {
//设置该属性的值 进入该方法看看他是怎么设置的
this.setPropertyValue(pv);
} catch (NotWritablePropertyException var14) {
if (!ignoreUnknown) {
throw var14;
}
} catch (NullValueInNestedPathException var15) {
if (!ignoreInvalid) {
throw var15;
}
} catch (PropertyAccessException var16) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new ArrayList();
}

propertyAccessExceptions.add(var16);
}
}
} finally {
if (ignoreUnknown) {
this.suppressNotWritablePropertyException = false;
}

}

if (propertyAccessExceptions != null) {
PropertyAccessException[] paeArray = (PropertyAccessException[])propertyAccessExceptions.toArray(new PropertyAccessException[0]);
throw new PropertyBatchUpdateException(paeArray);
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
...
public void setPropertyValue(PropertyValue pv) throws BeansException {
PropertyTokenHolder tokens = (PropertyTokenHolder)pv.resolvedTokens;
if (tokens == null) {
String propertyName = pv.getName();
//创建属性访问器
AbstractNestablePropertyAccessor nestedPa;
try {
//这一步是通过反射工具属性访问器PropertyAccessor获取属性的路径
nestedPa = this.getPropertyAccessorForPropertyPath(propertyName);
} catch (NotReadablePropertyException var6) {
throw new NotWritablePropertyException(this.getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", var6);
}

tokens = this.getPropertyNameTokens(this.getFinalPath(nestedPa, propertyName));
if (nestedPa == this) {
pv.getOriginalPropertyValue().resolvedTokens = tokens;
}
//这一步里面进行赋值 进入该方法
nestedPa.setPropertyValue(tokens, pv);
} else {
this.setPropertyValue(tokens, pv);
}

}
...//进入nestedPa.setPropertyValue(tokens, pv)方法里
protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
if (tokens.keys != null) { //此处令牌为空
this.processKeyedProperty(tokens, pv);
} else {
//进入这个方法
this.processLocalProperty(tokens, pv);
}

}
...
//进入processLocalProperty(tokens, pv)这个方法
private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
//获取属性处理器ph
PropertyHandler ph = this.getLocalPropertyHandler(tokens.actualName);
//如果ph不为空且属性可写
if (ph != null && ph.isWritable()) {

Object oldValue = null;

PropertyChangeEvent propertyChangeEvent;
try {
//获取原值
Object originalValue = pv.getValue();
//把原来的值封装进valueToApply
Object valueToApply = originalValue;
if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
if (pv.isConverted()) {
valueToApply = pv.getConvertedValue();
} else {
if (this.isExtractOldValueForEditor() && ph.isReadable()) {
try {
oldValue = ph.getValue();
} catch (Exception var9) {
Exception ex = var9;
if (var9 instanceof PrivilegedActionException) {
PrivilegedActionException pae = (PrivilegedActionException)var9;
ex = pae.getException();
}

if (logger.isDebugEnabled()) {
logger.debug("Could not read previous value of property '" + this.nestedPath + tokens.canonicalName + "'", ex);
}
}
}
//这一步将拿到的valueToApply的值转化为属性对应的java类型
//进入该方法
valueToApply = this.convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
}

pv.getOriginalPropertyValue().conversionNecessary = valueToApply != originalValue;
}
//之后转换成功的值在设置进属性中。
ph.setValue(valueToApply);
} catch (TypeMismatchException var10) {
throw var10;
} catch (InvocationTargetException var11) {
propertyChangeEvent = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
if (var11.getTargetException() instanceof ClassCastException) {
throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), var11.getTargetException());
} else {
Throwable cause = var11.getTargetException();
if (cause instanceof UndeclaredThrowableException) {
cause = cause.getCause();
}

throw new MethodInvocationException(propertyChangeEvent, cause);
}
} catch (Exception var12) {
propertyChangeEvent = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
throw new MethodInvocationException(propertyChangeEvent, var12);
}
} else if (pv.isOptional()) {
if (logger.isDebugEnabled()) {
String var10001 = tokens.actualName;
logger.debug("Ignoring optional value for property '" + var10001 + "' - property not found on bean class [" + this.getRootClass().getName() + "]");
}

} else if (!this.suppressNotWritablePropertyException) {
throw this.createNotWritablePropertyException(tokens.canonicalName);
}
}
}

第一步

java
1
2
3
4
5
6
public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {
//获取PropertyDescriptor(pd)的类型描述
public TypeDescriptor toTypeDescriptor() {
return new TypeDescriptor(BeanWrapperImpl.this.property(this.pd));
}
}

第二步

java
1
2
3
4
5
6
7
8
9
public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
...
@Nullable
protected Object convertForProperty(String propertyName, @Nullable Object oldValue, @Nullable Object newValue, TypeDescriptor td) throws TypeMismatchException {
//判断类型是否有需要转换
return this.convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
}
...
}

第三步

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
...
@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");

PropertyChangeEvent pce;
try {
//就是这一步进行类型转换,进入该方法
return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
} catch (IllegalStateException | ConverterNotFoundException var8) {
pce = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
throw new ConversionNotSupportedException(pce, requiredType, var8);
} catch (IllegalArgumentException | ConversionException var9) {
pce = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
throw new TypeMismatchException(pce, requiredType, var9);
}
}
...
}

第四步

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class TypeConverterDelegate {
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
//获取源数据类型描述——比如网页写的是年龄框里填18 传的就是字符串"18"
//而requiredType就是要转换成的数据类型
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
//判断能否转换的过程在这里,进入该方法,看看是怎么判断的
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
//进行转换
return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
} catch (ConversionFailedException var16) {
conversionAttemptEx = var16;
}
}
}
...
}
...
}

第五步

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class GenericConversionService implements ConfigurableConversionService {

public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType, "Target type to convert to cannot be null");
//源数据类型不为空
if (sourceType == null) {
return true;
} else {
//这一步里要获取converter转换器,看看他是怎么获取的,进入该方法
GenericConverter converter = this.getConverter(sourceType, targetType);
return converter != null;
}
}

...
@Nullable //进入上述getConverter方法后到这里
protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {

ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
//先从缓存中获取转换器,如果之间执行过这部操作,缓存终究会有能完成该类型转换的转换器。
GenericConverter converter = (GenericConverter)this.converterCache.get(key);
if (converter != null) {
return converter != NO_MATCH ? converter : null;
} else {
//如果上述缓存中没有找到converter,就要遍历124中转换器,找到能实现需求的转换器为止。我们进入该方法,看看他是怎么查找的。
converter = this.converters.find(sourceType, targetType);
if (converter == null) {
converter = this.getDefaultConverter(sourceType, targetType);
}

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

@Nullable //进入上述this.converters.find()方法后到这里
public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
//获取源数据类型的匹配的sourceCandidates
List<Class<?>> sourceCandidates = this.getClassHierarchy(sourceType.getType());
//获取目标数据类型匹配的targetCandidates
List<Class<?>> targetCandidates = this.getClassHierarchy(targetType.getType());
//创建迭代器以供遍历
Iterator var5 = sourceCandidates.iterator();
//遍历匹配sourceCandidates的converter
while(var5.hasNext()) {
Class<?> sourceCandidate = (Class)var5.next();
Iterator var7 = targetCandidates.iterator();
//遍历匹配targetCandidates的converter
while(var7.hasNext()) {
Class<?> targetCandidate = (Class)var7.next();
//创建一个新的source-to-target 转换匹配器
GenericConverter.ConvertiblePair convertiblePair = new GenericConverter.ConvertiblePair(sourceCandidate, targetCandidate);
//注册这个转换器
GenericConverter converter = this.getRegisteredConverter(sourceType, targetType, convertiblePair);
if (converter != null) {
// 找到能将sourceType转换为targetType的转换器并返回
return converter;
}
}
}

return null;
}
}

至此 ,if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) 方法执行结束。
接下来:执行return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor)方法
我们先进入这个方法:
第一步

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class GenericConversionService implements ConfigurableConversionService {
...
@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 this.handleResult((TypeDescriptor)null, targetType, this.convertNullSource((TypeDescriptor)null, targetType));
} else 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() + "]");
} else {
GenericConverter converter = this.getConverter(sourceType, targetType);
if (converter != null) {
//这里进行转换 进入该方法
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
return this.handleResult(sourceType, targetType, result);
} else {
return this.handleConverterNotFound(source, sourceType, targetType);
}
}
}
...
}

第二步

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class ConversionUtils {
...
@Nullable
public static Object invokeConverter(GenericConverter converter, @Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
try {
//调用converter的convert方法,进入该方法
return converter.convert(source, sourceType, targetType);
} catch (ConversionFailedException var5) {
throw var5;
} catch (Throwable var6) {
throw new ConversionFailedException(sourceType, targetType, source, var6);
}
}
...
}

第三步

java
1
2
3
4
5
6
7
8
9
public class GenericConversionService implements ConfigurableConversionService {
...
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
//三元表达式 为空返回第一个,不为空返回第二个
return source == null ? GenericConversionService.this.convertNullSource(sourceType, targetType) : this.converterFactory.getConverter(targetType.getObjectType()).convert(source);
}
...
} //此处不为空,执行this.converterFactory.getConverter(targetType.getObjectType()).convert(source)方法 先执行getObjectType()方法,再执行整个getConverter()方法

第四步

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Class<?> getObjectType() {  
//获取要转化的对象类型。
return ClassUtils.resolvePrimitiveIfNecessary(this.getType());
}


public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
//执行转换,将字符串类型转化为数字类型。
return new StringToNumber(targetType);
}
...//最终调用的转换方法
@Nullable
public T convert(String source) {
//三元表达式 source不为空,调用NumberUtils.parseNumber(source, this.targetType)方法,进入该方法
return source.isEmpty() ? null : NumberUtils.parseNumber(source, this.targetType);
}

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract class NumberUtils {
...
//最终根据目标类型进行将转换。到此,完成了数据类型的转化。
//转换都是三元表达式,是否返回16进制数字?真则将目标字符串解码,否则使用valueof获取目标字符串的值
public static <T extends Number> T parseNumber(String text, Class<T> targetClass) {
Assert.notNull(text, "Text must not be null");
Assert.notNull(targetClass, "Target class must not be null");
String trimmed = StringUtils.trimAllWhitespace(text);
if (Byte.class == targetClass) {
return isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed);
} else if (Short.class == targetClass) {
return isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed);
} else if (Integer.class == targetClass) {
return isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed);
} else if (Long.class == targetClass) {
return isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed);
} else if (BigInteger.class == targetClass) {
return isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed);
} else if (Float.class == targetClass) {
return Float.valueOf(trimmed);
} else if (Double.class == targetClass) {
return Double.valueOf(trimmed);
} else if (BigDecimal.class != targetClass && Number.class != targetClass) {
throw new IllegalArgumentException("Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]");
} else {
return new BigDecimal(trimmed);
}
}
...
}

至此AbstractNestablePropertyAccessor类中的valueToApply = this.convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor())方法执行完成了。
最终执行AbstractNestablePropertyAccessor类中的ph.setValue(valueToApply),将转换后的值设置进属性properties中。至此一个自定义参数的设置最终完成,如有多个参数,逐一遍历逐一转换。

36、请求处理-【源码分析】-自定义Converter原理

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

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

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//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;
}
});
}
};
}

37、响应处理-【源码分析】-ReturnValueHandler原理

在这里插入图片描述

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

xml
1
2
3
4
5
6
7
8
9
10
11
12
<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>

控制层代码如下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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;
}

}

32、请求处理-【源码分析】-各种类型参数解析原理 - 返回值处理器有讨论ReturnValueHandler。现在直接看看重点:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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();
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {

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

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

...

try {
//看下块代码 会先确定参数getReturnValueType(returnValue)的值(获取返回值的类型),之后再试执行handleReturnValue()方法
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
...
}
}


下面的类实现了上面这个接口。
这个接口就两个方法: 1.判断支持的返回值类型 2.处理返回值。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {

...

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

//selectHandler()实现在下面 selecthandle()就是遍历返回值处理器筛选处理器的方法
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
//调用找到的handler开始处理返回值
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;
}

如果要处理@ResponseBody 注解的返回值,需要使用RequestResponseBodyMethodProcessor类中的handleReturnValue()处理返回值,由于它实现HandlerMethodReturnValueHandler接口

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

...

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
//给mav容器设置请求处理器
mavContainer.setRequestHandled(true);
//封装原生请求为inputMessage
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
//封装原生响应为outputMessage
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

// 使用消息转换器进行写出操作,本方法下一章节介绍:
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

}

38、响应处理-【源码分析】-HTTPMessageConverter原理

返回值处理器ReturnValueHandler原理:

  1. 返回值处理器判断是否支持这种类型返回值 supportsReturnType
  2. 返回值处理器调用 handleReturnValue 进行处理
  3. RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
    • 利用 MessageConverters 进行处理 将数据写为json
      1. 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
      2. 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
      3. SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
        1. 得到MappingJackson2HttpMessageConverter可以将对象写为json
        2. 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155

//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) {
//将value的值封装到body中
body = value.toString();
//确认返回值类型和目标类型为String
valueType = String.class;
targetType = String.class;
}
else {

body = value;
//获取返回值类型
valueType = getReturnValueType(body, returnType);
//获取目标值类型
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
//判断是否是资源文件,inputResourceAsStream资源文件处理
//满足是InputStreamResource类且是Resource类才会进入
if (this.isResourceType(value, returnType)) {
outputMessage.getHeaders().set("Accept-Ranges", "bytes");
if (value != null && inputMessage.getHeaders().getFirst("Range") != null && outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource)value;

try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
} catch (IllegalArgumentException var17) {
outputMessage.getHeaders().set("Content-Range", "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}

...

//内容协商(浏览器默认会以请求头(参数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
selectedMediaType = contentType;
}
else {
//获取原生的请求
HttpServletRequest request = inputMessage.getServletRequest();
//获取请求能接受的媒体类型
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
//服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据 该方法里就是遍历所有messageConverters找出能将返回值类型的数据转换成媒体类型数据的方法。进入该方法!!!
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集合mediaTypesToUse里,
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;
}
}
}
...
}

我们研究一下 [[List producibleTypes = getProducibleMediaTypes(request, valueType, targetType)这个方法:]]

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {  
//一个是从handlerMapping里那可生产的媒体类型属性
Set<MediaType> mediaTypes = (Set)request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList(mediaTypes);
} else {
//设置返回结果result集合和创建迭代器遍历
Set<MediaType> result = new LinkedHashSet();
Iterator var6 = this.messageConverters.iterator();

while(true) {
while(var6.hasNext()) {
//死循环里遍历所有转换器并强转位HttpMessageConverter类型,共有8个
HttpMessageConverter<?> converter = (HttpMessageConverter)var6.next();
//是否是GenericHttpMessageConverter类型
if (converter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> ghmc = (GenericHttpMessageConverter)converter; //是则进行强转
if (targetType != null) {
//判断是否可以讲返回值类型转为媒体类型
if (ghmc.canWrite(targetType, valueClass, (MediaType)null)) {
//把这个转换器加入result集合
result.addAll(converter.getSupportedMediaTypes(valueClass));
}
continue;
}
}
//判断是否可以讲返回值类型转为媒体类型
if (converter.canWrite(valueClass, (MediaType)null)) {
////把这个转换器加入result集合
result.addAll(converter.getSupportedMediaTypes(valueClass));
}
}
//三元表达式(List)result为空?是则返回MediaType.ALL=*/*的单例集合,否则返回reult的Arraylist
return (List)(result.isEmpty() ? Collections.singletonList(MediaType.ALL) : new ArrayList(result));
}
}
}

以下为上述的8种HttpMessageConverter 每种转换器支持的返回值的类型不同

0-只支持Byte类型的
1-只支持String类型的但是字符集必须为UTF-8的
2-只支持String类型的但是字符集必须为ISO-8859-1的
3-只支持Resource类型
4-只支持ResourceRegion类型
5-支持吃MultiValueMap类型
6-true
7-true

我们在研究一下genericConverter.write(body, targetType, selectedMediaType, outputMessage)这个方法

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {  
//获取响应头
HttpHeaders headers = outputMessage.getHeaders();
//添加响应头
this.addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage streamingOutputMessage) {
streamingOutputMessage.setBody((outputStream) -> {
this.writeInternal(t, type, new HttpOutputMessage() {
public OutputStream getBody() {
return outputStream;
}

public HttpHeaders getHeaders() {
return headers;
}
});
});
} else {
//在这里进行写入 进入该方法!!!
this.writeInternal(t, type, outputMessage);
//上一步写入完成后就会将响应体写入响应
outputMessage.getBody().flush();
}

}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {  
//获取呢绒类型
MediaType contentType = outputMessage.getHeaders().getContentType();
//获取编码
JsonEncoding encoding = this.getJsonEncoding(contentType);
Class var10000;
if (object instanceof MappingJacksonValue mappingJacksonValue) {
var10000 = mappingJacksonValue.getValue().getClass();
} else {
var10000 = object.getClass();
}
//获取对象实例
Class<?> clazz = var10000;
//获取对象映射
ObjectMapper objectMapper = this.selectObjectMapper(clazz, contentType);
Assert.state(objectMapper != null, () -> {
return "No ObjectMapper for " + clazz.getName();
});
//获取输出流以便写入
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());

try {
//获取json生成器
JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding);

try {
//查看是否有Json前缀,有则写入,之后的后缀同理
this.writePrefix(generator, object);
//把对象写入value里
Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
if (object instanceof MappingJacksonValue mappingJacksonValue) {
value = mappingJacksonValue.getValue();
serializationView = mappingJacksonValue.getSerializationView();
filters = mappingJacksonValue.getFilters();
}
//获取java类型
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = this.getJavaType(type, (Class)null);
}
//获取对象写入器
ObjectWriter objectWriter = serializationView != null ? objectMapper.writerWithView(serializationView) : objectMapper.writer();
if (filters != null) { //如果有过滤器才进入
objectWriter = objectWriter.with(filters);
}

if (javaType != null && (javaType.isContainerType() || javaType.isTypeOrSubTypeOf(Optional.class))) {
objectWriter = objectWriter.forType(javaType);
}

SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}

objectWriter = this.customizeWriter(objectWriter, javaType, contentType);
//最终的写入方法
objectWriter.writeValue(generator, value);
this.writeSuffix(generator, object);
//发送出去。至此写入完成
generator.flush();
} catch (Throwable var17) {
if (generator != null) {
try {
//关闭流
generator.close();
} catch (Throwable var16) {
var17.addSuppressed(var16);
}
}

throw var17;
}

if (generator != null) {
generator.close();
}

} catch (InvalidDefinitionException var18) {
throw new HttpMessageConversionException("Type definition error: " + var18.getType(), var18);
} catch (JsonProcessingException var19) {
throw new HttpMessageNotWritableException("Could not write JSON: " + var19.getOriginalMessage(), var19);
}
}

HTTPMessageConverter接口:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Strategy interface for converting from and to HTTP requests and responses.
*/
public interface HttpMessageConverter<T> {

/**
* Indicates whether the given class can be read by this converter.
*/
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

/**
* Indicates whether the given class can be written by this converter.
*/
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

/**
* Return the list of {@link MediaType} objects supported by this converter.
*/
List<MediaType> getSupportedMediaTypes();

/**
* Read an object of the given type from the given input message, and returns it.
*/
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;

/**
* Write an given object to the given output message.
*/
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;

}

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

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

在这里插入图片描述

java
1
2
3
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
...
}

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

关于HttpMessageConverters的初始化

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

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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也被实例化。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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类:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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

39、响应处理-【源码分析】-内容协商原理

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

引入XML依赖:

xml
1
2
3
4
 <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。调用它进行转化 。
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132

//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);
}
//这里进行协商 根据客户端支持的类型每种requestedType和服务器端能生产的每种producibleType进行兼容匹配。如果requestedType兼容producibleType,则添加该种媒体类型进可用的媒体类型ArrayList mediaTypesToUse里
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
//如果mediaTypesToUse集合为空,抛异常。
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里
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) {
//判断是否是genericConverter类型?是则强转,否则返回空
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);

//判断是否可写 三元表达式根据判断调用不同的canWrite方法
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;
}
}
}
...
}

40、响应处理-【源码分析】-基于请求参数的内容协商原理

上一节内容协商原理的第二步:

获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)

  • contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
  • HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//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));
}

}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {

...

public ContentNegotiationManager() {
this(new HeaderContentNegotiationStrategy());//内容协商管理器 默认使用基于请求头的策略
}

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
//里面只有一个策略 就是HeaderContentNegotiationStrategy
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;
}
...

}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//基于请求头的策略
public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {

/**
* {@inheritDoc}
* @throws HttpMediaTypeNotAcceptableException if the 'Accept' header cannot be parsed
*/
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
//最终就是从请求里获取请求头的Accept字段的值
String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
if (headerValueArray == null) {
return MEDIA_TYPE_ALL_LIST;//即*/*
}
//把请求头里支持的类型封装成headerValues
List<String> headerValues = Arrays.asList(headerValueArray);
try {
//封装headerValues进mediaTypes
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
MediaType.sortBySpecificityAndQuality(mediaTypes);
//判断是否不为空,不为空返回mediaTypes,为空返回*/*
return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotAcceptableException(
"Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
}
}

}

开启浏览器参数方式内容协商功能

为了方便内容协商,开启基于请求参数的内容协商功能。

yaml
1
2
3
4
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式

开启之后就可以在请求的时候使用format=想要响应的数据类型:如http://localhost:8080/test/person?format=json来得到json数据的响应

内容协商管理器,就会多了一个ParameterContentNegotiationStrategy(由Spring容器注入)

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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) {
//在这里获取参数名为getParameterName()的值,getParameterName()=this.parameterName;= "format"
return request.getParameter(getParameterName());
}

//---以下方法在AbstractMappingContentNegotiationStrategy类

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
throws HttpMediaTypeNotAcceptableException {
//这里是format的类型转换成媒体类型的方法并进行返回。
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 {
//如果key不为空,此处key为format
if (StringUtils.hasText(key)) {
//获取format传入的媒体类型
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;
}


}

//以下方法在ContentNegotiationManage类里
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
//里面只有2个策略
// 0-ParameterContentNegotiationStrategy 上面的的方法就是它的内部执行
// 1-HeaderContentNegotiationStrategy
for (ContentNegotiationStrategy strategy : this.strategies) {
//在这个方法里如果确认了要返回的媒体类型
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
//且如果返回的媒体类型不等于MEDIA_TYPE_ALL_LIST,即!=*/*
if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
continue;
}
//就会返回该媒体类型,所以,不用遍历第二个请求头策略了。直接返回。
return mediaTypes;
}
return MEDIA_TYPE_ALL_LIST;
}

然后,浏览器地址输入带format参数的URL:

plaintext
1
2
3
http://localhost:8080/test/person?format=json

http://localhost:8080/test/person?format=xml

这样,后端会根据参数format的值,返回对应json或xml格式的数据。

这里我们思考一个问题,springboot是怎么帮我们添加所有messageConverters的呢?
其实,springboot的自动配置类帮我们在底层添加了很多messageConverters。具体添加了那些,让我追根溯源一下。 在配置类WebMvcAutoConfiguration添加了一个配置类WebMvcAutoConfigurationAdapter。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@AutoConfiguration(  
after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@ImportRuntimeHints({WebResourcesRuntimeHints.class})
public class WebMvcAutoConfiguration {
...
@Configuration(
proxyBeanMethods = false
)
@Import({EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware { //这是一个配置类,属性绑定了两个文件
//WebMvcProperties.class绑定了springmvc部分,WebProperties.class绑定了spring部分
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
private final WebProperties.Resources resourceProperties;
private final WebMvcProperties mvcProperties;
private final ListableBeanFactory beanFactory;
//配置了一个HttpMessageConverters属性
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;
private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
private ServletContext servletContext;
//该类的有参构造器 所有参数都会从容器里获取
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = webProperties.getResources();
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = (ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
}

public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}

public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//这里给messageConverters进行配置,使用customConverters.getConverters()获取conveters,然后添加进List<HttpMessageConverter<?>> converters集合里。进入此方法!
this.messageConvertersProvider.ifAvailable((customConverters) -> {
converters.addAll(customConverters.getConverters());
});
}

}

第一步:执行customConverters.getConverters()方法,获取里面的converters

java
1
2
3
4
public List<HttpMessageConverter<?>> getConverters() {  
//进入该方法!看看this.converters是什么
return this.converters;
}

第二步:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>> {  
private static final List<Class<?>> NON_REPLACING_CONVERTERS;
private static final Map<Class<?>, Class<?>> EQUIVALENT_CONVERTERS;
private final List<HttpMessageConverter<?>> converters;
//这有里三个HttpMessageConverters有参构造方法
//第一个是将添加进来的数组里多个additionalConverters封装成List集合
public HttpMessageConverters(HttpMessageConverter<?>... additionalConverters) {
this((Collection)Arrays.asList(additionalConverters));
}
//第二个是是否允许添加其他的additionalConverters,是允许的
public HttpMessageConverters(Collection<HttpMessageConverter<?>> additionalConverters) {
this(true, additionalConverters);
}
//第三个是将addDefaultConverters,和传进来的其他converters整合成combined集合
//由于boolean addDefaultConverters=true所以添加默认Converters
//所以我们进入this.getDefaultConverters()方法
public HttpMessageConverters(boolean addDefaultConverters, Collection<HttpMessageConverter<?>> converters) {
List<HttpMessageConverter<?>> combined = this.getCombinedConverters(converters, addDefaultConverters ? this.getDefaultConverters() : Collections.emptyList());
combined = this.postProcessConverters(combined);
this.converters = Collections.unmodifiableList(combined);
}

//好的接下来我们进入了getDefaultConverters()方法
private List<HttpMessageConverter<?>> getDefaultConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList();
//如果"org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport"此类存在,如果maven配置了spring-boot-starter-web就会有。
if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport", (ClassLoader)null)) {
//此时从WebMvcConfigurationSupport类中调用defaultMessageConverters()获取converters
//defaultMessageConverters()方法里调用的父类的getMessageConverters()获取converters
//最后将获取的所有MessageConverters添加到List集合converters中
converters.addAll((new WebMvcConfigurationSupport() {
public List<HttpMessageConverter<?>> defaultMessageConverters() {
//进入该方法
return super.getMessageConverters();
}
}).defaultMessageConverters());
} else {
converters.addAll((new RestTemplate()).getMessageConverters());
}

this.reorderXmlConvertersToEnd(converters);
return converters;
}
...

//此类在WebMvcConfigurationSupport中
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
//就是在这里添加各种默认的消息转换器
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
//present方法都是判断有没有添加其他的消息转换器,有则添加,无则跳过
if (romePresent) {
messageConverters.add(new AtomFeedHttpMessageConverter());
messageConverters.add(new RssChannelHttpMessageConverter());
}

Jackson2ObjectMapperBuilder builder;
if (jackson2XmlPresent) {
builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}

messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
} else if (jaxb2Present) {
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}

if (kotlinSerializationCborPresent) {
messageConverters.add(new KotlinSerializationCborHttpMessageConverter());
}

if (kotlinSerializationJsonPresent) {
messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
}

if (kotlinSerializationProtobufPresent) {
messageConverters.add(new KotlinSerializationProtobufHttpMessageConverter());
}

if (jackson2Present) {
builder = Jackson2ObjectMapperBuilder.json();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}

messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
} else if (gsonPresent) {
messageConverters.add(new GsonHttpMessageConverter());
} else if (jsonbPresent) {
messageConverters.add(new JsonbHttpMessageConverter());
}

if (jackson2SmilePresent) {
builder = Jackson2ObjectMapperBuilder.smile();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}

messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
}

if (jackson2CborPresent) {
builder = Jackson2ObjectMapperBuilder.cbor();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}

messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
}

}
...
}

41、响应处理-【源码分析】-自定义MessageConverter

实现多协议数据兼容。json、xml、x-guigu(这个是自创的)

  1. @ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理

  2. Processor 处理方法返回值。通过 MessageConverter处理

  3. 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)

  4. 内容协商找到最终的 messageConverter

想要给SpringMVC的配置一些其他功能,一个入口给容器中添加一个 WebMvcConfigurer
WebMvcConfigurer里重写很多功能

java
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiguMessageConverter());
}
}
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

/**
* 自定义的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());
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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),将返回自定义协议数据的写出。

42、响应处理-【源码分析】-浏览器与PostMan内容协商完全适配

假设你想基于自定义请求参数的自定义内容协商功能。

换句话,在地址栏输入http://localhost:8080/test/person?format=gg返回数据,跟http://localhost:8080/test/person且请求头参数Accept:application/x-guigu的返回自定义协议数据的一致。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@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);
// parameterStrategy.setParameterName("ff");

//还需添加请求头处理策略,否则accept:application/json、application/xml则会失效
HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();

configurer.strategies(Arrays.asList(parameterStrategy, headeStrategy));
}
}
}

...

}

日后开发要注意,有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。

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.——Link

Thymeleaf官方文档

thymeleaf使用

引入Starter

xml
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

自动配置好了thymeleaf

java
1
2
3
4
5
6
7
@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. 我们只需要直接开发页面

java
1
2
public static final String DEFAULT_PREFIX = "classpath:/templates/";//模板放置处
public static final String DEFAULT_SUFFIX = ".html";//文件的后缀名

编写一个控制层:

java
1
2
3
4
5
6
7
8
9
10
@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

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!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>

yaml
1
2
3
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

  • 设置单个值
html
1
2
3
4
5
6
<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>
  • 设置多个值
html
1
2
<img src="../../images/gtvglogo.png"  
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

官方文档 - 5 Setting Attribute Values

迭代

html
1
2
3
4
5
<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>
html
1
2
3
4
5
<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>

条件运算

html
1
2
3
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
html
1
2
3
4
5
<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>

属性优先级

Order Feature Attributes
1 Fragment inclusion th:insert th:replace
2 Fragment iteration th:each
3 Conditional evaluation th:if th:unless th:switch th:case
4 Local variable definition th:object th:with
5 General attribute modification th:attr th:attrprepend th:attrappend
6 Specific attribute modification th:value th:href th:src ...
7 Text (tag body modification) th:text th:utext
8 Fragment specification th:fragment
9 Fragment removal th:remove

官方文档 - 10 Attribute Precedence