关于java:Spring-Boot全面总结超详细建议收藏

4次阅读

共计 34975 个字符,预计需要花费 88 分钟才能阅读完成。

前言:本文十分长,倡议先 mark 后看,兴许是最初一次写这么长的文章

阐明:后面有 4 个大节对于 Spring 的基础知识

别离是:IOC 容器、JavaConfig、事件监听、SpringFactoriesLoader 详解

它们占据了本文的大部分内容

尽管它们之间可能没有太多的分割,但这些常识对于了解 Spring Boot 的外围原理至关重要,如果你对 Spring 框架烂熟于心,齐全能够跳过这 4 个大节。正是因为这个系列的文章是由这些看似不相干的知识点组成,因而取名常识清单。

在过来两三年的 Spring 生态圈,最让人兴奋的莫过于 Spring Boot 框架。或者从命名上就能看出这个框架的设计初衷:疾速的启动 Spring 利用。因此 Spring Boot 利用实质上就是一个基于 Spring 框架的利用,它是 Spring 对“约定优先于配置”理念的最佳实际产物,它可能帮忙开发者更疾速高效地构建基于 Spring 生态圈的利用。

那 Spring Boot 有何魔法

主动配置、起步依赖、Actuator、命令行界面(CLI) 是 Spring Boot 最重要的 4 大外围个性,其中 CLI 是 Spring Boot 的可选个性,尽管它功能强大,但也引入了一套不太惯例的开发模型,因此这个系列的文章仅关注其它 3 种个性。如文章题目,本文是这个系列的第一局部,将为你关上 Spring Boot 的大门,重点为你分析其启动流程以及主动配置实现原理。要把握这部分核心内容,了解一些 Spring 框架的基础知识,将会让你事倍功半。

我把 Spring Boot 相干的文章整顿成了 PDF,关注公众号 Java 后端 并回复 666 下载。

一、抛砖引玉:摸索 Spring IoC 容器

如果有看过 SpringApplication.run()办法的源码,Spring Boot 简短无比的启动流程肯定

会让你抓狂,透过景象看实质.

SpringApplication 只是将一个典型的 Spring 利用的启动流程进行了扩大,因而,透彻了解, Spring 容器是关上 Spring Boot 大门的一把钥匙。

1.1、Spring IoC 容器

能够把 Spring IoC 容器比作一间餐馆,当你来到餐馆,通常会间接招呼服务员:点菜!至于菜的原料是什么?如何用原料把菜做进去?可能你基本就不关怀。

IoC 容器也是一样,你只须要通知它须要某个 bean,它就把对应的实例(instance)扔给你,至于这个 bean 是否依赖其余组件,怎么实现它的初始化,基本就不须要你关怀。

作为餐馆,想要做出菜肴,得晓得菜的原料和菜谱,同样地,IoC 容器想要治理各个业务对象以及它们之间的依赖关系,须要通过某种路径来记录和治理这些信息。

BeanDefinition 对象就承当了这个责任:容器中的每一个 bean 都会有一个对应的 BeanDefinition 实例,该实例负责保留 bean 对象的所有必要信息,包含 bean 对象的 class 类型、是否是抽象类、构造方法和参数、其它属性等等。

当客户端向容器申请相应对象时,容器就会通过这些信息为客户端返回一个残缺可用的 bean 实例。

原材料曾经筹备好(把 BeanDefinition 看着原料),开始做菜吧,等等,你还须要一份菜谱,BeanDefinitionRegistry 和 BeanFactory 就是这份菜谱,BeanDefinitionRegistry 形象出 bean 的注册逻辑,而 BeanFactory 则形象出了 bean 的治理逻辑,而各个 BeanFactory 的实现类就具体承当了 bean 的注册以及管理工作。

它们之间的关系就如下图:

BeanFactory、BeanDefinitionRegistry 关系图(来自:Spring 揭秘)

DefaultListableBeanFactory 作为一个比拟通用的 BeanFactory 实现,它同时也实现了 BeanDefinitionRegistry 接口,因而它就承当了 Bean 的注册管理工作。从图中也能够看出,BeanFactory 接口中次要蕴含 getBean、containBean、getType、getAliases 等治理 bean 的办法,而 BeanDefinitionRegistry 接口则蕴含 registerBeanDefinition、removeBeanDefinition、getBeanDefinition 等注册治理 BeanDefinition 的办法。

上面通过一段简略的代码来模仿 BeanFactory 底层是如何工作的:

// 默认容器实现 DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory(); // 依据业务对象结构相应的 BeanDefinition AbstractBeanDefinition definition = new RootBeanDefinition(Business.class,true); // 将 bean 定义注册到容器中 beanRegistry.registerBeanDefinition(beanName,definition); // 如果有多个 bean,还能够指定各个 bean 之间的依赖关系 // ........  // 而后能够从容器中获取这个 bean 的实例 // 留神:这里的 beanRegistry 其实实现了 BeanFactory 接口,所以能够强转,// 单纯的 BeanDefinitionRegistry 是无奈强制转换到 BeanFactory 类型的 BeanFactory container = (BeanFactory)beanRegistry; Business business = (Business)container.getBean(beanName);

这段代码仅为了阐明 BeanFactory 底层的大抵工作流程. 理论状况会更加简单,比方 bean 之间的依赖关系可能定义在内部配置文件 (XML/Properties) 中、也可能是注解形式。

Spring IoC 容器的整个工作流程大抵能够分为两个阶段:

①、容器启动阶段

容器启动时,会通过某种路径加载 Configuration MetaData。除了代码形式比拟间接外,在大部分状况下,容器须要依赖某些工具类,比方:BeanDefinitionReader,BeanDefinitionReader 会对加载的 Configuration MetaData 进行解析和剖析,并将剖析后的信息组装为相应的 BeanDefinition,最初把这些保留了 bean 定义的 BeanDefinition,注册到相应的 BeanDefinitionRegistry,这样容器的启动工作就实现了。

这个阶段次要实现一些筹备性工作,更侧重于 bean 对象治理信息的收集,当然一些验证性或者辅助性的工作也在这一阶段实现。

来看一个简略的例子吧,过往,所有的 bean 都定义在 XML 配置文件中,上面的代码将模仿

BeanFactory 如何从配置文件中加载 bean 的定义以及依赖关系:

