Spring Boot编程思想(核心篇)

购买链接
spring-framework—github

一、总览SpringBoot

前言

1
2
3
4
5
6
7
8
9
VI. 2022-09-29 08.01 周四
总分总

Spring技术栈 => 非常成功的“重复发明轮子”
SpringBoot 易学难精 => 它的核心的 Spring,而 Spring理解程度取决于对 JSR规范及 Java熟悉度。

VII. 2022-09-29 08.03
SpringBoot1.x Spring4.x Java6
SpringBoot2.0 Spring5.0 Java8

Spring

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
2. 2022-09-29 08.17
Spring 颠覆 J2EE开发 => IOC(Inversion Of Control, 控制反转) DI(Dependency Inject, 依赖注入)
JSR(Java Specification Requests, Java规范要求)
Spring 重复发明轮子(实现 JSR)。
J2EE技术实现
IOC => JNDI(Java Naming and Directory Interface, Java命名和目录接口)
DI => EJB容器注入

Spring => 胶水框架
Web => Struts
模版 => JSP
持久层 => Hibernate iBatis(后改名MyBatis)
事务 => 抽象,统一数据库事务和分布式事务,利用AOP使事务大大简化。
Spring Web MVC => 使 Spring 发展到顶峰

缺点: 还是需要容器运行 ===> SpringBoot 来做(内嵌容器)

# SpringBoot特性 ===> https://spring.io/projects/spring-boot
+ Create stand-alone Spring applications
创建独立的Spring应用程序

+ Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
直接嵌入Tomcat, Jetty或Undertow(不需要部署WAR文件)

+ 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配置

JarLauncher

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
36. 2022-09-29 08.40
FAT JAR 和 WAR --- spring-boot-loader

<!--SpringBoot JarLauncher-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<scope>provided</scope>
</dependency>

java -jar 必须指定 Main-Class
!new-bi-service.jar/META-INF/MANIFEST.MF
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: new-bi-service
Implementation-Version: 1.0.0
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.amoros.newbi.service.NewBiServiceApplication # SpringBoot 定义
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.6.3
Created-By: Maven JAR Plugin 3.2.2
Main-Class: org.springframework.boot.loader.JarLauncher # oracle jar文件规范

!new-bi-service.jar/org/springframework/boot/loader/JarLauncher.class

spring-boot-maven-plugin => 加入

# 在 jar包解压的根目录下执行 => Main-Class
java org.springframework.boot.loader.JarLauncher
...
[2022-09-29 08:52:18.532] INFO [background-preinit] [] HV000001: Hibernate Validator 6.2.0.Final - org.hibernate.validator.internal.util.Version#<clinit>:21
[2022-09-29 08:52:18.674] INFO [main] [] App ID is set to new-bi by app.id property from System Property - com.ctrip.framework.foundation.internals.provider.DefaultApplicationProvider#initAppId:85
[2022-09-29 08:52:18.683] INFO [main] [] Environment is set to null. Because it is not available in either (1) JVM system property 'env', (2) OS env variable 'ENV' nor (3) property 'env' from the properties InputStream. - com.ctrip.framework.foundation.internals.provider.DefaultServerProvider#initEnvType:130
[2022-09-29 08:52:18.879] INFO [main] [] Located meta services from apollo.meta configuration: http://10.11.12.205:8080! - com.ctrip.framework.apollo.internals.DefaultMetaServerProvider#initMetaServerAddress:42
[2022-09-29 08:52:18.899] INFO [main] [] Located meta server address http://10.11.12.205:8080 for env UNKNOWN from com.ctrip.framework.apollo.internals.DefaultMetaServerProvider - com.ctrip.framework.apollo.core.MetaDomainConsts#initMetaServerAddress:93

# 在 jar包解压的根目录下执行 => Start-Class
java com.amoros.newbi.service.NewBiServiceApplication
错误: 找不到或无法加载主类 com.amoros.newbi.service.NewBiServiceApplication

SpringBoot依赖的 jar 都放在 !new-bi-service.jar/BOOT-INF/lib/*.jar 目录下。
运行 JarLauncher 会加载 lib 中的所有 jar 做为类库依赖,而运行 Start-Class 则不行。

JarLauncher "BOOT-INF/"
WarLauncher "WEB-INF/"

57. 2022-09-29 09.31

在这里插入图片描述
【日期标记】2022-09-29 11:31:38 以上同步完成

Maven 依赖

二选一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>


<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

容器

1
2
3
4
5
嵌入式 Java Web => Servlet => Tomcat Jetty Undertow
嵌入式 Reactive Web(异步非阻塞) => Netty Web Server(Reactor + Netty) => spring-boot-starter-webflux


Tomcat 7+ maven插件运行 jar 或 war(早期我这么做过)

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
Servlet Web
tomcat:web(自带tomcat)
jetty/undertow:web(排除tomcat)、jetty/undertow
Reactive Web
netty:flux(自带netty)
tomcat/jetty/undertow:flux、tomcat/jetty/undertow

# [1] tomcat
<!--[1] tomcat-->
<!--<dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!--</dependency>-->

# [2] undertow
<!--[2] undertow-->
<!--<dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-tomcat</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!--</dependency>-->
<!--<dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-undertow</artifactId>-->
<!--</dependency>-->

# [3] webflux ===> 添加 flux接口,web接口会失效
<!--[3] webflux ===> 添加 flux接口,web接口会失效-->
<!--<dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-webflux</artifactId>-->
<!--</dependency>-->
<!--<dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-undertow</artifactId>-->
<!--</dependency>-->

# [4] webflux ===> 使用默认 Netty Web Server
<!--[4] webflux ===> 使用默认 Netty Web Server -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
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
package com.example.demo5;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

import java.util.UUID;

/**
* 描述
*
* @author 陶攀峰
* @date 2022-09-30 15:10
*/
@RestController
public class TestController {
@GetMapping("web")
public String web() {
return "web:" + UUID.randomUUID().toString();
}

@GetMapping("flux")
public Mono<String> flux() {
return Mono.just("flux:" + UUID.randomUUID().toString());
}
}

