前言:本文十分长,倡议先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进阶营】