// 通常为 BeanDefinitionRegistry 的实现类,这里以 DeFaultListabeBeanFactory 为例 BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory();  // XmlBeanDefinitionReader 实现了 BeanDefinitionReader 接口,用于解析 XML 文件 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl(beanRegistry); // 加载配置文件 beanDefinitionReader.loadBeanDefinitions(classpath:spring-bean.xml);  // 从容器中获取 bean 实例 BeanFactory container = (BeanFactory)beanRegistry; Business business = (Business)container.getBean(beanName);

②、Bean 的实例化阶段

通过第一阶段,所有 bean 定义都通过 BeanDefinition 的形式注册到 BeanDefinitionRegistry 中当某个申请通过容器的 getBean 办法申请某个对象,或者因为依赖关系容器须要隐式的调用 getBean 时,就会触发第二阶段的流动:容器会首先查看所申请的对象之前是否曾经实例化实现。

如果没有,则会依据注册的 BeanDefinition 所提供的信息实例化被申请对象,并为其注入依赖。

当该对象拆卸结束后,容器会立刻将其返回给申请办法应用。BeanFactory 只是 Spring IoC 容器的一种实现,如果没有非凡指定,它采纳采纳提早初始化策略:只有当拜访容器中的某个对象时,才对该对象进行初始化和依赖注入操作。

而在理论场景下,咱们更多的应用另外一种类型的容器:ApplicationContext,它构建在 BeanFactory 之上,属于更高级的容器,除了具备 BeanFactory 的所有能力之外,还提供对事件监听机制以及国际化的反对等。它治理的 bean,在容器启动时全副实现初始化和依赖注入操作。

1.2、Spring 容器扩大机制

IoC 容器负责管理容器中所有 bean 的生命周期,而在 bean 生命周期的不同阶段,Spring 提供了不同的扩大点来扭转 bean 的命运。在容器的启动阶段,BeanFactoryPostProcessor 容许咱们在容器实例化相应对象之前,对注册到容器的 BeanDefinition 所保留的信息做一些额定的操作,比方批改 bean 定义的某些属性或者减少其余信息等。

如果要自定义扩大类,通常须要实现.

org.springframework.beans.factory.config.BeanFactoryPostProcessor 接口,与此同时,因为容器中可能有多个 BeanFactoryPostProcessor,可能还须要实现 org.springframework.core.Ordered 接口,以保障 BeanFactoryPostProcessor 依照程序执行。

Spring 提供了为数不多的 BeanFactoryPostProcessor 实现. 咱们以 PropertyPlaceholderConfigurer 来阐明其大抵的工作流程。

在 Spring 我的项目的 XML 配置文件中,常常能够看到许多配置项的值应用占位符,而将占位符所代表的值独自配置到独立的 properties 文件,这样能够将散落在不同 XML 文件中的配置集中管理,而且也不便运维依据不同的环境进行配置不同的值。

这个十分实用的性能就是由 PropertyPlaceholderConfigurer 负责实现的。

依据前文,当 BeanFactory 在第一阶段加载完所有配置信息时,BeanFactory 中保留的对象的属性还是以占位符形式存在的,比方 ${jdbc.mysql.url}。

当 PropertyPlaceholderConfigurer 作为 BeanFactoryPostProcessor 被利用时,它会应用 properties 配置文件中的值来替换相应的 BeanDefinition 中占位符所示意的属性值。当须要实例化 bean 时,bean 定义中的属性值就曾经被替换成咱们配置的值。当然其实现比下面形容的要简单一些,这里仅阐明其大抵工作原理,更具体的实现能够参考其源码。

与之类似的,还有 BeanPostProcessor,其存在于对象实例化阶段。跟 BeanFactoryPostProcessor 相似,它会解决容器内所有符合条件并且曾经实例化后的对象。

简略的比照,BeanFactoryPostProcessor 解决 bean 的定义,而 BeanPostProcessor 则解决 bean 实现实例化后的对象。

BeanPostProcessor 定义了两个接口:

// 通常为 BeanDefinitionRegistry 的实现类,这里以 DeFaultListabeBeanFactory 为例 BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory();  // XmlBeanDefinitionReader 实现了 BeanDefinitionReader 接口,用于解析 XML 文件 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl(beanRegistry); // 加载配置文件 beanDefinitionReader.loadBeanDefinitions(classpath:spring-bean.xml);  // 从容器中获取 bean 实例 BeanFactory container = (BeanFactory)beanRegistry; Business business = (Business)container.getBean(beanName);

为了了解这两个办法执行的机会,简略的理解下 bean 的整个生命周期:

Bean 的实例化过程(来自:Spring 揭秘)

postProcessBeforeInitialization()办法与 postProcessAfterInitialization()别离对应图中前置解决和后置解决两个步骤将执行的办法。这两个办法中都传入了 bean 对象实例的援用,为扩大容器的对象实例化过程提供了很大便当,在这儿简直能够对传入的实例执行任何操作。

注解、AOP 等性能的实现均大量应用了 BeanPostProcessor,比方有一个自定义注解,你齐全能够实现 BeanPostProcessor 的接口,在其中判断 bean 对象的脑袋上是否有该注解,如果有,你能够对这个 bean 实例执行任何操作,想想是不是十分的简略?

再来看一个更常见的例子,在 Spring 中常常可能看到各种各样的 Aware 接口,其作用就是在对象实例化实现当前将 Aware 接口定义中规定的依赖注入到以后实例中。

比方最常见的 ApplicationContextAware 接口,实现了这个接口的类都能够获取到一个 ApplicationContext 对象。当容器中每个对象的实例化过程走到 BeanPostProcessor 前置解决这一步时,容器会检测到之前注册到容器的 ApplicationContextAwareProcessor,而后就会调用其 postProcessBeforeInitialization()办法,查看并设置 Aware 相干依赖。

看看代码吧,是不是很简略:

// 代码来自:org.springframework.context.support.ApplicationContextAwareProcessor // 其 postProcessBeforeInitialization 办法调用了 invokeAwareInterfaces 办法 private void invokeAwareInterfaces(Object bean){if (bean instanceof EnvironmentAware){((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());     }     if (bean instanceof ApplicationContextAware){((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);     }     // ......}

最初总结一下,本大节内容和你一起回顾了 Spring 容器的局部核心内容,限于篇幅不能写更多,但了解这部分内容,足以让您轻松了解 Spring Boot 的启动原理,如果在后续的学习过程中遇到一些艰涩难懂的常识,再回过头来看看 Spring 的外围常识,兴许有意想不到的成果。