自动装配

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
@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

注解派生
@Component
@Repository
@Service
@Controller => RestController
@Configuration => @SpringBootApplication

@AliasFor => 简化
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(annotation = Controller.class)
String value() default "";
}

@Component
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";
}


"Lite"轻量模式 => 其他Bean(Bean 本身)
"Full"完全模式 => @Configuration标注的 Bean(CGLIB 代理)

112. 2022-09-30 18:35:21
【日期标记】2022-09-30 18:51:33 以上同步完成

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
# 自动装配
2022-10-12 07:36:30
Spring 无法自动装配 @Configuration(SpringBoot 可以)。
提供starter,@Condition 之类的注解,META-INF/spring.factories(spring-boot-configure 提交大量自动配置类)

# 监控
=> actuator(监控 Bean的情况,JMX指标采集)

# 内/外部化配置
Spring 内部化配置
中间件调整大量参数,切换环境 dev
=> Spring ConfigurableEnvironment setActiveProfiles("dev")
SpringBoot 外部化配置(17种)
application.properties 即使有默认,换个环境可使用命令行覆盖 java -jar -Dspring.application.name=xxx

# Spring 注解驱动之路
2.5 注解逐步代替 XML,@Component,派生@Service,还有DI @Autowired

3.0 @Configuration,@Bean,@Import

3.1 @ComponentScan,ImportSelector接口,@EnableCache(标注的类要是 Bean)

4.0 @Conditional(使 SpringBoot @ConditionalOnxxx 条件装配成为可能)


# SpringBoot 主要五大特性(SpringCloud 基础设施)
+ SpringApplication
+ 自动装配
+ 外部化配置
+ actuator
+ web嵌入容器

# Spring 在 SpringBoot 基础上研发出 SpringCloud,方便构建分布式系统
+ 配置中心
+ 服务注册与发现
+ 路由
+ 服务调用
+ 负载均衡
+ 熔断机制
+ 分布式消息

136. 2022-10-12 08:35:10
【日期标记】2022-10-12 08:35:16 以上同步完成

二、走向自动装配

注解驱动发展史

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
2022-10-12 08:47:24
改变是一个不破不立的过程。“破”不是否定过去,而是去除糟柏;“立,不是无中生有,而是与时俱进。

# Spring 是Spring Boot 核心,Java 规范是它们的基石
微服务架构 (Micro-Services Architecture)的“春风,吹向了软件行业的“大地”,无意间使得soring Boot 在Java社区变得出乎意料地风靡。
这种趋势的盛延让开发人员产生了对 Spring Boot的“狂热”以及对 Spring Framework 的“忽视”。甚至认为Spring Boot 是一个新的分水岭,Spring Boot 应用代表了微服务架构,其他的框架文摇着“单体应用”(Monoitic Application)。
诚如上述所言,我当初出如此“偏执地”认为 Spring Boot 引领了“新风向”。
不过随着系统性地深入研究,才幡然醒悟,殊不知 Spring Framework 是Spring Boot 核心,Java 规范才是它们的基石。
其中的是非曲直,都反映了一个客观的事实——Java EE 的世界发生了“改变”。

Spring 1.x
1.2.0 @ManagedResource、@Transactional (JDK 1.5+)
=> 仍然是 XML配置

Spring 2.x
2.0 => 2006年,几乎完美兼容1.x版本
新增注解:@Required、@Repository、AOP的@Aspect
提升 XML 扩展能力 => Dubbo 等框架扩展自身 XML

2.5 依赖注入 @Autowired => 可以注入单个Bean,也可以注入某个 Class 集合,相同类型按照名称筛选,需要 @Qualifier
依赖查找 @Qualifier => 相同类型,按照名称来查找某个Bean
组件声明 @Component、@Service
SpringMVC @Controller、@RequestMapping、@RequestParam、@ModelAttribute等
JSR-250 @Resource、@PostConstruct、@PreDestroy
=> 前者可替代 <bean init-method="..."/> 或 InitializingBean
=> 前者可替代 <bean destroy-method="..."/> 或 DisposableBean

只能单层派生 Bean,3.0才多层 Bean派生

