共计 33448 个字符,预计需要花费 84 分钟才能阅读完成。
SpringBoot 根底
从 Spring 到 SpringBoot
-
Spring 通过 IOC 与AOP实现了企业级的开发框架,尽管组件代码是轻量级的,然而 配置文件却是重量级的 ,Spring Boot 则简化 Spring 利用开发,基于 约定大于配置(为大部分配置组件提供了默认配置)的思维,just run 就能创立一个独立的,产品级别的利用
- SpringBoot 框架并不是微服务框架,只是为微服务框架的组件构建提供了一个很好的 脚手架
- SpringBoot 提供了 J2EE 一站式解决方案;Spring Cloud 提供了分布式整体解决方案
Spring 生态
-
Spring 严格来说是一个生态,而非仅仅几个常见的框架,基本上笼罩了以下几个次要场景的开发
-
web 开发
- Spring Framework
-
数据拜访
- Spring Data
-
安全控制
- Spring Security
-
分布式
- Spring Cloud
- Spring Session
- ..
-
音讯服务
- Spring AMQP
-
批处理
- Spring Batch
在大型企业中,因为业务简单、数据量大、数据格式不同、数据交互格局繁冗,并非所有的操作都能通过交互 界面 进行解决。而有一些操作须要 定期 读取大 批量 的数据,而后进行一系列的后续 解决 。这样的过程就是 批处理
- …
-
-
能够说 Spring 以一己之力晋升了整个 Java 的开发生态,使得 Java 语言在支流开发场景中都能大显神通,正如 Spring 官网的首页图展现的那样
<img src=”https://images.demoli.xyz/image-20210924220823740.png” alt=”image-20210924220823740″ style=”zoom:67%;” />
SpringBoot 存在的意义
-
一般来说,Spring 生态的学习路线都是从 SSM(Spring、Spring MVC、MyBatis) 到 SpringBoot,SSM 中的 配置天堂 是升高开发效率的微小阻碍;除此之外,上述说的 Spring 生态的泛滥性能的搭建与配合应用同样意味着大量的配置,是否简化配置,甚至做到主动配置(约定大于配置的思维),以晋升业务开发效率?SpringBoot 应运而生。具体的劣势在以下几个方面:
- 能够创立独立的 Spring 利用
- 内嵌 Web 服务器
- 基于 starter 的场景主动配置
- 主动配置 Spring 与第三方性能
- 提供生产级别的监控、健康检查,内部化配置
- 无代码生成,无需编写 XML
-
SpringBoot 的毛病
- 版本迭代快
- 封装比拟深,查看源码难度比拟高
SpringBoot2.0
- 作为 SpringBoot 的新一代版本,SpringBoot2.0 在整个框架结构设计上都有重大降级,次要包含以下两局部:
响应式编程
<img src=”https://images.demoli.xyz/image-20210924223917397.png” alt=”image-20210924223917397″ />
- 提出了响应式技术栈,即以异步非阻塞的形式使得整个框架可能以更少的系统资源解决更多的并发申请,对应的底层 Web 开发框架是 Spring WebFlux,而不是传统的同步阻塞式的 SpringMVC,这一部分可参考后续对于[Spring WebFlux 的学习介绍]()
源码设计调整
- 齐全基于 JDK8 构建,同时兼容 JDK11 甚至 Java17,基于一些新的 Java 个性对外部源码进行了从新设计,比方 JDK8 之前,接口没有默认办法实现的特色,导致接口的实现类不得不实现全副的接口办法,即使只应用其中几个办法,为了解决此问题,Spring 中大量应用的适配器模式,应用适配器实现接口办法为空办法,再让子类去重写办法,而在 JDK8 后,一个 default 关键字就解决了,实现类无必要实现全副办法,因而大量的 adapter 就隐没了
HelloWorld 我的项目
环境配置
-
首先依照 SpringBoot 文档要求保障 JDK 版本为 8 或以上版本,Spring Framework 版本、maven 以及 gradle 版本参考要求即可
- 可参考具体版本的 SpringBoot 文档的 Getting Started 页面,比方 2.6.5 版本
我的项目实现
- IDEA 创立 maven 我的项目
-
引入 starters
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.5</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
- 父工程对立治理可能应用到的各个组件的版本
- 导入 Web 场景启动器以实现 Web 利用开发
-
创立入口类与 Controller
@SpringBootApplication public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class, args); } }
@RestController public class HelloController {@RequestMapping("/hello") public String handleHello() {return "Hello SpringBoor 2";} }
- 与 Spring MVC 开发的代码编写统一,最大的不同在于无需增加大量的配置
- 留神业务代码的包应和入口类在同一个包下,否则无奈解析业务代码中的的注解配置
-
启动运行
- 间接启动入口程序即可开启内置的服务器提供 Web 服务,实现本地测试
-
自定义配置
server: port: 8888
- 我的项目类门路(maven 工程的 resources 目录下 )增加名为
application.yml
配置文件,既能够做自在的配置 - 所有可做的配置参考官网文档
- 我的项目类门路(maven 工程的 resources 目录下 )增加名为
-
部署,应用 SpringBoot 提供的打包为可执行 jar 包的形式
<!-- 这个插件,能够将利用打包成一个可执行的 jar 包;--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
- 执行
mvn package
即可取得打包好的 jar 文件,在指标服务器执行java -jar ${可执行 jar 包门路}
即可运行整个工程 - 在 IDEA 中可能显示红色划线谬误示意找不到此 plugin,应该是 IDEA 的一个 bug
- 执行
SpringBoot 的两个特色
-
以 HelloWorld 我的项目为例钻研 SpringBoot 的两个个性
- 依赖治理
- 自动化配置(约定大于配置)
依赖治理
-
实质上就是应用 maven 父工程和 starter 启动器做依赖治理
- maven 父工程做依赖版本治理
- starter 场景启动器做依赖包治理
Maven 父工程
spring-boot-starter-parent-2.5.5.pom
如下所示,能够发现spring-boot-starter-parent
设置了默认的 Java 编译版本是 8,因而如果我的项目应用更高版本的话,须要在我的项目的 pom 文件中显式设置<java.version>
这个 properties
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.5</version>
</parent>
<artifactId>spring-boot-starter-parent</artifactId>
<packaging>pom</packaging>
<name>spring-boot-starter-parent</name>
<description>Parent pom providing dependency and plugin management for applications built with Maven</description>
<properties>
<java.version>1.8</java.version>
<resource.delimiter>@</resource.delimiter>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
spring-boot-dependencies-2.5.5.pom
:
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.5</version>
<packaging>pom</packaging>
<properties>
<activemq.version>5.16.3</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.91</appengine-sdk.version>
<artemis.version>2.17.0</artemis.version>
<aspectj.version>1.9.7</aspectj.version>
<assertj.version>3.19.0</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.3</awaitility.version>
<build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.10.22</byte-buddy.version>
<caffeine.version>2.9.2</caffeine.version>
<cassandra-driver.version>4.11.3</cassandra-driver.version>
<classmate.version>1.5.1</classmate.version>
....
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-amqp</artifactId>
<version>${activemq.version}</version>
</dependency>
...
</dependencies>
</dependencyManagement>
-
在 pom 文件中找到最终的父工程,即
spring-boot-dependencies
,这个父我的项目中定义了一堆 properties,其中规定了简直所有的支流组件的版本,所以这个spring-boot-dependencies
用来治理 SpringBoot 我的项目中的依赖版本,是 SpringBoot 利用的 版本管理中心,之后的各种援用依赖就不须要再注明版本号了- 实际上利用的是maven 的版本继承的机制
-
上述的机制也叫做 SpringBoot 中的 版本仲裁机制 ,也是一种约定大于配置的思维的体现,就是当时依照版本匹配的约定做好配置,放弃版本的统一,而不须要手动再配置, 然而如果有非凡的版本需要或者是其余的不存在与版本管理中心的非凡依赖,则间接在以后的 SpringBoot Maven 工程显式的指定版本或者依赖即可
-
例如,如果只是更改版本的话,只须要在我的项目 pom 文件中,应用
property
标签设置版本号即可<properties> <mysql.version>5.1.43</mysql.version> </properties>
-
- 其余的 SpringBoot maven 更具体的配置参考
-
如果应用 Gradle 搭建我的项目,参考
-
以搭建一般 Web 利用为例,其配置文件如下所示:
plugins { id 'org.springframework.boot' version '2.6.5' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'xyz.demoli' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' repositories {mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' } tasks.named('test') {useJUnitPlatform() }
- 能够发现 Gradle 以 Plugin 的模式提供了依赖版本治理
-
场景启动器
- 因为要开发 Web 利用所以在 HelloWorld 我的项目中引入了一个
spring-boot-starter-web
依赖,在后续的 SpringBoot 应用中会用到很多所谓的spring-boot-starter-*
依赖,也就是 场景启动器依赖 -
场景启动器的意义在于能够将某利用场景的所有可能用到的依赖打包到一起,实现只须要引入一个启动器依赖就能够引入全副场景依赖的目标,简化依赖导入的流程 ,以
spring-boot-starter-web
为例,如下图所示,tomcat、Spring MVC、Spring 等相干场景的依赖被对立打包在 starter 内导入到我的项目中 -
启动器除了打包依赖,当然也负责这些依赖的版本治理。启动器给出了所有依赖的适配版本,如果我的项目应用默认提供的版本能够同样在 pom 文件中进行显式定制
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.5.5</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.5.5</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> <version>2.5.5</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.5.5</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.10</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.10</version> <scope>compile</scope> </dependency> </dependencies>
- 场景启动器不仅蕴含了特定开发场景须要的所有依赖 还蕴含反对该场景的组件默认配置,具体参考下边的主动配置原理局部
-
能够自定义场景启动器以满足定制化的开发需要,可参考文章[SpringBoot Starter 开发]()
- 个别本人开发的或者是第三方的 starter 的名称都是
*-spring-boot-starter
- 个别本人开发的或者是第三方的 starter 的名称都是
- SpringBoot 官网反对的 starters
主动配置原理
- 以 HelloWorld 我的项目的 Web 利用开发场景为例
从程序入口看起
- 入口 main 办法中的
SpringApplication
的run
办法是整个程序的理论入口,此办法 可实现 Spring 环境的创立 ,包含 各种 Bean 的注册以及后续要介绍的各种注解的解析与作用 - 入口 main 办法所在的入口类上有一个要害的注解,即
@SpringBootApplication
, 被这个注解标注的类是 SpringBoot 的主配置类,执行这个类的 main 办法来启动 SpringBoot 利用 -
该注解实际上是一个 组合注解
@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 { @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {}; @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true;}
- 留神
@AliasFor
注解的作用是申明被标记的 SpringBootApplication 注解的属性与@AliasFor
的annotation
属性申明的注解的同名属性或者是attribute
指定的属性是同一个属性
- 留神
SpringBootConfiguration 注解
-
用来指定作用的 入口类是 Spring 配置类,在包扫描后失效
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true;}
@SpringBootApplication public class SpringBootHelloWorldQuickApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringBootHelloWorldQuickApplication.class, args); SpringBootHelloWorldQuickApplication application = context.getBean("springBootHelloWorldQuickApplication", SpringBootHelloWorldQuickApplication.class); System.out.println(application); } }
- 能够从 IoC 容器中获取主配置类 Bean
-
只管启动类同时被
ComponentScan
注解和Configuration
注解作用,然而ComponentScan
做注解解析时,不会因为启动类被Configuration
作用,而反复的去解析启动类上的注解 ,这一点在ComponentScanAnnotationParser
的 parse 办法中有定义,该办法中增加了一个AbstractTypeHierarchyTraversingFilter
, 该 filter 的 match 办法会过滤掉启动类public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) { // ... ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); // ... scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {protected boolean matchClassName(String className) {return declaringClass.equals(className); } }); return scanner.doScan(StringUtils.toStringArray(basePackages)); }
ComponentScan 注解
-
默认的,主程序所在的包以及所有的下层级的包 都会被扫描解析
- 也能够应用
@SpringBootApplication
注解的scanBasePackages
属性指定 自定义的包扫描地位 - 或者间接 应用
@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
三个注解代替@SpringBootApplication
,而后再应用@ComponentScan
配置包扫描门路即可
- 也能够应用
-
SpringBootApplication
注解中的ComponentScan
注解应用了 两个主动扫描的排除规定@ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication {// ...}
TypeExcludeFilter
次要是做 扩大应用,能够通过继承此类实现自定义扫描排除规定AutoConfigurationExcludeFilter
设置不扫描所有的既是配置类又是主动配置类的组件,以避免出现反复注册- 参考 1
- 参考 2
EnableAutoConfiguration 注解
-
EnableAutoConfiguration
注解顾名思义用来 开启 starter 提供的主动配置(主动配置的性能是以主动配置类的模式作为 Bean 进行注册后提供),该注解实质上还是 合成注解@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {};}
-
@AutoConfigurationPackage
注解@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Registrar.class}) public @interface AutoConfigurationPackage {}
-
Import
注解作用的Registar
类实现了ImportBeanDefinitionRegistrar
接口,因而实际上会调用其registerBeanDefinitions
办法static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {Registrar() { } // 此办法被调用 public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()); } public Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata)); } } public static void register(BeanDefinitionRegistry registry, String... packageNames) {if (registry.containsBeanDefinition(BEAN)) {BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues(); constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } else {GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(2); registry.registerBeanDefinition(BEAN, beanDefinition); } }
- 通过断点剖析可得,实际上是将以后 注解所标注的类(即入口类)所在包的门路封装到
org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages
类型的 Bean 进行注册
- 通过断点剖析可得,实际上是将以后 注解所标注的类(即入口类)所在包的门路封装到
-
-
@Import({AutoConfigurationImportSelector.class})
,因为AutoConfigurationImportSelector
类实现了DeferredImportSelector
接口,所以该类会被作为配置类解析的最初一步,ConfigurationClassParser
类的parse
办法会调用AutoConfigurationImportSelector
类的process
办法public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {// ...}
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { //... // 外围办法 getAutoConfigurationEntry AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(this.getAutoConfigurationMetadata(), annotationMetadata); //... }
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { //... // 外围办法是 getCandidateConfigurations 通过该办法能够获取要注册的所有配置类汇合,后续的操作都是对此配置类汇合的 List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); // 对主动配置类汇合做一些调整 configurations = this.removeDuplicates(configurations); Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = this.filter(configurations, autoConfigurationMetadata); this.fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); } }
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 要害办法为 loadFactoryNames List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; }
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { // org.springframework.boot.autoconfigure.EnableAutoConfiguration String factoryTypeName = factoryType.getName(); // 要害办法 loadSpringFactories return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { // ... try {Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); // ... }
-
通过查看源码可知,实际上会 扫描
org.springframework.boot:spring-boot-autoconfigure
包下的META-INF/spring.factories
文件 ,会读取文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration
项下的所有主动配置类- 实质上是扫描类门路下所有包内的
META-INF/spring.factories
文件,然而 starter 组件包一起其余依赖包内都没有META-INF/spring.factories
文件,因而在自定义 starter 时,为了使自定义的默认配置失效,也须要向 starter 中照猫画虎的增加这么一个文件才行
- 实质上是扫描类门路下所有包内的
-
getAutoConfigurationEntry
办法对从getCandidateConfigurations
中取得的所有主动配置类的汇合进行了肯定的批改,次要包含- 去重
-
排除掉
@EnableAutoConfiguration
注解中的 exclude 和 excludeName 属性申明的配置类getExclusions
办法
-
排除掉被条件拆卸注解作用的主动配置类中的不符合条件的局部
filter
办法- 可参考下边的
@Condition
注解的原理局部
-
-
主动配置类
- SpringBoot 中所有的默认配置都被集中管理在一个我的项目中,即
spring-boot-autoconfigure
-
所有的 SpringBoot 的官网场景启动器都会有
spring-boot-starter
这个依赖 ,这个依赖的又有一个依赖就是spring-boot-autoconfigure
。查看其源码,能够发现 有各种场景下的默认配置类,个别这些默认配置类都会以**AutoConfiguration
命名 , 主动配置类在满足特定的条件后被注册到 Spring 容器中,则其主动配置能够在 SpringBoot 我的项目中失效-
spring-boot-autoconfigure
我的项目除了提供各个场景下的主动配置类,还提供了对应的配置类供用户实现自定义配置,以晋升灵活性。application.yaml
配置文件中的可用配置都会映射到配置类**Properties
中,**AutoConfiguration
主动配置类会尝试加载注册到**Properties
中的用户自定义配置,使其失效(如果有的话)// 以 server.port=9000 为例 对应的配置类是 org.springframework.boot.autoconfigure.web.ServerProperties @ConfigurationProperties( prefix = "server", ignoreUnknownFields = true ) public class ServerProperties { private Integer port; private InetAddress address; //... public void setPort(Integer port) {this.port = port;} //.. }
- 应用的要害注解是
@ConfigurationProperties
- 应用的要害注解是
-
配置的按需加载
-
前边说到
META-INF/spring.factories
中定义的主动配置类不会全副都被注册失效,只会在特定条件下失效,这种按条件注册的性能依赖于下边的一系列注解// 以批处理的主动配置类为例 @Configuration(proxyBeanMethods = false) @ConditionalOnClass({JobLauncher.class, DataSource.class}) @AutoConfigureAfter({HibernateJpaAutoConfiguration.class}) @ConditionalOnBean({JobLauncher.class}) @EnableConfigurationProperties({BatchProperties.class}) @Import({BatchConfigurerConfiguration.class, DatabaseInitializationDependencyConfigurer.class}) public class BatchAutoConfiguration {//...}
- 要害注解是
@ConditionalOnClass
、@ConditionalOnBean
,顾名思义,其要求注解属性中的类被导入后,才会使得BatchAutoConfiguration
的配置失效,而JobLauncher
等类只有在批处理的场景启动器依赖导入或者是相干的次要第三方依赖导入后才会被导入到工程中,由此实现主动配置的按需加载
- 要害注解是
@Conditional 系列注解
@Conditional 扩大注解 | 作用(判断是否满足以后指定条件) |
---|---|
@ConditionalOnJava | 零碎的 java 版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定 Bean |
@ConditionalOnMissingBean | 容器中不存在指定 Bean |
@ConditionalOnExpression | 满足 SpEL 表达式指定 |
@ConditionalOnClass | 零碎中有指定的类 |
@ConditionalOnMissingClass | 零碎中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的 Bean,或者这个 Bean 是首选 Bean |
@ConditionalOnProperty | 零碎中指定的属性是否有指定的值 |
@ConditionalOnResource | 类门路下是否存在指定资源文件 |
@ConditionalOnWebApplication | 以后是 web 环境 |
@ConditionalOnNotWebApplication | 以后不是 web 环境 |
@ConditionalOnJndi | JNDI 存在指定项 |
-
上边列举的都是能够间接拿来用的条件拆卸的注解,即
@Conditional
的扩大注解,@Conditional
注解自身的应用就绝对繁琐了,须要本人定义判断逻辑@Component // 依据 OnSmtpEnvCondition 中的条件判断是否注册 SmtpMailService @Conditional(OnSmtpEnvCondition.class) public class SmtpMailService implements MailService {...}
// 实现 Condition 接口 public class OnSmtpEnvCondition implements Condition {public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return "true".equalsIgnoreCase(System.getenv("smtp")); } }
OnSmtpEnvCondition
的条件是存在环境变量smtp
,值为true
。这样就能够通过环境变量来管制是否创立SmtpMailService
- 这个系列的依赖能够作用在办法上,与
@Bean
一起应用,决定该办法的返回对象是否要注册到容器中,也能够作用在类上,与 @Configuration 一起应用,决定该配置类整体是否失效 -
以
ConditionalOnClass
注解为例,实质上起作用的是OnClassCondition
类,而该类实际上实现了AutoConfigurationImportFilter
接口,通过其match
办法判断条件是否失效,而match
办法判断是否失效的过程则是在前文提到的AutoConfigurationImportSelector
类的filter
办法中实现的@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional({OnClassCondition.class}) public @interface ConditionalOnClass {Class<?>[] value() default {}; String[] name() default {};}
class OnClassCondition extends FilteringSpringBootCondition {// ...}
public interface AutoConfigurationImportFilter {boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata); }
@Profile 注解
-
应用该注解定义不同环境下的条件拆卸
- dev 开发环境
- test 测试环境
- prod 生产环境
@Configuration @ComponentScan public class AppConfig { @Bean @Profile("!test") ZoneId createZoneId() {return ZoneId.systemDefault(); } @Bean @Profile("test") ZoneId createZoneIdForTest() {return ZoneId.of("America/New_York"); } }
- 如果以后的 Profile 设置为
test
,则 Spring 容器会调用createZoneIdForTest()
创立ZoneId
,否则,调用createZoneId()
创立ZoneId
- 在运行程序时,加上 JVM 参数
-Dspring.profiles.active=test
就能够指定以test
环境启动,也能够指定多个环境-Dspring.profiles.active=test,master
补充
-
用户定制化配置的几种形式
-
间接应用
@Configuration
+@Bean
替换底层的组件- 底层大量应用了
@ConditionOnMissingBean
以优先应用用户注册的 Bean - 举荐首先查看文档或者去大抵看看源码的配置要点
- 底层大量应用了
- 通过看文档或者间接查看主动配置中的配置文件类,对应的向零碎配置文件
application.yaml
中增加自定义配置 -
应用组件提供的
customizer 类
或者configurer 类
- 通过查看文档或材料取得相干常识
-
-
除了
..AutoConfiguration
系列的主动配置类与..Properties
系列的配置文件类,SpringBoot 中还有两种类型的类即:..Configurer
是 SpringBoot 中的用于用户扩大配置的接口或者类,例如WebMvcConfigurer
..Customizer
用来进行定制配置,例如WebServerFactoryCustomizer
- 参考[应用 SpringBoot 进行 Web 开发的文章]()
-
语法提醒,通过增加下边的插件提供配置文件中的关联提醒的性能
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </excludes> </configuration> </plugin> </plugins> </build> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
- 同时留神在打包插件中配置打包时不蕴含该依赖,因为只是开发时要用到,没必要将其打包
最佳实际
-
依据需要引入官网或者第三方提供的 starter
- SpringBoot 提供的所有官网 starter
-
个别都须要对场景做自定义配置,能够通过查官网的配置文档,或者间接从源码层面查看配置项
- SpringBoot 官网提供的所有可用配置
- 依照需要批改配置,或者增加自定义组件
-
trick:在配置文件中 应用
debug=true
以 debug 模式运行 ,控制台会有主动配置报告, 能够看到失效的主动配置类与未失效的主动配置类以及其未失效的起因Positive matches:
这个栏目下是失效的主动配置类Negative matches:
这个栏目下是未失效的主动配置类,以及未失效的起因(这一点能够用来做自定义配置失败的 debug)- 如果引入了日志框架,能够将日志打印等径设置为 debug,成果是相似的,当然更举荐上边的形式
-
应用 lombok 优化 Bean 开发,SpringBoot 曾经治理了其版本,因而间接引入 maven 依赖即可,而后在 IDEA 中装置 lombok 插件以优化应用体验
@Data
等价于@Getter
与@Setter
组合@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
-
@Log4j2
- 间接为以后类注入一个
org.apache.logging.log4j.Logger
类型的名为log
的属性,用来做日志打印 - 须要留神的是必须首先增加
log4j2
的依赖
- 间接为以后类注入一个
-
应用 devtools 热更新性能,能够增加 maven 依赖也能够在创立我的项目时在 Spring initializer 中指定
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency>
- 此时只需 rebuild 就能够使更改失效,其工作机制本质上就是 监听 classpath 下的文件的变动,一旦监听到变动就重启服务器加载变动
- 除了能够监听 classpath 门路下的文件的变动,还能够监听自定义的目录下的文件变动,以达到文件变动后,服务器重新启动的目标,参照 codesheep-SpringBoot 热部署加持
- 举荐应用 Spring Initializer 创立我的项目(IDEA 自带)能够通过勾选的形式做一系列场景配置,生成的我的项目主动引入相干依赖以及写好主配置类,以及特定场景的对应的文件构造,比方 Web 场景下会创立类门路下的 static 文件夹和 template 文件夹等等
SpringBoot 配置文件
配置文件
-
SpringBoot 默认应用两种配置文件
application.properties
application.yml
Yaml 语法
-
介绍一下 yaml 的根本语法
- 应用缩进示意层级关系
- 缩进时不容许应用 tab 键,只容许应用空格
- 缩进的空格数不重要,只有雷同层级的元素左侧对其即可
- 大小写敏感
- 示意数据应用 k: v 的模式展现 冒号后边必须要有一个空格
-
数据类型
-
字面量:字符串,数字,bool,date
-
留神字符串类型的值,不须要加引号,如果加的话,
''
和""
别离对应着对其中的字符串中的转义字符进行字符串输入和本义输入- 当字符串中有特殊字符时,务必加引号
-
-
对象,Map
-
对象还是用键值对的模式来示意
friends: lastName: zhangsan age: 20
-
行内写法
friends: {lastName: zhangsan,age: 20}
- 留神行内写法中的对象属性冒号后边也得有空格
-
-
数组
-
用
-
示意数组中的一个元素pets: - cat - dog - pig
-
行内写法
pets: [cat,dog,pig]
-
-
配置文件占位符
server: port: 8081 bean: # 随机数的拼接 lastName: hello${random.uuid} age: ${random.int} boss: false birth: 1997/12/10 maps: {k1: v1,k2: v2} lists: [lisi,zhaoliu] dog: # 应用: 指定当属性不存在时的默认值 name: ${bean.Name:lee}_dog # 复用前边的设置 lastName: ${bean.lastName:gee} age: 2
-
随机数占位符
random.value
random.int
random.int(int max)
random.long
- 两种类型的配置文件都反对占位符的性能
-
-
Profile
-
Profile 是 Spring 对多环境的反对,程序开发的时候有几个环节 开发 - 测试 - 部署,三个场景有三个环境,不同的环境下配置文件也可能不同,能够通过激活,指定参数等形式疾速切换环境,同时主动切换指定场景的配置文件
- 为每一个环境指定一个配置文件,每一个配置文件对应一个 profile,命名格局为 application-{profile}.properties 例如:application-dev.properties application-prod.properties
- 当工程中有多个配置文件的时候,默认加载的是不带 profile 的配置文件也就是 application.properties/application.yml
yaml 文档块
- 对于 properties 类型的配置文件来说,须要定义多个配置文件对应不同的 profile,然而对于 yaml 配置文件来说,能够把多个 profile 通过文档块的模式写到一个文件中
-
所谓的文档块就是在 yml 配置文件中用
---
符号宰割为多个块,每一个块就对应着一个 profile 的配置文件server: port: 8081 spring: profiles: active: prod --- server: port: 8082 spring: profiles: dev --- server: port: 8083 spring: profiles: prod
- 非默认配置文件文档块应用 spring.profiles 申明文档块属于哪个环境
环境切换形式
-
在 默认的不指定 profile 的配置文件 中进行激活
spring: profiles: active: dev #profile 名字
-
JVM 参数
- 在 idea 的 edit configuration 中配置 vm arguments
-Dspring.profiles.active=dev
- 优先级高于配置文件
- 在 idea 的 edit configuration 中配置 vm arguments
- 命令行
-
- 在 idea 的 edit configuration 中配置 program arguments
--spring.profiles.active=dev
- 在 idea 的 edit configuration 中配置 program arguments
-
- 这种配置办法优先级最高
默认配置文件优先级
-
所有地位的默认文件都会被加载,高优先级配置的内容会笼罩低优先级配置的内容,不反复的内容则是补充内容
- root:./config/application.yaml
- root:./application.yaml
- classpath:/config/application.yaml
- classpath:/application.yaml
- 参考
内部配置文件
- 所谓应用内部配置文件指的是对 SpringBoot Application 进行部署的时候能够指定包内部的配置文件,来代替外部的配置,以进步配置的自由度(配置批改后不用从新打包)
-
依照优先级排列
- 命令行中间接指定配置参数:
java -jar ***.jar --server.port=8090
优先级最高 - 命令行中指定配置文件:能够应用
--spring.config.location=...
指定配置文件的地位 -
jar 包内部的 application-{profile}.properties 或者 application.yml(带 spring.profile)配置文件
- 带 profile 的优先(profile 的指定形式就是前边说的那三种)
- jar 包外指的是与 jar 包同目录的配置文件,会主动执行加载,不必配置其余命令行参数
- jar 包内部的 application.properties 或者 application.yml(不带 spring.profile)配置文件
- jar 包外部的 application-{profile}.properties 或者 application.yml(带 spring.profile)配置文件
-
jar 包外部的 application.properties 或者 application.yml(不带 spring.profile)配置文件
- 如果同时呈现则 application.properties 优先
- 命令行中间接指定配置参数:
- 参考
SpringBoot 事件监听
- 所谓的事件监听就是指 SprigBoot 为 利用启动的不同期间提供了能够监听的钩子组件,容许利用执行到特定的阶段时,执行注册好的钩子函数
- 注册钩子函数的形式就是 实现 SpringBoot 提供的特定的接口 ,而后 注册成为 Spring IoC 中的组件 或者是在 以后利用的类门路下的 META-INF 文件夹下的 spring.factories 文件中进行配置,具体的看下边的例子
几个罕用的事件回调机制
-
前三个须要配置在 spring.factories 中,后两个须要注册成为 IoC 组件
-
org.springframework.context.ApplicationContextInitializer
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {void initialize(C var1); }
public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) {System.out.println("ApplicationContextInitializer initialize"); } }
org.springframework.context.ApplicationContextInitializer=\ com.springboot.springboot.listenerAndRunner.HelloApplicationContextInitializer
-
org.springframework.boot.SpringApplicationRunListener
public interface SpringApplicationRunListener {default void starting() { } default void environmentPrepared(ConfigurableEnvironment environment) { } default void contextPrepared(ConfigurableApplicationContext context) { } default void contextLoaded(ConfigurableApplicationContext context) { } default void started(ConfigurableApplicationContext context) { } default void running(ConfigurableApplicationContext context) { } default void failed(ConfigurableApplicationContext context, Throwable exception) {}}
public class HelloSpringApplicationRunListener implements SpringApplicationRunListener { /** * 须要提供此构造函数,否则报错 * * @param application SpringApplication * @param args 参数 */ public HelloSpringApplicationRunListener(SpringApplication application, String[] args) { } @Override public void starting() {System.out.println("SpringApplicationRunListener starting"); } @Override public void environmentPrepared(ConfigurableEnvironment environment) {System.out.println("SpringApplicationRunListener environmentPrepared" + environment.getSystemProperties().get("os.name")); } @Override public void contextPrepared(ConfigurableApplicationContext context) {System.out.println("SpringApplicationRunListener contextPrepared"); } @Override public void contextLoaded(ConfigurableApplicationContext context) {System.out.println("SpringApplicationRunListener contextLoaded"); } @Override public void started(ConfigurableApplicationContext context) {System.out.println("SpringApplicationRunListener started"); } @Override public void running(ConfigurableApplicationContext context) {System.out.println("SpringApplicationRunListener running"); } @Override public void failed(ConfigurableApplicationContext context, Throwable exception) {System.out.println("SpringApplicationRunListener failed"); } }
org.springframework.boot.SpringApplicationRunListener=\ com.springboot.springboot.listenerAndRunner.HelloSpringApplicationRunListener
-
org.springframework.context.ApplicationListener
@FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E var1); }
public class HelloApplicationListener implements ApplicationListener<ApplicationPreparedEvent> { @Override public void onApplicationEvent(ApplicationPreparedEvent applicationPreparedEvent) {System.out.println(applicationPreparedEvent.getTimestamp()); } }
org.springframework.context.ApplicationListener=\ com.springboot.springboot.listenerAndRunner.HelloApplicationListener
-
org.springframework.boot.ApplicationRunner
@FunctionalInterface public interface ApplicationRunner {void run(ApplicationArguments args) throws Exception; }
@Component public class HelloApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception {System.out.println("ApplicationRunner run" + args); } }
-
org.springframework.boot.CommandLineRunner
@FunctionalInterface public interface CommandLineRunner {void run(String... args) throws Exception; }
@Component public class HelloCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception {System.out.println("CommandLineRunner run" + Arrays.toString(args)); } }
-
从启动流程剖析回调机制
-
在 SpringApplication 的 run 办法上打断点剖析启动流程
-
创立 SpringApplication 对象 org.springframework.boot.SpringApplication#100
public SpringApplication(Class<?>... primarySources) {this((ResourceLoader)null, primarySources); } public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.sources = new LinkedHashSet(); this.bannerMode = Mode.CONSOLE; this.logStartupInfo = true; this.addCommandLineProperties = true; this.addConversionService = true; this.headless = true; this.registerShutdownHook = true; this.additionalProfiles = new HashSet(); this.isCustomEnvironment = false; this.lazyInitialization = false; this.resourceLoader = resourceLoader; // 必须提供非空的主配置类,否则无奈启动 Assert.notNull(primarySources, "PrimarySources must not be null"); // 保留主配置类 this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); // 判断 web 利用的类型,留神这里并不是默认就是 web 利用,实际上其外部会判断是不是 web 利用,如果不是会返回 NONE,是 web 利用的话,也要辨别是哪种类型 1. REACTIVE 2,SERVLET this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 从类门路下的 META-INF/spring.factories 中取得 org.springframework.context.ApplicationContextInitializer 并保存起来 this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 同样的从类门路下的 META-INF/spring.factories 中取得配置的 org.springframework.context.ApplicationListener 并保存起来 this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); // 从多个配置类中找到含有 main 办法的入口类 this.mainApplicationClass = this.deduceMainApplicationClass();}
- 程序入口的 main 办法未必肯定在主配置类上,因而在晓得主配置类的前提下寻找含有 main 办法的入口类并不抵触
-
利用默认加载的 ApplicationContextInitializer 如下,红色标记为自定义
-
利用默认加载的 ApplicationListener 如下,红色标记为自定义
-
执行 run 办法 org.springframework.boot.SpringApplication#138
public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 申明一个 ioc 容器 ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); this.configureHeadlessProperty(); // 获取 org.springframework.boot.SpringApplicationRunListener // 从类门路的 META-INF 的 spring.factories 中取得配置好的 runlisteners SpringApplicationRunListeners listeners = this.getRunListeners(args); // 回调执行所有 SpringApplicationRunListener 的 starting 办法 listeners.starting(); Collection exceptionReporters; try { // 封装命令行参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 筹备环境, 创立环境并回调执行 SpringApplicationRunListener 的 environmentPrepared 办法 ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); this.configureIgnoreBeanInfo(environment); // 打印 spring 的图标 Banner printedBanner = this.printBanner(environment); // 创立 ioc 容器,并且依据前边的判断的 web 利用的类型来创立并返回不同类型的容器 context = this.createApplicationContext(); exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); // 筹备上下文,将 environment 环境存储到容器中,并回调执行所有的保留的 ApplicationContextInitializer 的 initialize 办法,执行 SpringApplicationRunListeners 的 contextPrepared 办法,在 prepareContext 函数执行的最初执行 SpringApplicationRunListeners 的 contextLoaded 办法 this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 刷新环境 IOC 容器初始化 // 如果是 web 利用的话,还会创立 Tomcat 容器,并启动 Tomcat 容器 // 扫描,创立加载 所有的组件(配置类,组件,主动配置都会在这里失效)this.refreshContext(context); // 空办法,可能只是为了与前几个版本进行兼容吧 this.afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } // 回调执行所有 SpringApplicationRunListeners 的 started 办法 listeners.started(context); // 从 IOC 容器中取得 ApplicationRunner,和 CommandLineRunner,并先后进行回调其 run 办法 this.callRunners(context, applicationArguments); } catch (Throwable var10) {this.handleRunFailure(context, var10, exceptionReporters, listeners); throw new IllegalStateException(var10); } try { // 回调执行所有 SpringApplicationRunListeners 的 running 办法 listeners.running(context); // 整个 springboot 利用启动实现当前返回 ioc 容器 return context; } catch (Throwable var9) {this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } }
-
须要留神,注册自定义的
SpringApplicationRunListener
时,除了实现SpringApplicationRunListener
之外,还必须要提供特定构造的构造函数,否则会创立实例失败,这是因为在getRunListeners
源码中是这样创立SpringApplicationRunListener
实例的private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class[]{SpringApplication.class, String[].class}; // getSpringFactoriesInstances 办法提供了 this 与 args 两个构造函数的参数 return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); } private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = this.getClassLoader(); Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; } private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {List<T> instances = new ArrayList(names.size()); Iterator var7 = names.iterator(); while(var7.hasNext()) {String name = (String)var7.next(); try {Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); // 获取特定构造的构造函数以初始化,否则报错 Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); T instance = BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable var12) {throw new IllegalArgumentException("Cannot instantiate" + type + ":" + name, var12); } } return instances; }
-
-
总结执行机会
- 进入动态 run 办法
- 创立 SpringApplication 实例
- SpringApplication 的实例 run 办法执行,申明 IoC 容器
- 执行 SpringApplicationRunListener 的 starting 办法
- 执行 SpringApplicationRunListener 的 environmentPrepared 办法
- 打印 SpringBoot 图标,创立 IoC 容器
- 执行 ApplicationContextInitializer 的 initialize 办法
- 执行 SpringApplicationRunListener 的 contextPrepared 办法
- 执行 SpringApplicationRunListener 的 contextLoaded 办法
- 执行 refreshContext 办法,初始化 IoC 容器,如果是 Servlet Web 利用,会依据配置创立对应的 Servlet 容器,比方 Tomcat,并启动,除此之外,会扫描,创立,加载所有的组件(也包含主动配置的失效,即对应的一系列注解的失效)
- 执行 SpringApplicationRunListener 的 started 办法
- 执行 ApplicationRunner 的 run 办法
- 执行 CommandLineRunner 的 run 办法
- 执行 SpringApplicationRunListener 的 running 办法
- 至于 ApplicationListener,依据其泛型中申明的事件不同,其 onApplicationEvent 办法的执行工夫也不同,具体情况具体分析
参考
- SpringBoot 官网文档
- SpringBoot 更新日志
-
B 站视频材料
- 配套文档