兴许 Spring Boot 的中文材料很少,但 Spring 的中文材料和书籍有太多太多,总有货色能给你启发。

二、夯实根底:JavaConfig 与常见 Annotation

2.1、JavaConfig

咱们晓得 bean 是 Spring IOC 中十分外围的概念,Spring 容器负责 bean 的生命周期的治理。

在最后,Spring 应用 XML 配置文件的形式来形容 bean 的定义以及相互间的依赖关系,但随着 Spring 的倒退,越来越多的人对这种形式示意不满,因为 Spring 我的项目的所有业务类均以 bean 的模式配置在 XML 文件中,造成了大量的 XML 文件,使我的项目变得复杂且难以治理。

起初,基于纯 Java Annotation 依赖注入框架 Guice 入世,其性能显著优于采纳 XML 形式的 Spring,甚至有局部人认为,Guice 能够齐全取代 Spring(Guice 仅是一个轻量级 IOC 框架,取代 Spring 还差的挺远).

正是这样的危机感,促使 Spring 及社区推出并继续欠缺了 JavaConfig 子项目,它基于 Java 代码和 Annotation 注解来形容 bean 之间的依赖绑定关系。

比方,上面是应用 XML 配置形式来形容 bean 的定义:

<bean id=bookService class=cn.moondev.service.BookServiceImpl></bean>

而基于 JavaConfig 的配置模式是这样的:

@Configuration public class MoonBookConfiguration {// 任何标记了 @Bean 的办法,其返回值将作为一个 bean 注册到 Spring 的 IOC 容器中     // 办法名默认成为该 bean 定义的 id     @Bean     public BookService bookService() {return new BookServiceImpl();     } }

如果两个 bean 之间有依赖关系的话,在 XML 配置中应该是这样:

<bean id=bookService class=cn.moondev.service.BookServiceImpl>     <property name=dependencyService ref=dependencyService/> </bean>  <bean id=otherService class=cn.moondev.service.OtherServiceImpl>     <property name=dependencyService ref=dependencyService/> </bean>  <bean id=dependencyService class=DependencyServiceImpl/>

而在 JavaConfig 中则是这样:

@Configuration public class MoonBookConfiguration {// 如果一个 bean 依赖另一个 bean,则间接调用对应 JavaConfig 类中依赖 bean 的创立办法即可     // 这里间接调用 dependencyService()     @Bean     public BookService bookService() {         return new BookServiceImpl(dependencyService());     }      @Bean     public OtherService otherService() {         return new OtherServiceImpl(dependencyService());     }      @Bean     public DependencyService dependencyService() {         return new DependencyServiceImpl();     } }

你可能留神到这个示例中,有两个 bean 都依赖于 dependencyService,也就是说当初始化 bookService 时会调用 dependencyService(),在初始化 otherService 时也会调用 dependencyService(),那么问题来了?

这时候 IOC 容器中是有一个 dependencyService 实例还是两个?这个问题留着大家思考吧,这里不再赘述。

2.2、@ComponentScan

@ComponentScan 注解对应 XML 配置模式中的元素示意启用组件扫描,Spring 会主动扫描所有通过注解配置的 bean,而后将其注册到 IOC 容器中。

咱们能够通过 basePackages 等属性来指定 @ComponentScan 主动扫描的范畴,如果不指定,默认从申明 @ComponentScan 所在类的 package 进行扫描。正因为如此,SpringBoot 的启动类都默认在 src/main/java 下。

2.3、@Import

@Import 注解用于导入配置类,举个简略的例子:

@Configuration public class MoonBookConfiguration{@Bean     public BookService bookService() {return new BookServiceImpl();     } }

当初有另外一个配置类,比方:MoonUserConfiguration,这个配置类中有一个 bean 依赖于 MoonBookConfiguration 中的 bookService,如何将这两个 bean 组合在一起?

借助 @Import 即可:

@Configuration // 能够同时导入多个配置类,比方:@Import({A.class,B.class}) @Import(MoonBookConfiguration.class) public class MoonUserConfiguration {@Bean     public UserService userService(BookService bookService) {return new BookServiceImpl(bookService);     } }

须要留神的是,在 4.2 之前,@Import 注解只反对导入配置类,然而在 4.2 之后,它反对导入一般类,并将这个类作为一个 bean 的定义注册到 IOC 容器中。

2.4、@Conditional

@Conditional 注解示意在满足某种条件后才初始化一个 bean 或者启用某些配置。

它个别用在由 @Component、@Service、@Configuration 等注解标识的类下面,或者由 @Bean 标记的办法上。如果一个 @Configuration 类标记了 @Conditional,则该类中所有标识了 @Bean 的办法和 @Import 注解导入的相干类将听从这些条件。

在 Spring 里能够很不便的编写你本人的条件类,所要做的就是实现 Condition 接口,并笼罩它的 matches()办法。

举个例子,上面的简略条件类示意只有在 Classpath 里存在 JdbcTemplate 类时才失效:

public class JdbcTemplateCondition implements Condition {@Override     public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {try {         conditionContext.getClassLoader().loadClass(org.springframework.jdbc.core.JdbcTemplate);             return true;         } catch (ClassNotFoundException e) {e.printStackTrace();         }         return false;     } }

当你用 Java 来申明 bean 的时候,能够应用这个自定义条件类:

@Conditional(JdbcTemplateCondition.class) @Service public MyService service() { ......}

这个例子中只有当 JdbcTemplateCondition 类的条件成立时才会创立 MyService 这个 bean。

也就是说 MyService 这 bean 的创立条件是 classpath 外面蕴含 JdbcTemplate,否则这个 bean 的申明就会被疏忽掉。

Spring Boot 定义了很多乏味的条件,并把他们使用到了配置类上,这些配置类形成了 Spring Boot 的主动配置的根底。

Spring Boot 使用条件化配置的办法是:定义多个非凡的条件化注解,并将它们用到配置类上。

上面列出了 Spring Boot 提供的局部条件化注解:

2.5、@ConfigurationProperties 与 @EnableConfigurationProperties

当某些属性的值须要配置的时候,咱们个别会在 application.properties 文件中新建配置项,而后在 bean 中应用 @Value 注解来获取配置的值,比方上面配置数据源的代码。

// jdbc config jdbc.mysql.url=jdbc:mysql://localhost:3306/sampledb jdbc.mysql.username=root jdbc.mysql.password=123456 ......  // 配置数据源 @Configuration public class HikariDataSourceConfiguration {@Value(jdbc.mysql.url)     public String url;     @Value(jdbc.mysql.username)     public String user;     @Value(jdbc.mysql.password)     public String password;          @Bean     public HikariDataSource dataSource() {         HikariConfig hikariConfig = new HikariConfig();         hikariConfig.setJdbcUrl(url);         hikariConfig.setUsername(user);         hikariConfig.setPassword(password);         // 省略局部代码         return new HikariDataSource(hikariConfig);     } }

应用 @Value 注解注入的属性通常都比较简单,如果同一个配置在多个中央应用,也存在不不便保护的问题(思考下,如果有几十个中央在应用某个配置,而当初你想改下名字,你改怎么做?)

对于更为简单的配置,Spring Boot 提供了更优雅的实现形式, 那就是 @ConfigurationProperties 注解。在此我向大家举荐一个架构学习交换圈。交流学习领导伪鑫:1253431195(外面有大量的面试题及答案)外面会分享一些资深架构师录制的视频录像:有 Spring,MyBatis,Netty 源码剖析,高并发、高性能、分布式、微服务架构的原理,JVM 性能优化、分布式架构等这些成为架构师必备的常识体系。还能支付收费的学习资源,目前受害良多

咱们能够通过上面的形式来改写下面的代码:

@Component // 还能够通过 @PropertySource(classpath:jdbc.properties)来指定配置文件 @ConfigurationProperties(jdbc.mysql) // 前缀 =jdbc.mysql,会在配置文件中寻找 jdbc.mysql.* 的配置项 pulic class JdbcConfig {public String url;     public String username;     public String password;}  @Configuration public class HikariDataSourceConfiguration {@AutoWired     public JdbcConfig config;          @Bean     public HikariDataSource dataSource() {HikariConfig hikariConfig = new HikariConfig();         hikariConfig.setJdbcUrl(config.url);         hikariConfig.setUsername(config.username);         hikariConfig.setPassword(config.password);         // 省略局部代码         return new HikariDataSource(hikariConfig);   } }

@ConfigurationProperties 对于更为简单的配置,解决起来也是得心应手,比方有如下配置文件:

#App app.menus[0].title=Home app.menus[0].name=Home app.menus[0].path=/ app.menus[1].title=Login app.menus[1].name=Login app.menus[1].path=/login  app.compiler.timeout=5 app.compiler.output-folder=/temp/  app.error=/error/

能够定义如下配置类来接管这些属性:

@Component @ConfigurationProperties(app) public class AppProperties {public String error;     public List<Menu> menus = new ArrayList<>();     public Compiler compiler = new Compiler();      public static class Menu {         public String name;         public String path;         public String title;}      public static class Compiler {public String timeout;         public String outputFolder;} }

@EnableConfigurationProperties 注解示意对 @ConfigurationProperties 的内嵌反对默认会将对应 Properties Class 作为 bean 注入的 IOC 容器中,即在相应的 Properties 类上不必加 @Component 注解。

三、削铁如泥:SpringFactoriesLoader 详解

JVM 提供了 3 品种加载器:

BootstrapClassLoader、ExtClassLoader、AppClassLoader 别离加载 Java 外围类库、扩大类库以及利用的类门路 (CLASSPATH) 下的类库。

JVM 通过双亲委派模型进行类的加载,咱们也能够通过继承 java.lang.classloader 实现本人的类加载器。

何为双亲委派模型?当一个类加载器收到类加载工作时,会先交给本人的父加载器去实现,因而最终加载工作都会传递到最顶层的 BootstrapClassLoader,只有当父加载器无奈实现加载工作时,才会尝试本人来加载。

采纳双亲委派模型的一个益处是保障应用不同类加载器最终失去的都是同一个对象,这样就能够保障 Java 外围库的类型平安,比方,加载位于 rt.jar 包中的 java.lang.Object 类,不论是哪个加载器加载这个类,最终都是委托给顶层的 BootstrapClassLoader 来加载的,这样就能够保障任何的类加载器最终失去的都是同样一个 Object 对象。

查看 ClassLoader 的源码,对双亲委派模型会有更直观的意识:

protected Class<?> loadClass(String name, boolean resolve) {synchronized (getClassLoadingLock(name)) {// 首先,查看该类是否曾经被加载,如果从 JVM 缓存中找到该类,则间接返回     Class<?> c = findLoadedClass(name);     if (c == null) {try {             // 遵循双亲委派的模型,首先会通过递归从父加载器开始找,// 直到父类加载器是 BootstrapClassLoader 为止             if (parent != null) {c = parent.loadClass(name, false);             } else {c = findBootstrapClassOrNull(name);             }        } catch (ClassNotFoundException e) {}         if (c == null) {// 如果还找不到,尝试通过 findClass 办法去寻找             // findClass 是留给开发者本人实现的,也就是说             // 自定义类加载器时,重写此办法即可            c = findClass(name);         }     }     if (resolve) {resolveClass(c);     }     return c;     } }

但双亲委派模型并不能解决所有的类加载器问题,比方,Java 提供了很多服务提供者接口(Service Provider Interface,SPI),容许第三方为这些接口提供实现。

常见的 SPI 有 JDBC、JNDI、JAXP 等,这些 SPI 的接口由外围类库提供,却由第三方实现这样就存在一个问题:SPI 的接口是 Java 外围库的一部分,是由 BootstrapClassLoader 加载的;SPI 实现的 Java 类个别是由 AppClassLoader 来加载的。BootstrapClassLoader 是无奈找到 SPI 的实现类的,因为它只加载 Java 的外围库。它也不能代理给 AppClassLoader,因为它是最顶层的类加载器。也就是说,双亲委派模型并不能解决这个问题。

线程上下文类加载器 (ContextClassLoader) 正好解决了这个问题。

从名称上看,可能会误会为它是一种新的类加载器,实际上,它仅仅是 Thread 类的一个变量而已,能够通过 setContextClassLoader(ClassLoader cl)和 getContextClassLoader()来设置和获取该对象。

如果不做任何的设置,Java 利用的线程的上下文类加载器默认就是 AppClassLoader。

在外围类库应用 SPI 接口时,传递的类加载器应用线程上下文类加载器,就能够胜利的加载到 SPI 实现的类。

线程上下文类加载器在很多 SPI 的实现中都会用到。但在 JDBC 中,你可能会看到一种更间接的实现形式,比方,JDBC 驱动治理 java.sql.Driver 中的 loadInitialDrivers()办法中

你能够间接看到 JDK 是如何加载驱动的:

for (String aDriver : driversList) {try {         // 间接应用 AppClassLoader         Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());     } catch (Exception ex) {println(DriverManager.Initialize: load failed:  + ex);      }   }

其实解说线程上下文类加载器,最次要是让大家在看到:

Thread.currentThread().getClassLoader()Thread.currentThread().getContextClassLoader()

时不会一脸懵逼.

这两者除了在许多底层框架中获得的 ClassLoader 可能会有所不同外,其余大多数业务场景下都是一样的,大家只有晓得它是为了解决什么问题而存在的即可。

类加载器除了加载 class 外,还有一个十分重要性能,就是加载资源,它能够从 jar 包中读取任何资源文件,比方,ClassLoader.getResources(String name)办法就是用于读取 jar 包中的资 源文件,其代码如下:

public Enumeration<URL> getResources(String name) throws IOException {Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];     if (parent != null) {tmp[0] = parent.getResources(name);     } else {tmp[0] = getBootstrapResources(name);     }     tmp[1] = findResources(name);     return new CompoundEnumeration<>(tmp); }

是不是感觉有点眼生,不错,它的逻辑其实跟类加载的逻辑是一样的, 首先判断父类加载器是否为空,不为空则委托父类加载器执行资源查找工作, 直到 BootstrapClassLoader,最初才轮到本人查找。

而不同的类加载器负责扫描不同门路下的 jar 包,就如同加载 class 一样,最初会扫描所有的 jar 包,找到符合条件的资源文件。

类加载器的 findResources(name)办法会遍历其负责加载的所有 jar 包,找到 jar 包中名称为 name 的资源文件,这里的资源能够是任何文件,甚至是.class 文件,比方上面的示例,用于查找 Array.class 文件:

// 寻找 Array.class 文件 public static void main(String[] args) throws Exception{// Array.class 的残缺门路     String name = java/sql/Array.class;     Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);     while (urls.hasMoreElements()) {URL url = urls.nextElement();         System.out.println(url.toString());     } }

运行后能够失去如下后果:

$JAVA_HOME/jre/lib/rt.jar!/java/sql/Array.class

依据资源文件的 URL,能够结构相应的文件来读取资源内容。

看到这里,你可能会感到挺奇怪的,你不是要详解 SpringFactoriesLoader 吗?

上来讲了一 ClassLoader 是几个意思?看下它的源码你就晓得了:

public static final String FACTORIES_RESOURCE_LOCATION = META-INF/spring.factories; // spring.factories 文件的格局为:key=value1,value2,value3 // 从所有的 jar 包中找到 META-INF/spring.factories 文件 // 而后从文件中解析出 key=factoryClass 类名称的所有 value 值 public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {String factoryClassName = factoryClass.getName();     // 获得资源文件的 URL     Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));     List<String> result = new ArrayList<String>();     // 遍历所有的 URL     while (urls.hasMoreElements()) {URL url = urls.nextElement();         // 依据资源文件 URL 解析 properties 文件         Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));         String factoryClassNames = properties.getProperty(factoryClassName);         // 组装数据,并返回         result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));     }     return result; }

有了后面对于 ClassLoader 的常识,再来了解这段代码,是不是感觉恍然大悟:

从 CLASSPATH 下的每个 Jar 包中搜查所有 META-INF/spring.factories 配置文件,而后将解析 properties 文件,找到指定名称的配置后返回。

须要留神的是,其实这里不仅仅是会去 ClassPath 门路下查找,会扫描所有门路下的 Jar 包,只不过这个文件只会在 Classpath 下的 jar 包中。

来简略看下 spring.factories 文件的内容吧:

// 来自 org.springframework.boot.autoconfigure 下的 META-INF/spring.factories // EnableAutoConfiguration 后文会讲到,它用于开启 Spring Boot 主动配置性能 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\

执行 loadFactoryNames(EnableAutoConfiguration.class, classLoader)后,失去对应的一组 @Configuration 类,咱们就能够通过反射实例化这些类而后注入到 IOC 容器中,最初容器里就有了一系列标注了 @Configuration 的 JavaConfig 模式的配置类。

这就是 SpringFactoriesLoader,它实质上属于 Spring 框架公有的一种扩大计划,相似于 SPI,Spring Boot 在 Spring 根底上的很多外围性能都是基于此,心愿大家能够了解。

四、另一件武器:Spring 容器的事件监听机制

过来,事件监听机制多用于图形界面编程,比方:点击按钮、在文本框输出内容等操作被称为事件,而当事件触发时,应用程序作出肯定的响应则示意利用监听了这个事件,而在服务器端,事件的监听机制更多的用于异步告诉以及监控和异样解决。

Java 提供了实现事件监听机制的两个根底类:自定义事件类型扩大自 java.util.EventObject、事件的监听器扩大自 java.util.EventListener。

来看一个简略的实例:简略的监控一个办法的耗时。

首先定义事件类型,通常的做法是扩大 EventObject,随着事件的产生,相应的状态通常都封装在此类中:

public class MethodMonitorEvent extends EventObject {// 工夫戳,用于记录办法开始执行的工夫     public long timestamp;      public MethodMonitorEvent(Object source) {super(source);     } }

事件公布之后,相应的监听器即可对该类型的事件进行解决,咱们能够在办法开始执行之前公布一个 begin 事件.

在办法执行完结之后公布一个 end 事件,相应地,事件监听器须要提供办法对这两种状况下接 收到的事件进行解决:

// 1、定义事件监听接口 public interface MethodMonitorEventListener extends EventListener {// 解决办法执行之前公布的事件     public void onMethodBegin(MethodMonitorEvent event);     // 解决办法完结时公布的事件     public void onMethodEnd(MethodMonitorEvent event); } // 2、事件监听接口的实现:如何解决 public class AbstractMethodMonitorEventListener implements MethodMonitorEventListener {@Override     public void onMethodBegin(MethodMonitorEvent event) {// 记录办法开始执行时的工夫         event.timestamp = System.currentTimeMillis();     }      @Override     public void onMethodEnd(MethodMonitorEvent event) {// 计算方法耗时         long duration = System.currentTimeMillis() - event.timestamp;         System.out.println(耗时:+ duration);     } }

事件监听器接口针对不同的事件公布理论提供相应的解决办法定义,最重要的是,其办法只接管 MethodMonitorEvent 参数,阐明这个监听器类只负责监听器对应的事件并进行解决。

有了事件和监听器,剩下的就是公布事件,而后让相应的监听器监听并解决。

通常状况,咱们会有一个事件发布者,它自身作为事件源,在适合的机会,将相应的事件公布 给对应的事件监听器:

public class MethodMonitorEventPublisher {private List<MethodMonitorEventListener> listeners = new ArrayList<MethodMonitorEventListener>();      public void methodMonitor() {         MethodMonitorEvent eventObject = new MethodMonitorEvent(this);         publishEvent(begin,eventObject);         // 模仿办法执行:休眠 5 秒钟         TimeUnit.SECONDS.sleep(5);         publishEvent(end,eventObject);      }      private void publishEvent(String status,MethodMonitorEvent event) {// 防止在事件处理期间,监听器被移除,这里为了平安做一个复制操作         List<MethodMonitorEventListener> copyListeners = ➥ new ArrayList<MethodMonitorEventListener>(listeners);         for (MethodMonitorEventListener listener : copyListeners) {if (begin.equals(status)) {listener.onMethodBegin(event);             } else {listener.onMethodEnd(event);             }         }     }         public static void main(String[] args) {MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher();         publisher.addEventListener(new AbstractMethodMonitorEventListener());         publisher.methodMonitor();}     // 省略实现     public void addEventListener(MethodMonitorEventListener listener) {}     public void removeEventListener(MethodMonitorEventListener listener) {}     public void removeAllListeners() {}

对于事件发布者(事件源)通常须要关注两点:

1. 在适合的机会公布事件。此例中的 methodMonitor()办法是事件公布的源头,其在办法执行之前和完结之后两个工夫点公布 MethodMonitorEvent 事件,每个工夫点公布的事件都会传给相应的监听器进行解决。

在具体实现时须要留神的是,事件公布是程序执行,为了不影响解决性能,事件监听器的解决逻辑应尽量简略。

2. 事件监听器的治理。publisher 类中提供了事件监听器的注册与移除办法,这样客户端能够依据理论状况决定是否须要注册新的监听器或者移除某个监听器。

如果这里没有提供 remove 办法,那么注册的监听器示例将始终 MethodMonitorEventPublisher 援用,即便曾经废除不必了,也仍然在发布者的监听器列表中,这会导致隐性的内存透露。

Spring 容器内的事件监听机制

Spring 的 ApplicationContext 容器外部中的所有事件类型均继承自 org.springframework.context.AppliationEvent,容器中的所有监听器都实现 org.springframework.context.ApplicationListener 接口,并且以 bean 的模式注册在容器中。

一旦在容器内公布 ApplicationEvent 及其子类型的事件,注册到容器的 ApplicationListener 就会对这些事件进行解决。

你应该曾经猜到是怎么回事了。

ApplicationEvent 继承自 EventObject,Spring 提供了一些默认的实现,比方:

ContextClosedEvent 示意容器在行将敞开时公布的事件类型,ContextRefreshedEvent

示意容器在初始化或者刷新的时候公布的事件类型 …… 容器外部应用 ApplicationListener 作为事件监听器接口定义,它继承自 EventListener。

ApplicationContext 容器在启动时,会自动识别并加载 EventListener 类型的 bean 一旦容器内有事件公布,将告诉这些注册到容器的 EventListener。

ApplicationContext 接口继承了 ApplicationEventPublisher 接口,该接口提供了 void publishEvent(ApplicationEvent event)办法定义,不难看出,ApplicationContext 容器担当的就是事件发布者的角色。

如果有趣味能够查看 AbstractApplicationContext.publishEvent(ApplicationEvent event)办法的源码:ApplicationContext 将事件的公布以及监听器的管理工作委托给 ApplicationEventMulticaster 接口的实现类。

在容器启动时,会查看容器内是否存在名为 applicationEventMulticaster 的 ApplicationEventMulticaster 对象实例。

如果有就应用其提供的实现,没有就默认初始化一个 SimpleApplicationEventMulticaster 作为实现。

最初,如果咱们业务须要在容器外部公布事件,只须要为其注入 ApplicationEventPublisher 依赖即可:实现 ApplicationEventPublisherAware 接口或者 ApplicationContextAware 接口.

五、炉火纯青:揭秘主动配置原理

典型的 Spring Boot 利用的启动类个别均位于 src/main/java 根门路下

比方 MoonApplication 类:

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

其中 @SpringBootApplication 开启组件扫描和主动配置,而 SpringApplication.run 则负责启动疏导应用程序。

@SpringBootApplication 是一个复合 Annotation,它将三个有用的注解组合在一起:

@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 就是 @Configuration,它是 Spring 框架的注解,表明该类是一个 JavaConfig 配置类。

而 @ComponentScan 启用组件扫描,前文曾经具体解说过,这里着重关注 @EnableAutoConfiguration。@EnableAutoConfiguration 注解示意开启 Spring Boot 主动配置性能,Spring Boot 会依据利用的依赖、自定义的 bean、classpath 下有没有某个类 等等因素来猜想你须要的 bean,

而后注册到 IOC 容器中。

那 @EnableAutoConfiguration 是如何推算出你的需要?

首先看下它的定义:

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {// ......}

你的关注点应该在 @Import(EnableAutoConfigurationImportSelector.class)上了,前文说过,@Import 注解用于导入类,并将这个类作为一个 bean 的定义注册到容器中,这里将把 EnableAutoConfigurationImportSelector 作为 bean 注入到容器中,而这个类会将 所有符合条件的 @Configuration 配置都加载到容器中,看看它的代码:

public String[] selectImports(AnnotationMetadata annotationMetadata) {// 省略了大部分代码,保留一句外围代码     // 留神:SpringBoot 最近版本中,这句代码被封装在一个独自的办法中     // SpringFactoriesLoader 相干常识请参考前文     List<String> factories = new ArrayList<String>(new LinkedHashSet<String>(           SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader))); }

这个类会扫描所有的 jar 包,将所有符合条件的 @Configuration 配置类注入的容器中 何为符合条件,看看 META-INF/spring.factories 的文件内容:

// 来自 org.springframework.boot.autoconfigure 下的 META-INF/spring.factories // 配置的 key = EnableAutoConfiguration,与代码中统一 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\ .....

以 DataSourceAutoConfiguration 为例,看看 Spring Boot 是如何主动配置的:

@Configuration @ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class}) @EnableConfigurationProperties(DataSourceProperties.class) @Import({Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class}) public class DataSourceAutoConfiguration {}

别离说一说:

@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class}):当 Classpath 中存在 DataSource 或者 EmbeddedDatabaseType 类时才启用这个配置,否则这个配置将被疏忽。

@EnableConfigurationProperties(DataSourceProperties.class):将 DataSource 的默认配置类注入到 IOC 容器中,DataSourceproperties 定义为:

// 提供对 datasource 配置信息的反对,所有的配置前缀为:spring.datasource @ConfigurationProperties(prefix = spring.datasource) public class DataSourceProperties {private ClassLoader classLoader;     private Environment environment;     private String name = testdb;     ......}

@Import({Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class}):导入其余额定的配置,就以 DataSourcePoolMetadataProvidersConfiguration 为例吧。

@Configuration public class DataSourcePoolMetadataProvidersConfiguration {@Configuration     @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)     static class TomcatDataSourcePoolMetadataProviderConfiguration {@Bean         public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() {.....}     }   ...... }

DataSourcePoolMetadataProvidersConfiguration 是数据库连接池提供者的一个配置类,即 Classpath 中存在 org.apache.tomcat.jdbc.pool.DataSource.class,则应用 tomcat-jdbc 连接池,如果 Classpath 中存在 HikariDataSource.class 则应用 Hikari 连接池。

这里仅形容了 DataSourceAutoConfiguration 的冰山一角,但足以阐明 Spring Boot 如何利用条件话配置来实现主动配置的。

回顾一下,@EnableAutoConfiguration 中导入了 EnableAutoConfigurationImportSelector 类,而这个类的 selectImports()通过 SpringFactoriesLoader 失去了大量的配置类,而每一个配置类则依据条件化配置来做出决策,以实现主动配置。

整个流程很清晰,但漏了一个大问题:

EnableAutoConfigurationImportSelector.selectImports()是何时执行的?其实这个办法会在容器启动过程中执行:AbstractApplicationContext.refresh(),更多的细节在下一大节中阐明。

六、启动疏导:Spring Boot 利用启动的机密

6.1 SpringApplication 初始化

SpringBoot 整个启动流程分为两个步骤:初始化一个 SpringApplication 对象、执行该对象的 run 办法。看下 SpringApplication 的初始化流程,SpringApplication 的构造方法中调用 initialize(Object[] sources)办法,其代码如下:

private void initialize(Object[] sources) {if (sources != null && sources.length > 0) {this.sources.addAll(Arrays.asList(sources));      }      // 判断是否是 Web 我的项目      this.webEnvironment = deduceWebEnvironment();      setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));      setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));      // 找到入口类      this.mainApplicationClass = deduceMainApplicationClass();}

初始化流程中最重要的就是通过 SpringFactoriesLoader 找到 spring.factories 文件中配置的 ApplicationContextInitializer 和 ApplicationListener 两个接口的实现类名称,以便前期结构相应的实例。

ApplicationContextInitializer 的次要目标是在 ConfigurableApplicationContext 做 refresh 之前,对 ConfigurableApplicationContext 实例做进一步的设置或解决。

ConfigurableApplicationContext 继承自 ApplicationContext,其次要提供了对 ApplicationContext 进行设置的能力。

实现一个 ApplicationContextInitializer 非常简单,因为它只有一个办法,但大多数状况下咱们没有必要自定义一个 ApplicationContextInitializer,即使是 Spring Boot 框架,它默认也只是注册了两个实现,毕竟 Spring 的容器曾经十分成熟和稳固,你没有必要来扭转它。

而 ApplicationListener 的目标就没什么好说的了,它是 Spring 框架对 Java 事件监听机制的一种框架实现,具体内容在前文 Spring 事件监听机制这个大节有具体解说。这里次要说说,如果你想为 Spring Boot 利用增加监听器,该如何实现?

Spring Boot 提供两种形式来增加自定义监听器:

通过 SpringApplication.addListeners(ApplicationListener… listeners)或者 SpringApplication.setListeners(Collection> listeners)两个办法来增加一个或者多个自定义监听器

既然 SpringApplication 的初始化流程中曾经从 spring.factories 中获取到 ApplicationListener 的实现类,那么咱们间接在本人的 jar 包的 META-INF/spring.factories 文件中新增配置即可:

org.springframework.context.ApplicationListener=\ cn.moondev.listeners.xxxxListener\

对于 SpringApplication 的初始化,咱们就说这么多。

6.2 Spring Boot 启动流程.

Spring Boot 利用的整个启动流程都封装在 SpringApplication.run 办法中,其整个流程真的是太长太长了,但实质上就是在 Spring 容器启动的根底上做了大量的扩大,依照这个思路来看看

源码:

public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();         stopWatch.start();         ConfigurableApplicationContext context = null;         FailureAnalyzers analyzers = null;         configureHeadlessProperty();         // ①         SpringApplicationRunListeners listeners = getRunListeners(args);         listeners.starting();         try {             // ②ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);             ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);             // ③             Banner printedBanner = printBanner(environment);             // ④             context = createApplicationContext();             // ⑤             analyzers = new FailureAnalyzers(context);             // ⑥             prepareContext(context, environment, listeners, applicationArguments,printedBanner);             // ⑦              refreshContext(context);             // ⑧             afterRefresh(context, applicationArguments);             // ⑨             listeners.finished(context, null);             stopWatch.stop();             return context;}         catch (Throwable ex) {handleRunFailure(context, listeners, analyzers, ex);             throw new IllegalStateException(ex);         }     }

① 通过 SpringFactoriesLoader 查找并加载所有的 SpringApplicationRunListeners 通过调用 starting()办法告诉所有的 SpringApplicationRunListeners:利用开始启动了。

SpringApplicationRunListeners 其本质上就是一个事件发布者,它在 SpringBoot 利用启动的不同工夫点公布不同利用事件类型 (ApplicationEvent),如果有哪些事件监听者(ApplicationListener) 对这些事件感兴趣,则能够接管并且解决。

还记得初始化流程中,SpringApplication 加载了一系列 ApplicationListener 吗?这个启动流程中没有发现有公布事件的代码,其实都曾经在 SpringApplicationRunListeners 这儿实现了。

简略的剖析一下其实现流程,首先看下 SpringApplicationRunListener 的源码:

public interface SpringApplicationRunListener {// 运行 run 办法时立刻调用此办法,能够用户十分晚期的初始化工作     void starting();          // Environment 筹备好后,并且 ApplicationContext 创立之前调用     void environmentPrepared(ConfigurableEnvironment environment);      // ApplicationContext 创立好后立刻调用     void contextPrepared(ConfigurableApplicationContext context);      // ApplicationContext 加载实现,在 refresh 之前调用     void contextLoaded(ConfigurableApplicationContext context);      // 当 run 办法完结之前调用     void finished(ConfigurableApplicationContext context, Throwable exception);  }

SpringApplicationRunListener 只有一个实现类:EventPublishingRunListener。

①处的代码只会获取到一个 EventPublishingRunListener 的实例

咱们来看看 starting()办法的内容:

public void starting() {     // 公布一个 ApplicationStartedEvent     this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args)); }

顺着这个逻辑,你能够在②处的 prepareEnvironment()办法的源码中找到

listeners.environmentPrepared(environment);

即 SpringApplicationRunListener 接口的第二个办法,那不出你所料,environmentPrepared()又公布了另外一个事件 ApplicationEnvironmentPreparedEvent。

接下来会产生什么,就不必我多说了吧。

② 创立并配置以后利用将要应用的 Environment,Environment 用于形容应用程序以后的运行环境,其形象了两个方面的内容:

配置文件 (profile) 和属性 (properties),开发经验丰富的同学对这两个货色肯定不会生疏:不同的环境(eg:生产环境、预公布环境) 能够应用不同的配置文件,而属性则能够从配置文件、环境变量、命令行参数等起源获取。

因而,当 Environment 筹备好后,在整个利用的任何时候,都能够从 Environment 中获取资源。

总结起来,②处的两句代码,次要实现以下几件事:

判断 Environment 是否存在,不存在就创立(如果是 web 我的项目就创立 StandardServletEnvironment,否则创立 StandardEnvironment)

配置 Environment:配置 profile 以及 properties

调用 SpringApplicationRunListener 的 environmentPrepared()办法,告诉事件监听者:利用的 Environment 曾经筹备好

③、SpringBoot 利用在启动时会输入这样的货色:

. ____          _            __ _ _  /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ (()\___ |'_ | '_| |'_ \/ _` | \ \ \ \  \\/ ___)| |_)| | | | | || (_| |) ) ) )   ' |____| .__|_| |_|_| |_\__, | / / / /  =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.6.RELEASE)

如果想把这个货色改成本人的涂鸦,你能够钻研以下 Banner 的实现,这个工作就留给你们吧。

④、依据是否是 web 我的项目,来创立不同的 ApplicationContext 容器。

⑤、创立一系列 FailureAnalyzer,创立流程仍然是通过 SpringFactoriesLoader 获取到所有实现 FailureAnalyzer 接口的 class,而后在创立对应的实例。FailureAnalyzer 用于剖析故障并提供相干诊断信息。

⑥、初始化 ApplicationContext,次要实现以下工作:

将筹备好的 Environment 设置给 ApplicationContext

遍历调用所有的 ApplicationContextInitializer 的 initialize()办法来对曾经创立好的 ApplicationContext 进行进一步的解决

调用 SpringApplicationRunListener 的 contextPrepared()办法,告诉所有的监听者:ApplicationContext 曾经筹备结束

将所有的 bean 加载到容器中

调用 SpringApplicationRunListener 的 contextLoaded()办法,告诉所有的监听者:ApplicationContext 曾经装载结束

⑦、调用 ApplicationContext 的 refresh()办法,实现 IoC 容器可用的最初一道工序。

从名字上了解为刷新容器,那何为刷新?就是插手容器的启动,分割一下第一大节的内容。

那如何刷新呢?且看上面代码:

// 摘自 refresh()办法中一句代码 invokeBeanFactoryPostProcessors(beanFactory);

看看这个办法的实现:

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());     ...... }

获取到所有的 BeanFactoryPostProcessor 来对容器做一些额定的操作。

BeanFactoryPostProcessor 容许咱们在容器实例化相应对象之前,对注册到容器的 BeanDefinition 所保留的信息做一些额定的操作。

这里的 getBeanFactoryPostProcessors()办法能够获取到 3 个 Processor:

ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor ConfigFileApplicationListener$PropertySourceOrderingPostProcessor

不是有那么多 BeanFactoryPostProcessor 的实现类,为什么这儿只有这 3 个?

因为在初始化流程获取到的各种 ApplicationContextInitializer 和 ApplicationListener 中,只有上文 3 个做了相似于如下操作:

public void initialize(ConfigurableApplicationContext context) {context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks())); }

而后你就能够进入到 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()办法了,这个办法除了会遍历下面的 3 个 BeanFactoryPostProcessor 解决外,还会获取类型为 BeanDefinitionRegistryPostProcessor 的 bean:org.springframework.context.annotation.internalConfigurationAnnotationProcessor,对应的 Class 为 ConfigurationClassPostProcessor。ConfigurationClassPostProcessor 用于解析解决各种注解,包含:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。当解决 @import 注解的时候,就会调用这一大节中的 EnableAutoConfigurationImportSelector.selectImports()来实现主动配置性能。其余的这里不再多讲,如果你有趣味,能够查阅参考资料 6。

⑧、查找以后 context 中是否注册有 CommandLineRunner 和 ApplicationRunner,如果有则遍历执行它们。

⑨、执行所有 SpringApplicationRunListener 的 finished()办法。这就是 Spring Boot 的整个启动流程,其外围就是在 Spring 容器初始化并启动的根底上退出各种扩大点,这些扩大点包含:ApplicationContextInitializer、ApplicationListener 以及各种 BeanFactoryPostProcessor 等等。

你对整个流程的细节不用太过关注,甚至没弄明确也没有关系,你只有了解这些扩大点是在何时如何工作的,能让它们为你所用即可。

整个启动流程的确非常复杂,能够查问参考资料中的局部章节和内容,对照着源码,多看看,我想最终你都能弄清楚的。言而总之,Spring 才是外围,了解分明 Spring 容器的启动流程,那 Spring Boot 启动流程就不在话下了。

更多材料能够分割小编支付!【Java 进阶营】

正文完
 0