2.x 仍然 XML 配置驱动
<context:annotation-config> 注册 Annotation处理器
<context:component-scan> 扫描 Base Package,注解Bean

1.x Bean排序 实现Ordered接口,2.0 @Component 标注 @Order

Spring 3.x
全面拥抱 JDK 1.5(泛型、注解、...),全面替代 XML配置

3.0 @Configuration => @Component派生,替代 XML配置
没有替代 <context:component-scan> => 而是过渡方案
=> @ImportResource 导入 XML配置文件
=> @Import 导入一个或多个普通类,作为 Bean
=> 需要配合 @Configuration 使用:@ImportResource+@Configuration、@Import+@Configuration

3.0 引入 ApplicationContext 实现 AnnotationConfigApplicationContext
=> 先使用 AnnotationConfigApplicationContext 注册 @Configuration 类
=> @Configuration 类上标注 @ImportResource、@Import

3.0 替换 XML Bean
@Bean <bean>
@DependsOn <bean depends-on="..."/>
@Lazy <bean lazy-init="true | false"/>
@Primary <bean primary="true | false"/>

3.0 web方面
@RequestHeader、@CookieValue、@RequestPart 使得@Controller不使用 Servlet API
@PathVariable 方便Rest开发
@RequestBody 反序列化
@ResponseBody 序列化
整合Servlet 3.0+规范,javax.servlet.ServletContainerInitializer 实现 web 自动装配

3.0 异步
@Async、@Scheduled

3.1
@ComponentScan <context:component-scan> 向前一小步,注解驱动一大步
@Profile 不同环境 Bean

3.1 抽象
Environment 配置接口、PropertySources 配置源抽象
=> 这两个接口奠定了 SpringBoot 外部化配置的基础,也是 SpringCloud配置中心的基石。
@PropertySource
=> value() 关联 Properties资源

3.1 缓存
API => Cache、CacheManager
注解 => @EnableCaching、@Cacheable、@CachePut、@CacheEvict、@Caching

3.1 @Validated 整合 JSR-303

3.1 @Enable模块驱动
@EnableWebMvc

Spring 4.x
4.0 @Conditional 使用 SpringBoot 1.0.0 @ConditionalOn* 自定义条件成为可能
JDK 1.8 @Repeatable => @PropertySource 升级为重复标注
@Repeatable(PropertySources.class)
public @interface PropertySource {
...
}

@PropertySource("classpath:a.properties")
@PropertySource("classpath:b.properties")
@Configuration
public class MyConfig{
}
4.0 @RestController
4.1 @Lookup 依赖查找
4.2 @EventListener
4.2 @AliasFor 派生别名,自己别名
4.2 @CrossOrigin
4.3 @GetMapping、PutMapping、... 里面都是 @AliasFor(annotation = RequestMapping.class)
4.3 @ComponentScans
4.3 @RestControllerAdvice AOP拦截

Spring 5.x
SpringBoot 2.0 使用 Spring 5.0 作为依赖
引入 JSR-305 @NonNull、@Nullable

Spring注解编程模型

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、元注解 (Meta-Annotations);
@Repeatable @Inherited @Documented 都属于元注解
+ 2、Spring 模式注解 (Stereotype Annotations); ===> 注解的派生 => @Service 被 @Component 标注,也有相应功能
创建 Servlet 上下文
=> 添加 @Component includeFilter

doScan 扫描 BeanDefinition
=> basePackage 扫描 Resource class类文件
=> Resource 转换为 MetadataReader
---> 这里使用 ClassReader.accept
---> 使用 SimpleAnnotationMetadataReadingVisitor 进行ASM解析(耗时,忽略不计)
---> StandardAnnotationMetadata 使用 Java反射解析(耗时长)
=> 主类excludeFilter排除,正常Bean includeFilter匹配成功(含有@Component 或 包含@Component)
+ 3、Spring 组合注解 (Composed Annotations);
=> 多个注解组合在一起,形成一个注解。例如:@SpringBootApplication
+ 4、Spring 注解属性别名和覆盖 (Attribute Aliases and Overrides )。
代码参考地址 => https://gitee.com/taopanfeng/thinking-in-spring-boot-samples/tree/master/spring-framework-samples/spring-framework-5.0.x-sample/src/main/java/thinking/in/spring/boot/samples/spring5/bootstrap

补充:为什么会并存两种访问:Java反射 和 ASM解析。
1、Java反射适用于已经被 ClassLoader 加载的类
2、如果依赖的 jar 中,有些类没有被 ClassLoader加载,可以使用 ASM解析

162. 2022-10-12 11:12:01
【日期标记】2022-10-12 11:12:06 以上同步完成

186. 2022-10-13 09:58:30
224. 2022-10-13 18:49:32
  • 2、Spring 模式注解 (Stereotype Annotations); ===> 注解的派生性
    在这里插入图片描述

Spring注解驱动设计模式

1
2
3
@Enable模块驱动 ===> @Import
+ 注解驱动(@Configuration、@Bean、...)
+ 接口编程(ImportSelector、ImportBeanDefinitionRegistrar)

在这里插入图片描述
236页
【日期标记】2022-10-17 10:01:10 以上同步完成


上面是使用方式,下面开始讲实现“ConfigurationClassPostProcessor 三阶段”
250页
【日期标记】2022-10-17 18:31:12 以上同步完成

  • 阶段1:ConfigurationClassPostProcessor 创建 BeanDefinition
    在这里插入图片描述
  • 阶段2:解析@Configuration(scan,parse,@Import,load),BeanDefinition注册
  • 阶段3:enhance(CGLIB代理@Configuration,拦截@Bean)
    在这里插入图片描述
    补充:查找配置类候选人(FULL、LITE)(2022-10-27 10:16:41)
    在这里插入图片描述
    【日期标记】2022-10-18 17:24:14 以上同步完成

1
2
3
4
5
6
7
8
综上所述,ConfigurationClassPostProcessor 负责筛选 @Component Class、@Configuration Class 及 @Bean方法的 Bean定义(BeanDefinition),
ConfigurationClassParser 则从候选的 Bean定义中解析出 ConfigurationClass 集合,
随后被 ConfigurationClassBeanDefinitionReader 转换并注册 BeanDefinition(为后续的创建 Bean打下基础)。

模块装配(@Enable模块装配 ) => 手动挡
自动装配 => 自动挡
两者并不完全排斥,而是互相依存。
===> 通过上面可以看出,Spring Framework 在走向 “自动装配” 上迈出了一大步。

Spring-Web 自动装配

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
tomcat
- ServletContainerInitializer(Servlet 3.0 => 支持编程式替换传统的 XML文件)

spring
- WebApplicationInitializer(可以被任何 Servlet 3+ 容器侦测,并自动地初始化)
- AbstractDispatcherServletInitializer(Spring XML 配置驱动)
- AbstractAnnotationConfigDispatcherServletInitializer(Spring Java 代码驱动)


全局来看,SpringServletContainerInitializer 实现 Servlet 3.0 SPI接口 ServletContainerInitializer,
与 @HandlesTypes 配合过滤出 WebApplicationInitializer 具体实现类集合,随后顺序地执行,达到实现 Web自动装配。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException
{
...
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
----------------------------------------------------------------------

1. 存在多个此抽象类,会报错。

2. 自己创建上下文,还是麻烦。

3. 指定配置类即可。

在这里插入图片描述

XML条件装配

在这里插入图片描述

条件装配原理

  • 配置条件装配原理:处理流程
    在这里插入图片描述
    280页
    【日期标记】2022-10-20 18:43:31 以上同步完成
  • shouldSkip @Conditional 条件匹配
    在这里插入图片描述
  • ConfigurationCondition 接口(ConditionEvaluator 评估两阶段:“Bean 注册阶段”和“Configuration Class 解析阶段”。)
    在这里插入图片描述
1
2
3
4
5
6
7
8
至此,关于 Spring Framework 走向注解驱动编程 (Annotation-Driven)的讨论告一段落。
深刻地感受到在此过程中,Spring Framework 曾付出的努力。

# Spring不足,SpringBoot弥补
尽管如此,Spring Framework 组件装配的自动化程度仍不是特别理想,比如:
+ @Enable 模块驱动不仅需要将@Enable 注解显式地标注在配置类上,而且该类还依赖@Import 或@Componentscan 的配合。
+ 同时, Web 自动装配必须部署在外部 Servlet 3.0+容器中,无法做到 Spring Web 应用自我驱动。
或许正因为 Spring Framework 中诸如此类的特性限制,直接或间接地促使 Spring Boot 的出现,这些限制终被 Spring Boot 自动装配和嵌入式 Web 容器等特性“各个击破”。

291页
【日期标记】2022-10-21 09:31:57 以上同步完成

开启自动装配

  • 开启自动装配 @EnableAutoConfiguration
    在这里插入图片描述
    316页
    【日期标记】2022-10-24 10:40:29 以上同步完成(看完待整理)
    【日期标记】2022-10-31 13:56:07 以上同步完成(整理完成)

  • AutoConfigurationImportSelector 延迟导入
    在这里插入图片描述
    324页
    【日期标记】2022-11-01 08:14:35 以上同步完成

  • 排序:@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder

    1
    2
    3
    4
    5
    6
    # 元注解配置文件
    META-INF/spring-autoconfigure-metadata.properties

    # 好处 => 提升性能
    存在其中,直接读取;
    不存在,则需要 ClassLoader加载反射读取 或 字节码ASM读取

    在这里插入图片描述
    在这里插入图片描述
    332页
    【日期标记】2022-11-01 10:50:58 以上同步完成

  • 自动扫描包 @AutoConfigurationPackage(JPA entity扫描)

自定义starter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
0. 配置
自动配置类 META-INF/spring.factories
spring-boot-configuration-processor 生成元信息
外部化配置 @ConfigurationProperties => META-INF/spring-configuration-metadata.json

1. 命名
类名 *AutoConfiguration
包名 => 这就有点像 SpringBoot项目一样,根目录下面是一个SpringBoot启动类,启动类同级都是包(包里面业务类)
${root-package}.autoconfigure.${module-package}.*AutoConfiguration
${root-package}.autoconfigure.${module-package}.${sub-module-package}
例如:org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
${root-package} org.springframework.boot
${module-package} cache
模块名
官方 spring-boot-stater-${module} spring-boot-autoconfigure-${module}
三方 ${module}-spring-boot-stater ${module}-spring-boot-autoconfigure

2. 依赖 + 条件装配
<optional>true</optional>

@ConditionalOn*
@ConditionalOnClass => name()
@ConditionalOnMissingClass => value()
...

@ConditionOn*

代码参考: thinking-in-spring-boot-samples — gitee
starter ===> spring-boot-2.0-samples/formatter-spring-boot-starter
使用案例 ===> spring-boot-2.0-samples/auto-configuration-sample


  • Class ===> Class.forName加载是否存在
    1
    2
    3
    4
    5
    6
    starter optional=true
    @ConditionalOnClass(name = "xxx")
    @ConditionalOnMissingClass(value = "xxx")

    已启动 => 未生效
    加入依赖 => 生效!
    在这里插入图片描述
    在这里插入图片描述
    358页

  • Bean ===> BeanFactory 获取
    class 与 Bean 同时使用
    在这里插入图片描述
    在这里插入图片描述
    370页

  • Property ===> Environment获取进行Value匹配 String.equalsIgnoreCase
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    prefix + name => key
    havingValue => value(配置的如果是这个值,才生效;否则不是这个值,则不失效)
    matchIfMissing => 没配置key,默认匹配成功

    # 例如:AOP自动装配
    # havingValue = "true" 配置必须是 true 才可以;如果配置false,则关闭
    # matchIfMissing = true 不配置默认也开启
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
    public class AopAutoConfiguration {
    在这里插入图片描述
    375页

  • Resource

  • Web

  • SpEL

小总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Spring:1.x => 5.0   => 渐进式发展(核心特性,注解能力,)

虽然 SpringBoot 1.x => 2.0 升级是破坏性的,不可否认,它任然是非常优秀的框架。

# 下一站 SpringApplication
SpringBoot 依赖的注解驱动、@Enable模块驱动、条件装配、Spring工厂加载机制等特性都来自于 Spring。
无论 Spring还是 SpringBoot应用场景,这些功能都来自于 Spring应用上下文 以及 管理Bean生命周期来展开的。

不同的是:
Spring时代:Spring应用上下文由容器启动(例如:Tomcat容器),如 ContextLoaderListener 或 WebApplicationInitializer的实现类由 Servlet容器装载并驱动。
SpringBoot时代:Spring应用上下文启动则通过调用 SpringApplication#run(Object, String...) 或 SpringApplicationBuilder#run(String...)方法,
并配合 @SpringBootApplication 或 @EnableAutoConfiguration 注解方式完成。

402页
【日期标记】2022-11-03 10:46:12 以上同步完成

SpringApplication

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 构造阶段
推断Web应用类型 => REACTIVE / NONE / SERVLET
加载 应用上下文初始化器 ApplicationContextInitializer
加载 应用事件监听器 ApplicationListener
推断 main方法所在类 Class<?> mainApplicationClass
# 2. 配置阶段
位于构建阶段 与 运行阶段之间,使用 SpringApplication => set*方法覆盖=调整行为 / add*方法追加=补充行为
set* add* => SpringApplicationBuilder 流式构建
# 3. 运行阶段
SpringApplication 准备阶段
ApplicationContext 启动阶段
ApplicationContext 启动后阶段

SpringApplication 准备阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
// ...省略
}

理解 SpringApplicationRunListeners

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
SringBoot 监听
收集 SpringApplicationRunListener => "META-INF/spring.factories" => 默认实现 EventPublishingRunListener
=> EventPublishingRunListener 维护默认广播器 SimpleApplicationEventMulticaster

Spring 事件监听 => 参考下图
+ **Spring 事件监听:拍书本 UML图、微信公众号案例**(参考下图)
实现 ApplicationListener<ContextRefreshedEvent> BeanFactory 获取Bean,防止Bean提前初始化

测试1 自定义监听器
// context.refresh(); // 发布 ContextRefreshedEvent 上下文刷新
// context.stop(); // 发布 ContextStoppedEvent 暂停
// context.start(); // 发布 ContextStartedEvent 开启
// context.close(); // 发布 ContextClosedEvent 关闭
测试2 context.close() 之后还可以发布事件吗?
// 可以执行发布代码,但不会触发监听器,因为监听器被移除了
// context.close();// 会使用 ApplicationListenerDetector.postProcessBeforeDestruction 移除 ApplicationListener
测试3 @EventListener
// 标注在子类,和自己的都会被执行
// void,非void 都会执行
// ===> 事件监听器的监听方法无论是接口驱动,还是注解驱动,都是公共的,无返回值的,并且不会 throws 异常
// 首先,要是 Bean,public修饰符,可以支持 void 和 非void,可以监听1~n个 ApplicationEvent,参数0~1个 ApplicationEvent
测试4 异步 @Async + @EventListener
// @EnableAsync // 需要激活异步,否则 @Async 无效
// 返回值不可以设置原生类型 boolean,要改为包装类型 Boolean(最好设置 void,因为它的返回值没有任何价值)
测试5 发布非 ApplicationEvent对象
// context.publishEvent(new User("taopanfeng"));// 发送 User 对象作为事件源
//
// @EventListener// 监听
// public void onUser(User user) { System.out.println("onUser : " + user); }
@EventListener 实现原理
EventListenerMethodProcessor implements SmartInitializingSingleton
// 创建 => 在上下文构造的时候会执行 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
// => 会创建 DefaultEventListenerFactory、EventListenerMethodProcessor
// 执行 => 会找出所有 Bean上面有 @EventListener的方法,把方法包装成 ApplicationListener,并添加至 context

在 SpringBoot 1.0~1.3.x(包含1.3.x),EventPublishingRunListener.contextPrepared 会把自己的广播器注册到 BeanFactory,
在 AbstractApplicationContext 初始化广播器,就会看 BeanFactory有就用,没有就创建一个,并注册单例。
===> 在 1.4开始,SpringBoot就会用到不同的广播器,在 EventPublishingRunListener 不会把自己的广播器注册到 BeanFactory。
+ **源码:SpringBoot 广播器、Spring 广播器 低版本相同**(参考下图)
+ **案例:不同版本对比 SpringBoot 1.3相同广播器、SpringBoot 输出事件顺序**(参考下图)

# Spring 事件基类
# EventPublishingRunListener.contextLoaded 会把自己收集的 spring.factories ApplicationListener接口,全部添加到上下文
# AbstractApplicationContext 注册 Listener时,会获取上下文的 Listener,添加到广播器
# 后面使用广播器进行发布事件,由 ResolvableType维护泛型,
# 而广播器的基类 AbstractApplicationEventMulticaster 内部维护了泛型事件对应的 Listener ===> Map<ListenerCacheKey, CachedListenerRetriever>
# ===> 避免发布事件到所有的 Listener
org.springframework.context.ApplicationEvent
public abstract class ApplicationEvent extends EventObject
# Spring 内建事件基类
org.springframework.context.event.ApplicationContextEvent
public abstract class ApplicationContextEvent extends ApplicationEvent
# SpringBoot 内建事件基类
=> SpringApplication 创建时,spring.factories 收集 ApplicationListener接口,由 EventPublishingRunListener 发布(唯一 SpringApplicationRunListener内置实现)
org.springframework.boot.context.event.SpringApplicationEvent
public abstract class SpringApplicationEvent extends ApplicationEvent
  • 源码:SpringBoot 广播器、Spring 广播器 低版本相同
    在这里插入图片描述
  • 案例:不同版本对比 SpringBoot 1.3相同广播器、SpringBoot 输出事件顺序
    在这里插入图片描述
  • Spring 事件监听:拍书本 UML图、微信公众号案例
    在这里插入图片描述
  • @EnableAsync:源码分析
    在这里插入图片描述
    508页
    【日期标记】2022-11-16 11:49:36 以上同步完成

装配 ApplicationArguments

1
2
3
4
5
6
7
8
9
10
11
12
封装参数,带有--表示可选参数,不带有--表示非可选参数,例如:--name=taopanfeng   会解析为 name:taopanfeng 键值对

public class DefaultApplicationArguments implements ApplicationArguments {
private final Source source;
private final String[] args;
public DefaultApplicationArguments(String... args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}

private static class Source extends SimpleCommandLinePropertySource

准备 ConfigurableEnvironment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
TODO 待补充
会在外部化配置,深入讨论

根据 WebApplicationType 创建 ConfigurableEnvironment
===> 注意:这里SpringApplication 提前创建,如果这里不创建,那么会上下文刷新时创建,使用 BeanFactoryPostProcessor 实现 PropertySource装载
===> 因为 BeanFactoryPostProcessor优先级难维护,所以在 SpringApplication提前创建好 ConfigurableEnvironment。
AbstractApplicationContext refresh() -> prepareRefresh() -> getEnvironment()
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}

创建 Spring应用上下文(ConfigurableApplicationContext)

1
2
3
根据类是否存在,判断上下文类型

也可以显示的传递 SpringApplicationBuilder#web(WebApplicationType)

Spring应用上下文运行前准备

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
# 设置 Spring应用上下文 ConfigurableEnvironment
context.setEnvironment(environment);
因为这里 environment 的创建来自上面【准备 ConfigurableEnvironment】,上面有详细介绍
===> Spring Framework API 良好的设计是一把双刃剑,一方面能够充分地保证扩展的弹性,另一方面又引入一定的复杂度。

# Spring应用上下文后置处理
postProcessApplicationContext(context);
× 默认 beanNameGenerator = null,不注册Bean BeanNameGenerator
× 默认 resourceLoader = null,默认不执行 setResourceLoader、setClassLoader
√ 默认 addConversionService = true,执行 setConversionService

# 应用 Spring应用上下文初始化器(ApplicationContextInitializer)
applyInitializers(context);
在创建 SpringApplication时,已经收集了 spring.factories ApplicationContextInitializer。

ApplicationContextInitializer 如果不重写 hashcode equals 方法,可能会重复出现。
例如:如下重写,避免重复添加 ApplicationContextInitializer
@Override
public int hashCode() { return getClass().hashCode(); }

@Override
public boolean equals(Object obj) { return getClass().equals(obj.getClass()); }

# SpringApplicationRunListener#contextPrepared
listeners.contextPrepared(context);
发布 SpringBoot事件 ===> ApplicationContextInitializedEvent(在 ApplicationContext加载配置源之前执行)

EventPublishingRunListener.contextPrepared 此方法,在之前讨论广播器时候说了,
=> SpringBoot 1.0~1.3这个方法不为空,注册Bean 广播器,Spring 与 SpringBoot 共用广播器

默认 SpringBoot内置一个 SpringApplicationRunListener => 也就是 EventPublishingRunListener
在外部化配置中,就存在基于 SpringApplicationRunListener.contextPrepared方法,而扩展外部化配置源的情况。

# 注册 SpringBoot Bean
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
beanFactory.registerSingleton("springBootBanner", printedBanner);

# 合并 Spring应用上下文配置源
Set<Object> sources = getAllSources();
private Set<Class<?>> primarySources;
private Set<String> sources = new LinkedHashSet<>();
收集为 Set<Object> allSources = new LinkedHashSet<>();

# 加载 Spring应用上下文配置源
load(context, sources.toArray(new Object[0]));
创建 BeanDefinitionLoader
遍历 allSources,进行加载,本质是注册 BeanDefinition
load((Class<?>) source); ===> AnnotatedBeanDefinitionReader ===> 注册 BeanDefinition【此逻辑就是 SpringApplication.run 传入 Class】
load((Resource) source); ===> XmlBeanDefinitionReader ===> xml 注册 BeanDefinition
load((Package) source); ===> ClassPathBeanDefinitionScanner ===> 扫描包 注册 BeanDefinition
load((CharSequence) source); ===> ClassUtils.forName 反射获取Class,再 load(Class) ===> 注册 BeanDefinition

# SpringApplicationRunListener#contextLoaded
listeners.contextLoaded(context);
发布 SpringBoot事件 ===> ApplicationPreparedEvent
监听器传播 SpringBoot ApplicationListener => context.addApplicationListener(listener);

537页
【日期标记】2022-11-17 10:36:53 以上同步完成

Spring应用上下文启动阶段

Spring应用上下文进入了实质性的启动阶段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public ConfigurableApplicationContext run(String... args) {
// ...省略
refreshContext(context);
// ...省略
}

private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {// 默认 true,这里注册一个钩子,方便 JVM退出时进行回调
shutdownHook.registerApplicationContext(context);
}
refresh(context);
}

protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();// ===> SpringBoot驱动 Spring应用上下文刷新 AbstractApplicationContext#refresh()
}

Spring应用上下文启动后阶段

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 ConfigurableApplicationContext run(String... args) {
// ...省略
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
// ...省略
}

1. afterRefresh 留给子类去实现

2. 打印日志 => JVM启动花费时间 n秒

3. 发布事件 ApplicationStartedEvent

4. 调用 ApplicationRunner CommandLineRunner
===> 可使用 @Order 或 Ordered接口,实现排序
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);// ===> 排序
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}

顺便说一嘴:
ApplicationListener<?> => 需要写入 spring.factories中,可以获取到 SpringApplication对象。
ApplicationRunner、CommandLineRunner => 被 Bean代理,可以方便使用一些 @Autowired注入等特性

549页
【日期标记】2022-11-18 07:59:40 以上同步完成

SpringApplication 正常结束

1
2
3
4
5
6
7
最后,发布事件 ApplicationReadyEvent
public ConfigurableApplicationContext run(String... args) {
// ...省略
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
// ...省略
}

SpringApplication 异常结束

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 ConfigurableApplicationContext run(String... args) {
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);// ===> 异常处理
throw new IllegalStateException(ex);
}
// ...省略
}
  • 【案例】SpringBoot 自定义错误分析器,错误报告器
    在这里插入图片描述
  • 【源码分析】SpringBoot 启动异常处理 handleRunFailure
    在这里插入图片描述

570页
【日期标记】2022-11-18 09:40:39 以上同步完成

1
2
3
4
5
6
7
8
9
10
2022-11-18 09:53:44
真快啊,快看完了,

“日拱一卒无有尽,功不唐捐终入海。”,

一天一天,陆陆续续,
有看的时候,也有早上不看的时候,
真正从300页开始,后面我都进入了状态看的很快,因为每天早上都有来看。
最近也不知怎么了,每天早上都可以6点钟起床来,不像11月之前的状态,
(那个时候,真的是起不来,每天早上都做梦,不想起床,不过也有偶尔起得来,或者8点到公司,不过很少7点到公司,)

SpringBoot 应用正常退出

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
1、手动调用,进行退出 SpringApplication#exit(ApplicationContext, ExitCodeGenerator...)
2、默认为 0表示正常;非0表示错误;
3、不为0,会推送事件 ExitCodeEvent
4、finally 块中会进行上下文的关闭 context.close();

===> IDEA是父进程,java应用程序跑起来是子进程。
===> 实际 SpringBoot应用并不用使用 exit退出,因为它会关闭上下文。
===> 未来可能 SpringCloud Data Flow会使用,这里 SpringBoot应用作为 JVM子进程被容器管理生命周期。

@EnableAutoConfiguration
public class ExitCodeGeneratorBootstrap {

@Bean
public ExitCodeGenerator exitCodeGenerator() {
return () -> {
System.out.println("执行退出码(88)生成...");
return 88;
};
}

public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(ExitCodeGeneratorBootstrap.class)
.web(WebApplicationType.NONE) // 非 Web 应用
.run(args);// 运行 SpringBoot 应用

// 重构后的实现
int exitCode = SpringApplication.exit(context);

// 传递退出码到 System#exit(int) 方法
System.exit(exitCode);
}
}

// 执行退出码(88)生成...
//
// Process finished with exit code 88 ===> 这里输出 IDEA子进程退出

SpringBoot 应用异常退出

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
1、处理异常,其中就有异常码处理
public ConfigurableApplicationContext run(String... args) {
...
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
...
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
...
}


private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, SpringApplicationRunListeners listeners) {
...
handleExitCode(context, exception);
...
}

private void handleExitCode(ConfigurableApplicationContext context, Throwable exception) {
int exitCode = getExitCodeFromException(context, exception);
if (exitCode != 0) {
if (context != null) {
context.publishEvent(new ExitCodeEvent(context, exitCode));
}
...
}
}
  • 源码:SpringBoot handleExitCode 处理异常码
    在这里插入图片描述

  • 小总结
    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

    Spring Framework时代,Spring 应用上下文通常由容器启动。
    ===> 如 ContextLoaderListener 或 WebApplicationInitializer 的实现类由 Servlet容器(如:Tomcat容器)装载并启动。

    到了 Spring Boot时代,Spring 应用上下文的启动则通过调用 SpringApplication#run(Object, String...) 或 SpringApplicationBuilder#run(String...)方法,
    并配合 @SpringApplication 或 @EnableAutoConfiguration 注解的方式完成。

    ===> 注意,上面并没有说 SpringApplication 限定在嵌入式 Web应用场景,而是强调 Spring应用上下文的启动,
    不仅因为 SpringApplication 可以引导非 Web应用和嵌入式 Web应用,而且它还能出现在 SpringBoot应用部署在传统 Servlet 3.0+容器中的场景。
    后续在“Web篇”会讲到,war file部署 SpringBoot应用,使用主引导类继承 SpringBootServletInitializer。

    ===> SpringBootServletInitializer 是 WebApplicationInitializer的实现类,因此,SpringBoot应用在部署 Servlet 3.0+容器中时,其 onStartUp(ServletContext)方法将在启动时回调。
    (关于 WebApplicationInitializer 在上面 “## Spring-Web 自动装配” 有讲到。)
    public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
    ...
    WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
    ...
    }
    protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
    SpringApplicationBuilder builder = createSpringApplicationBuilder();
    builder.main(getClass());
    ...
    SpringApplication application = builder.build();
    ...
    return run(application);
    }
    }
    ------------------------------------------------

    SpringApplication 同样运用在 SpringBoot应用部署到传统 Servlet3.0+容器的场景中,所以说,SpringApplication并非为 SpringBoot嵌入式Web应用“量身定制”。
    既然如此,无论在哪种 SpringBoot应用场景下,功能组件均为 Spring Bean,那么为何不直接使用 ConfigurableApplicationContext实现类来引导 Spring应用呢???
    => 如注解驱动的 AnnotationConfigApplicationContext,尤其是非 Web应用场景下所创建的 ConfigurableApplicationContext实例就是 AnnotationConfigApplicationContext。
    => 个人认为SpringBoot引入 SpringApplication是对 Spring Framework的应用上下文生命周期的补充。

    传统的 Spring应用上下文生命的启动源于 ConfigurableApplicationContext对象的创建,运行则由其 refresh()方法引导,而终止于 close()方法的调用。
    Spring Framework内建的 ConfigurableApplicationContext实现类均继承与 抽象类 AbstractApplicationContext。
    在 AbstractApplicationContext#refresh() 方法执行过程中,伴随着组件 BeanFactory、Environment、ApplicationEventMulticaster 和 ApplicationListener 的创建,
    它们的职责分别涉及 Bean容器、Spring属性配置、Spring事件广播和监听。

    实际上,SpringApplication并未从本质上改变这些,因为 AbstractApplicationContext提供了扩展接口,
    如 setEnvironment(ConfigurableEnvironment)方法允许替换默认的 Environment对象,以及 initApplicationMulticaster 和 ApplicationListener Bean的机制。

    不过,这些扩展接口被 SpringApplication 在 Spring应用上下文调用 refresh()方法之前予以运用,
    在 SpringApplicationRunListener实现类 EventPublishingRunListener的帮助下,全新地引入 SpringBoot事件,并且间接地过渡到外部化配置,而后者是运维篇“超过外部化配置”重点讨论。

597页,完结。
【日期标记】2022-11-21 09:20:43 以上同步完成


聊天结束