1. 首页
  2. IT资讯

干货!没学过 Spring Boot?送你一份超详细的知识清单

“u003Cimg src=”http:u002Fu002Fp9.pstatp.comu002Flargeu002Fpgc-imageu002FRcV6XHe158Bc2v” img_width=”1080″ img_height=”345″ alt=”干货!没学过 Spring Boot?送你一份超详细的知识清单” inline=”0″u003Eu003Cpu003E预警:本文非常长,u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E建议u003Cu002Fiu003E先 mark 后看,预计阅读时间 81 min。u003Cu002Fpu003Eu003Cpu003E说明:前面有 4 个小节关于 Spring 的基础知识,分别是:IOC 容器、JavaConfig、事件监听、SpringFactoriesLoader 详解,它们占据了本文的大部分内容,虽然它们之间可能没有太多的联系,但这些知识对于理解 Spring Boot 的核心原理至关重要,如果你对 Spring 框架烂熟于心,完全可以跳过这 4 个小节。接下来便带你步入Spring Boot的学习之路,这个文章是由这些看似不相关的知识点组成,算是 Spring Boot 的一个知识清单。u003Cu002Fpu003Eu003Cpu003E在过去两三年的 Spring 生态圈,最让人兴奋的莫过于 Spring Boot 框架。或许从命名上就能看出这个框架的设计初衷:快速的启动 Spring 应用。因而 Spring Boot 应用本质上就是一个基于 Spring 框架的应用,它是 Spring 对“约定优先于配置”理念的最佳实践产物,它能够帮助开发者更快速高效地构建基于 Spring 生态圈的应用。u003Cemu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E web_resource u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后获取更多干货!u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003E那 Spring Boot 有何魔法?u003Cstrongu003E自动配置u003Cu002Fstrongu003E、u003Cstrongu003E起步依赖u003Cu002Fstrongu003E、u003Cstrongu003EActuatoru003Cu002Fstrongu003E、u003Cstrongu003E命令行界面(CLI)u003Cu002Fstrongu003E是 Spring Boot 最重要的 4 大核心特性,其中 CLI 是 Spring Boot 的可选特性,虽然它功能强大,但也引入了一套不太常规的开发模型,因而这个系列的文章仅u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E其它 3 种特性。u003Cu002Fpu003Eu003Cpu003E如文章标题,本文是这个系列的第一部分,将为你打开 Spring Boot 的大门,重点为你剖析其启动流程以及自动配置实现原理。要掌握这部分核心内容,理解一些 Spring 框架的基础知识,将会让你事半功倍。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003Eu003Cstrongu003E一、抛砖引玉:探索Spring IoC容器u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003E如果有看过u003Ccodeu003ESpringApplication.runu003Cu002Fcodeu003E方法的源码,Spring Boot 冗长无比的启动流程一定会让你抓狂,透过现象看本质,SpringApplication 只是将一个典型的 Spring 应用的启动流程进行了扩展,因此,透彻理解 Spring 容器是打开 Spring Boot 大门的一把钥匙。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch2 toutiao-origin=”h3″u003Eu003Cstrongu003E1.1、Spring IoC容器u003Cu002Fstrongu003Eu003Cu002Fh2u003Eu003Cpu003E可以把 Spring IoC 容器比作一间餐馆,当你来到餐馆,通常会直接招呼服务员:点菜!至于菜的原料是什么?如何用原料把菜做出来?可能你根本就不关心。IoC 容器也是一样,你只需要告诉它需要某个bean,它就把对应的实例(instance)扔给你,至于这个bean是否依赖其他组件,怎样完成它的初始化,根本就不需要你关心。u003Cu002Fpu003Eu003Cpu003E作为餐馆,想要做出菜肴,得知道菜的原料和菜谱,同样地,IoC 容器想要管理各个业务对象以及它们之间的依赖关系,需要通过某种途径来记录和管理这些信息。u003Ccodeu003EBeanDefinitionu003Cu002Fcodeu003E对象就承担了这个责任:容器中的每一个 bean 都会有一个对应的 BeanDefinition 实例,该实例负责保存bean对象的所有必要信息,包括 bean 对象的 class 类型、是否是抽象类、构造方法和参数、其它属性等等。当u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E客户端u003Cu002Fiu003E向容器请求相应对象时,容器就会通过这些信息为u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E客户端u003Cu002Fiu003E返回一个完整可用的 bean 实例。u003Cemu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E web_resource u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后获取更多干货!u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003E原材料已经准备好(把 BeanDefinition 看着原料),开始做菜吧,等等,你还需要一份菜谱,u003Ccodeu003EBeanDefinitionRegistryu003Cu002Fcodeu003E和u003Ccodeu003EBeanFactoryu003Cu002Fcodeu003E就是这份菜谱,BeanDefinitionRegistry 抽象出 bean 的注册逻辑,而 BeanFactory 则抽象出了 bean 的管理逻辑,而各个 BeanFactory 的实现类就具体承担了 bean 的注册以及管理工作。它们之间的关系就如下图:u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp9.pstatp.comu002Flargeu002Fpgc-imageu002FRcgGxEv5BCcWus” img_width=”720″ img_height=”176″ alt=”干货!没学过 Spring Boot?送你一份超详细的知识清单” inline=”0″u003Eu003Cpu003Eu003Ccodeu003EDefaultListableBeanFactoryu003Cu002Fcodeu003E作为一个比较通用的 BeanFactory 实现,它同时也实现了 BeanDefinitionRegistry 接口,因此它就承担了 Bean 的注册管理工作。从图中也可以看出,BeanFactory 接口中主要包含 getBean、containBean、getType、getAliases 等管理 bean 的方法,而 BeanDefinitionRegistry 接口则包含 registerBeanDefinition、removeBeanDefinition、getBeanDefinition 等注册管理 BeanDefinition 的方法。u003Cu002Fpu003Eu003Cpu003E下面通过一段简单的代码来模拟 BeanFactory 底层是如何工作的:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Eu002Fu002F 默认容器实现u003Cu002Fcodeu003Eu003Ccodeu003EDefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory;u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 根据业务对象构造相应的BeanDefinitionu003Cu002Fcodeu003Eu003Ccodeu003EAbstractBeanDefinition definition = new RootBeanDefinition(Business.class,true);u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 将bean定义注册到容器中u003Cu002Fcodeu003Eu003Ccodeu003EbeanRegistry.registerBeanDefinition(“beanName”,definition);u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 如果有多个bean,还可以指定各个bean之间的依赖关系u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F ……..u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Eu002Fu002F 然后可以从容器中获取这个bean的实例u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 注意:这里的beanRegistry其实实现了BeanFactory接口,所以可以强转,u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 单纯的BeanDefinitionRegistry是无法强制转换到BeanFactory类型的u003Cu002Fcodeu003Eu003Ccodeu003EBeanFactory container = (BeanFactory)beanRegistry;u003Cu002Fcodeu003Eu003Ccodeu003EBusiness business = (Business)container.getBean(“beanName”);u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E这段代码仅为了说明 BeanFactory 底层的大致工作流程,实际情况会更加复杂,比如 bean 之间的依赖关系可能定义在外部配置文件(XMLu002FProperties)中、也可能是注解方式。Spring IoC 容器的整个工作流程大致可以分为两个阶段:u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E1. 容器启动阶段u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E容器启动时,会通过某种途径加载u003Ccodeu003EConfigurationMetaDatau003Cu002Fcodeu003E。除了代码方式比较直接外,在大部分情况下,容器需要依赖某些工具类,比如:u003Ccodeu003EBeanDefinitionReaderu003Cu002Fcodeu003E,BeanDefinitionReader 会对加载的u003Ccodeu003EConfigurationMetaDatau003Cu002Fcodeu003E进行解析和分析,并将分析后的信息组装为相应的 BeanDefinition,最后把这些保存了 bean 定义的 BeanDefinition,注册到相应的 BeanDefinitionRegistry,这样容器的启动工作就完成了。这个阶段主u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003E要完u003Cu002Fiu003E成一些准备性工作,更侧重于 bean 对象管理信息的收集,当然一些验证性或者辅助性的工作也在这一阶段完成。u003Cemu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E web_resource u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后获取更多干货u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003E来看一个简单的例子吧,过往,所有的 bean 都定义在 XML 配置文件中,下面的代码将模拟 BeanFactory 如何从配置文件中加载 bean 的定义以及依赖关系:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Eu002Fu002F 通常为BeanDefinitionRegistry的实现类,这里以DeFaultListabeBeanFactory为例u003Cu002Fcodeu003Eu003Ccodeu003EBeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory; u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F XmlBeanDefinitionReader实现了BeanDefinitionReader接口,用于解析XML文件u003Cu002Fcodeu003Eu003Ccodeu003EXmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl(beanRegistry);u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 加载配置文件u003Cu002Fcodeu003Eu003Ccodeu003EbeanDefinitionReader.loadBeanDefinitions(“classpath:spring-bean.xml”);u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003Eu002Fu002F 从容器中获取bean实例u003Cu002Fcodeu003Eu003Ccodeu003EBeanFactory container = (BeanFactory)beanRegistry;u003Cu002Fcodeu003Eu003Ccodeu003EBusiness business = (Business)container.getBean(“beanName”);u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Cstrongu003E2. Bean的实例化阶段u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E经过第一阶段,所有 bean 定义都通过 BeanDefinition 的方式注册到 BeanDefinitionRegistry 中,当某个请求通过容器的 getBean 方法请求某个对象,或者因为依赖关系容器需要隐式的调用 getBean 时,就会触发第二阶段的活动:容器会首先检查所请求的对象之前是否已经实例化完成。如果没有,则会根据注册的 BeanDefinition 所提供的信息实例化被请求对象,并为其注入依赖。当该对象装配完毕后,容器会立即将其返回给请求方法使用。u003Cu002Fpu003Eu003Cpu003EBeanFactory 只是 Spring IoC 容器的一种实现,如果没有特殊指定,它采用采用延迟初始化策略:只有当访问容器中的某个对象时,才对该对象进行初始化和依赖注入操作。而在实际场景下,我们更多的使用另外一种类型的容器:u003Ccodeu003EApplicationContextu003Cu002Fcodeu003E,它构建在 BeanFactory 之上,属于更高级的容器,除了具有 BeanFactory 的所有能力之外,还提供对事件监听机制以及国际化的支持等。它管理的 bean,在容器启动时全部完成初始化和依赖注入操作。u003Cemu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E web_resource u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后获取更多干货u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch2 toutiao-origin=”h3″u003Eu003Cstrongu003E1.2、Spring容器扩展机制u003Cu002Fstrongu003Eu003Cu002Fh2u003Eu003Cpu003EIoC 容器负责管理容器中所有bean的生命周期,而在 bean 生命周期的不同阶段,Spring 提供了不同的扩展点来改变 bean 的命运。在容器的启动阶段,u003Ccodeu003EBeanFactoryPostProcessoru003Cu002Fcodeu003E允许我们在容器实例化相应对象之前,对注册到容器的 BeanDefinition 所保存的信息做一些额外的操作,比如修改 bean 定义的某些属性或者增加其他信息等。u003Cu002Fpu003Eu003Cpu003E如果要自定义扩展类,通常需要实现u003Ccodeu003Eorg.springframework.beans.factory.config.BeanFactoryPostProcessoru003Cu002Fcodeu003E接口,与此同时,因为容器中可能有多个BeanFactoryPostProcessor,可能还需要实现u003Ccodeu003Eorg.springframework.core.Orderedu003Cu002Fcodeu003E接口,以保证BeanFactoryPostProcessor按照顺序执行。Spring提供了为数不多的BeanFactoryPostProcessor实现,我们以u003Ccodeu003EPropertyPlaceholderConfigureru003Cu002Fcodeu003E来说明其大致的工作流程。u003Cu002Fpu003Eu003Cpu003E在Spring项目的XML配置文件中,经常可以看到许多配置项的值使用占位符,而将占位符所代表的值单独配置到独立的properties文件,这样可以将散落在不同XML文件中的配置集中管理,而且也方便运维根据不同的环境进行配置不同的值。这个非常实用的功能就是由PropertyPlaceholderConfigurer负责实现的。u003Cu002Fpu003Eu003Cpu003E根据前文,当BeanFactory在第一阶段加载完所有配置信息时,BeanFactory中保存的对象的属性还是以占位符方式存在的,比如u003Ccodeu003E${jdbc.mysql.url}u003Cu002Fcodeu003E。当PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用时,它会使用properties配置文件中的值来替换相应的BeanDefinition中占位符所表示的属性值。当需要实例化bean时,bean定义中的属性值就已经被替换成我们配置的值。当然其实现比上面描述的要复杂一些,这里仅说明其大致工作原理,更详细的实现可以参考其源码。u003Cemu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E web_resource u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后获取更多干货u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003E与之相似的,还有u003Ccodeu003EBeanPostProcessoru003Cu002Fcodeu003E,其存在于对象实例化阶段。跟BeanFactoryPostProcessor类似,它会处理容器内所有符合条件并且已经实例化后的对象。简单的对比,BeanFactoryPostProcessor处理bean的定义,而BeanPostProcessor则处理bean完成实例化后的对象。BeanPostProcessor定义了两个接口:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Epublic interface BeanPostProcessor {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 前置处理u003Cu002Fcodeu003Eu003Ccodeu003E Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 后置处理u003Cu002Fcodeu003Eu003Ccodeu003E Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E为了理解这两个方法执行的时机,简单的了解下bean的整个生命周期:u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRcgGxFGFdE2F3n” img_width=”720″ img_height=”341″ alt=”干货!没学过 Spring Boot?送你一份超详细的知识清单” inline=”0″u003Eu003Cpu003Eu003Ccodeu003EpostProcessBeforeInitializationu003Cu002Fcodeu003E方法与u003Ccodeu003EpostProcessAfterInitializationu003Cu002Fcodeu003E分别对应图中前置处理和后置处理两个步骤将执行的方法。这两个方法中都传入了bean对象实例的引用,为扩展容器的对象实例化过程提供了很大便利,在这儿几乎可以对传入的实例执行任何操作。注解、AOP等功能的实现均大量使用了u003Ccodeu003EBeanPostProcessoru003Cu002Fcodeu003E,比如有一个自定义注解,你完全可以实现BeanPostProcessor的接口,在其中判断bean对象的脑袋上是否有该注解,如果有,你可以对这个bean实例执行任何操作,想想是不是非常的简单?u003Cu002Fpu003Eu003Cpu003E再来看一个更常见的例子,在Spring中经常能够看到各种各样的Aware接口,其作用就是在对象实例化完成以后将Aware接口定义中规定的依赖注入到当前实例中。比如最常见的u003Ccodeu003EApplicationContextAwareu003Cu002Fcodeu003E接口,实现了这个接口的类都可以获取到一个ApplicationContext对象。当容器中每个对象的实例化过程走到BeanPostProcessor前置处理这一步时,容器会检测到之前注册到容器的ApplicationContextAwareProcessor,然后就会调用其postProcessBeforeInitialization方法,检查并设置Aware相关依赖。看看代码吧,是不是很简单:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Eu002Fu002F 代码来自:org.springframework.context.support.ApplicationContextAwareProcessoru003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 其postProcessBeforeInitialization方法调用了invokeAwareInterfaces方法u003Cu002Fcodeu003Eu003Ccodeu003Eprivate void invokeAwareInterfaces(Object bean) {u003Cu002Fcodeu003Eu003Ccodeu003E if (bean instanceof EnvironmentAware) {u003Cu002Fcodeu003Eu003Ccodeu003E ((EnvironmentAware) bean).setEnvironment(this.u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003ElicationContext.getEnvironment);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E if (bean instanceof ApplicationContextAware) {u003Cu002Fcodeu003Eu003Ccodeu003E ((ApplicationContextAware) bean).setApplicationContext(this.u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003ElicationContext);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ……u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E最后总结一下,本小节内容和你一起回顾了Spring容器的部分核心内容,限于篇幅不能写更多,但理解这部分内容,足以让您轻松理解Spring Boot的启动原理,如果在后续的学习过程中遇到一些晦涩难懂的知识,再回过头来看看Spring的核心知识,也许有意想不到的效果。也许Spring Boot的中文资料很少,但Spring的中文资料和书籍有太多太多,总有东西能给你启发。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003Eu003Cstrongu003E二、夯实基础:JavaConfig与常见Annotationu003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch2 toutiao-origin=”h3″u003Eu003Cstrongu003E2.1、JavaConfigu003Cu002Fstrongu003Eu003Cu002Fh2u003Eu003Cpu003E我们知道u003Ccodeu003Ebeanu003Cu002Fcodeu003E是Spring IOC中非常核心的概念,Spring容器负责bean的生命周期的管理。在最初,Spring使用XML配置文件的方式来描述bean的定义以及相互间的依赖关系,但随着Spring的发展,越来越多的人对这种方式表示不满,因为Spring项目的所有业务类均以bean的形式配置在XML文件中,造成了大量的XML文件,使项目变得复杂且难以管理。u003Cu002Fpu003Eu003Cpu003E后来,基于纯Java Annotation依赖注入框架u003Ccodeu003EGuiceu003Cu002Fcodeu003E出世,其性能明显优于采用XML方式的Spring,甚至有部分人认为,u003Ccodeu003EGuiceu003Cu002Fcodeu003E可以完全取代Spring(u003Ccodeu003EGuiceu003Cu002Fcodeu003E仅是一个轻量级IOC框架,取代Spring还差的挺远)。正是这样的危机感,促使Spring及社区推出并持续完善了u003Ccodeu003EJavaConfigu003Cu002Fcodeu003E子项目,它基于Java代码和Annotation注解来描述bean之间的依赖绑定关系。比如,下面是使用XML配置方式来描述bean的定义:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003E<bean id=”bookService” class=”cn.moondev.service.BookServiceImpl”><u002Fbean>u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E而基于JavaConfig的配置形式是这样的:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003E@Configurationu003Cu002Fcodeu003Eu003Ccodeu003Epublic class MoonBookConfiguration {u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003Eu002Fu002F 任何标志了@Bean的方法,其返回值将作为一个bean注册到Spring的IOC容器中u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 方法名默认成为该bean定义的idu003Cu002Fcodeu003Eu003Ccodeu003E @Beanu003Cu002Fcodeu003Eu003Ccodeu003E public BookService bookService {u003Cu002Fcodeu003Eu003Ccodeu003E return new BookServiceImpl;u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E如果两个bean之间有依赖关系的话,在XML配置中应该是这样:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003E<bean id=”bookService” class=”cn.moondev.service.BookServiceImpl”>u003Cu002Fcodeu003Eu003Ccodeu003E <property name=”dependencyService” ref=”dependencyService”u002F>u003Cu002Fcodeu003Eu003Ccodeu003E<u002Fbean>u003Cu002Fcodeu003Eu003Ccodeu003E<bean id=”otherService” class=”cn.moondev.service.OtherServiceImpl”>u003Cu002Fcodeu003Eu003Ccodeu003E <property name=”dependencyService” ref=”dependencyService”u002F>u003Cu002Fcodeu003Eu003Ccodeu003E<u002Fbean>u003Cu002Fcodeu003Eu003Ccodeu003E<bean id=”dependencyService” class=”DependencyServiceImpl”u002F>u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E而在JavaConfig中则是这样:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003E@Configurationu003Cu002Fcodeu003Eu003Ccodeu003Epublic class MoonBookConfiguration {u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003Eu002Fu002F 如果一个bean依赖另一个bean,则直接调用对应JavaConfig类中依赖bean的创建方法即可u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 这里直接调用dependencyServiceu003Cu002Fcodeu003Eu003Ccodeu003E @Beanu003Cu002Fcodeu003Eu003Ccodeu003E public BookService bookService {u003Cu002Fcodeu003Eu003Ccodeu003E return new BookServiceImpl(dependencyService);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003E@Beanu003Cu002Fcodeu003Eu003Ccodeu003E public OtherService otherService {u003Cu002Fcodeu003Eu003Ccodeu003E return new OtherServiceImpl(dependencyService);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003E@Beanu003Cu002Fcodeu003Eu003Ccodeu003E public DependencyService dependencyService {u003Cu002Fcodeu003Eu003Ccodeu003E return new DependencyServiceImpl;u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E你可能注意到这个示例中,有两个bean都依赖于dependencyService,也就是说当初始化bookService时会调用u003Ccodeu003EdependencyServiceu003Cu002Fcodeu003E,在初始化otherService时也会调用u003Ccodeu003EdependencyServiceu003Cu002Fcodeu003E,那么问题来了?这时候IOC容器中是有一个dependencyService实例还是两个?这个问题留着大家思考吧,这里不再赘述。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch2 toutiao-origin=”h3″u003Eu003Cstrongu003E2.2、@ComponentScanu003Cu002Fstrongu003Eu003Cu002Fh2u003Eu003Cpu003Eu003Ccodeu003E@ComponentScanu003Cu002Fcodeu003E注解对应XML配置形式中的u003Ccodeu003E<context:component-scan>u003Cu002Fcodeu003E元素,表示启用组件扫描,Spring会自动扫描所有通过注解配置的bean,然后将其注册到IOC容器中。我们可以通过u003Ccodeu003EbasePackagesu003Cu002Fcodeu003E等属性来指定u003Ccodeu003E@ComponentScanu003Cu002Fcodeu003E自动扫描的范围,如果不指定,默认从声明u003Ccodeu003E@ComponentScanu003Cu002Fcodeu003E所在类的u003Ccodeu003Epackageu003Cu002Fcodeu003E进行扫描。正因为如此,SpringBoot的启动类都默认在u003Ccodeu003Esrcu002Fmainu002Fjavau003Cu002Fcodeu003E下。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch2 toutiao-origin=”h3″u003Eu003Cstrongu003E2.3、@Importu003Cu002Fstrongu003Eu003Cu002Fh2u003Eu003Cpu003Eu003Ccodeu003E@Importu003Cu002Fcodeu003E注解用于导入配置类,举个简单的例子:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003E@Configurationu003Cu002Fcodeu003Eu003Ccodeu003Epublic class MoonBookConfiguration {u003Cu002Fcodeu003Eu003Ccodeu003E @Beanu003Cu002Fcodeu003Eu003Ccodeu003E public BookService bookService {u003Cu002Fcodeu003Eu003Ccodeu003E return new BookServiceImpl;u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E现在有另外一个配置类,比如:u003Ccodeu003EMoonUserConfigurationu003Cu002Fcodeu003E,这个配置类中有一个bean依赖于u003Ccodeu003EMoonBookConfigurationu003Cu002Fcodeu003E中的bookService,如何将这两个bean组合在一起?借助u003Ccodeu003E@Importu003Cu002Fcodeu003E即可:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003E@Configurationu003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 可以同时导入多个配置类,比如:@Import({A.class,B.class})u003Cu002Fcodeu003Eu003Ccodeu003E@Import(MoonBookConfiguration.class)u003Cu002Fcodeu003Eu003Ccodeu003Epublic class MoonUserConfiguration {u003Cu002Fcodeu003Eu003Ccodeu003E @Beanu003Cu002Fcodeu003Eu003Ccodeu003E public UserService userService(BookService bookService) {u003Cu002Fcodeu003Eu003Ccodeu003E return new BookServiceImpl(bookService);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E需要注意的是,在4.2之前,u003Ccodeu003E@Importu003Cu002Fcodeu003E注解只支持导入配置类,但是在4.2之后,它支持导入普通类,并将这个类作为一个bean的定义注册到IOC容器中。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch2 toutiao-origin=”h3″u003Eu003Cstrongu003E2.4、@Conditionalu003Cu002Fstrongu003Eu003Cu002Fh2u003Eu003Cpu003Eu003Ccodeu003E@Conditionalu003Cu002Fcodeu003E注解表示在满足某种条件后才初始化一个bean或者启用某些配置。它一般用在由u003Ccodeu003E@Componentu003Cu002Fcodeu003E、u003Ccodeu003E@Serviceu003Cu002Fcodeu003E、u003Ccodeu003E@Configurationu003Cu002Fcodeu003E等注解标识的类上面,或者由u003Ccodeu003E@Beanu003Cu002Fcodeu003E标记的方法上。如果一个u003Ccodeu003E@Configurationu003Cu002Fcodeu003E类标记了u003Ccodeu003E@Conditionalu003Cu002Fcodeu003E,则该类中所有标识了u003Ccodeu003E@Beanu003Cu002Fcodeu003E的方法和u003Ccodeu003E@Importu003Cu002Fcodeu003E注解导入的相关类将遵从这些条件。u003Cemu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E web_resource u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后获取更多干货u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003E在Spring里可以很方便的编写你自己的条件类,所要做的就是实现u003Ccodeu003EConditionu003Cu002Fcodeu003E接口,并覆盖它的u003Ccodeu003Ematchesu003Cu002Fcodeu003E方法。举个例子,下面的简单条件类表示只有在u003Ccodeu003EClasspathu003Cu002Fcodeu003E里存在u003Ccodeu003EJdbcTemplateu003Cu002Fcodeu003E类时才生效:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Epublic class JdbcTemplateCondition implements Condition {u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003E@Overrideu003Cu002Fcodeu003Eu003Ccodeu003E public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {u003Cu002Fcodeu003Eu003Ccodeu003E try {u003Cu002Fcodeu003Eu003Ccodeu003E conditionContext.getClassLoader.loadClass(“org.springframework.jdbc.core.JdbcTemplate”);u003Cu002Fcodeu003Eu003Ccodeu003E return true;u003Cu002Fcodeu003Eu003Ccodeu003E } catch (ClassNotFoundException e) {u003Cu002Fcodeu003Eu003Ccodeu003E e.printStackTrace;u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E return false;u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E当你用Java来声明bean的时候,可以使用这个自定义条件类:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003E@Conditional(JdbcTemplateCondition.class)u003Cu002Fcodeu003Eu003Ccodeu003E@Serviceu003Cu002Fcodeu003Eu003Ccodeu003Epublic MyService service {u003Cu002Fcodeu003Eu003Ccodeu003E ……u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E这个例子中只有当u003Ccodeu003EJdbcTemplateConditionu003Cu002Fcodeu003E类的条件成立时才会创建MyService这个bean。也就是说MyService这bean的创建条件是u003Ccodeu003Eclasspathu003Cu002Fcodeu003E里面包含u003Ccodeu003EJdbcTemplateu003Cu002Fcodeu003E,否则这个bean的声明就会被忽略掉。u003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003ESpringBootu003Cu002Fcodeu003E定义了很多有趣的条件,并把他们运用到了配置类上,这些配置类构成了u003Ccodeu003ESpringBootu003Cu002Fcodeu003E的自动配置的基础。u003Ccodeu003ESpringBootu003Cu002Fcodeu003E运用条件化配置的方法是:定义多个特殊的条件化注解,并将它们用到配置类上。下面列出了u003Ccodeu003ESpringBootu003Cu002Fcodeu003E提供的部分条件化注解:u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp9.pstatp.comu002Flargeu002Fpgc-imageu002FRcgGxFRz7QGgL” img_width=”687″ img_height=”386″ alt=”干货!没学过 Spring Boot?送你一份超详细的知识清单” inline=”0″u003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch2 toutiao-origin=”h3″u003Eu003Cstrongu003E2.5、@ConfigurationProperties与@EnableConfigurationPropertiesu003Cu002Fstrongu003Eu003Cu002Fh2u003Eu003Cpu003E当某些属性的值需要配置的时候,我们一般会在u003Ccodeu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003Elication.propertiesu003Cu002Fcodeu003E文件中新建配置项,然后在bean中使用u003Ccodeu003E@Valueu003Cu002Fcodeu003E注解来获取配置的值,比如下面配置数据源的代码。u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Eu002Fu002F jdbc configu003Cu002Fcodeu003Eu003Ccodeu003Ejdbc.mysql.url=jdbc:mysql:u002Fu002Flocau003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Elhu003Cu002Fiu003Eost:3u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E30u003Cu002Fiu003E6u002Fsampledbu003Cu002Fcodeu003Eu003Ccodeu003Ejdbc.mysql.username=rootu003Cu002Fcodeu003Eu003Ccodeu003Ejdbc.mysql.password=123456u003Cu002Fcodeu003Eu003Ccodeu003E……u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Eu002Fu002F 配置数据源u003Cu002Fcodeu003Eu003Ccodeu003E@Configurationu003Cu002Fcodeu003Eu003Ccodeu003Epublic class HikariDataSourceConfiguration {u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003E@Value(“jdbc.mysql.url”)u003Cu002Fcodeu003Eu003Ccodeu003E public String url;u003Cu002Fcodeu003Eu003Ccodeu003E @Value(“jdbc.mysql.username”)u003Cu002Fcodeu003Eu003Ccodeu003E public String user;u003Cu002Fcodeu003Eu003Ccodeu003E @Value(“jdbc.mysql.password”)u003Cu002Fcodeu003Eu003Ccodeu003E public String password;u003Cu002Fcodeu003Eu003Ccodeu003E @Beanu003Cu002Fcodeu003Eu003Ccodeu003E public HikariDataSource dataSource {u003Cu002Fcodeu003Eu003Ccodeu003E HikariConfig hikariConfig = new HikariConfig;u003Cu002Fcodeu003Eu003Ccodeu003E hikariConfig.setJdbcUrl(url);u003Cu002Fcodeu003Eu003Ccodeu003E hikariConfig.setUsername(user);u003Cu002Fcodeu003Eu003Ccodeu003E hikariConfig.setPassword(password);u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 省略部分代码u003Cu002Fcodeu003Eu003Ccodeu003E return new HikariDataSource(hikariConfig);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E使用u003Ccodeu003E@Valueu003Cu002Fcodeu003E注解注入的属性通常都比较简单,如果同一个配置在多个地方使用,也存在不方便维护的问题(考虑下,如果有几十个地方在使用某个配置,而现在你想改下名字,你改怎么做?)。对于更为复杂的配置,Spring Boot提供了更优雅的实现方式,那就是u003Ccodeu003E@ConfigurationPropertiesu003Cu002Fcodeu003E注解。我们可以通过下面的方式来改写上面的代码:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003E@Componentu003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 还可以通过@PropertySource(“classpath:jdbc.properties”)来指定配置文件u003Cu002Fcodeu003Eu003Ccodeu003E@ConfigurationProperties(“jdbc.mysql”)u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 前缀=jdbc.mysql,会在配置文件中寻找jdbc.mysql.*的配置项u003Cu002Fcodeu003Eu003Ccodeu003Epulic class JdbcConfig {u003Cu002Fcodeu003Eu003Ccodeu003E public String url;u003Cu002Fcodeu003Eu003Ccodeu003E public String username;u003Cu002Fcodeu003Eu003Ccodeu003E public String password;u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003E@Configurationu003Cu002Fcodeu003Eu003Ccodeu003Epublic class HikariDataSourceConfiguration {u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003E@AutoWiredu003Cu002Fcodeu003Eu003Ccodeu003E public JdbcConfig config;u003Cu002Fcodeu003Eu003Ccodeu003E @Beanu003Cu002Fcodeu003Eu003Ccodeu003E public HikariDataSource dataSource {u003Cu002Fcodeu003Eu003Ccodeu003E HikariConfig hikariConfig = new HikariConfig;u003Cu002Fcodeu003Eu003Ccodeu003E hikariConfig.setJdbcUrl(config.url);u003Cu002Fcodeu003Eu003Ccodeu003E hikariConfig.setUsername(config.username);u003Cu002Fcodeu003Eu003Ccodeu003E hikariConfig.setPassword(config.password);u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 省略部分代码u003Cu002Fcodeu003Eu003Ccodeu003E return new HikariDataSource(hikariConfig);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Ccodeu003E@ConfigurationPropertiesu003Cu002Fcodeu003E对于更为复杂的配置,处理起来也是得心应手,比如有如下配置文件:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003E#u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003EAppu003Cu002Fiu003Eu003Cu002Fcodeu003Eu003Ccodeu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003E.menus[0].title=Homeu003Cu002Fcodeu003Eu003Ccodeu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003E.menus[0].name=Homeu003Cu002Fcodeu003Eu003Ccodeu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003E.menus[0].path=u002Fu003Cu002Fcodeu003Eu003Ccodeu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003E.menus[1].title=Loginu003Cu002Fcodeu003Eu003Ccodeu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003E.menus[1].name=Loginu003Cu002Fcodeu003Eu003Ccodeu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003E.menus[1].path=u002Floginu003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E.comu003Cu002Fiu003Epiler.timeout=5u003Cu002Fcodeu003Eu003Ccodeu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E.comu003Cu002Fiu003Epiler.output-folder=u002Ftempu002Fu003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003E.error=u002Ferroru002Fu003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E可以定义如下配置类来接收这些属性u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003E@Componentu003Cu002Fcodeu003Eu003Ccodeu003E@ConfigurationProperties(“u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003E”)u003Cu002Fcodeu003Eu003Ccodeu003Epublic class AppProperties {u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003Epublic String error;u003Cu002Fcodeu003Eu003Ccodeu003E public List<Menu> menus = new ArrayList<>;u003Cu002Fcodeu003Eu003Ccodeu003E public Compiler compiler = new Compiler;u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003Epublic static class Menu {u003Cu002Fcodeu003Eu003Ccodeu003E public String name;u003Cu002Fcodeu003Eu003Ccodeu003E public String path;u003Cu002Fcodeu003Eu003Ccodeu003E public String title;u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003Epublic static class Compiler {u003Cu002Fcodeu003Eu003Ccodeu003E public String timeout;u003Cu002Fcodeu003Eu003Ccodeu003E public String outputFolder;u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Ccodeu003E@EnableConfigurationPropertiesu003Cu002Fcodeu003E注解表示对u003Ccodeu003E@ConfigurationPropertiesu003Cu002Fcodeu003E的内嵌支持,默认会将对应Properties Class作为bean注入的IOC容器中,即在相应的Properties类上不用加u003Ccodeu003E@Componentu003Cu002Fcodeu003E注解。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003Eu003Cstrongu003E三、削铁如泥:SpringFactoriesLoader详解u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003EJVM提供了3种类加载器:u003Ccodeu003EBootstrapClassLoaderu003Cu002Fcodeu003E、u003Ccodeu003EExtClassLoaderu003Cu002Fcodeu003E、u003Ccodeu003EAppClassLoaderu003Cu002Fcodeu003E分别加载Java核心类库、扩展类库以及应用的类路径(u003Ccodeu003ECLASSPATHu003Cu002Fcodeu003E)下的类库。JVM通过双亲委派模型进行类的加载,我们也可以通过继承u003Ccodeu003Ejava.lang.classloaderu003Cu002Fcodeu003E实现自己的类加载器。u003Cu002Fpu003Eu003Cpu003E何为双亲委派模型?当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader,只有当父加载器无法完成加载任务时,才会尝试自己来加载。u003Cemu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E web_resource u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后获取更多干货u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003E采用双亲委派模型的一个好处是保证使用不同类加载器最终得到的都是同一个对象,这样就可以保证Java 核心库的类型安全,比如,加载位于rt.jar包中的u003Ccodeu003Ejava.lang.Objectu003Cu002Fcodeu003E类,不管是哪个加载器加载这个类,最终都是委托给顶层的BootstrapClassLoader来加载的,这样就可以保证任何的类加载器最终得到的都是同样一个Object对象。查看ClassLoader的源码,对双亲委派模型会有更直观的认识:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Eprotected Class<?> loadClass(String name, boolean resolve) {u003Cu002Fcodeu003Eu003Ccodeu003E synchronized (getClassLoadingLock(name)) {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 首先,检查该类是否已经被加载,如果从JVM缓存中找到该类,则直接返回u003Cu002Fcodeu003Eu003Ccodeu003E Class<?> c = findLoadedClass(name);u003Cu002Fcodeu003Eu003Ccodeu003E if (c == ) {u003Cu002Fcodeu003Eu003Ccodeu003E try {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 遵循双亲委派的模型,首先会通过递归从父加载器开始找,u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 直到父类加载器是BootstrapClassLoader为止u003Cu002Fcodeu003Eu003Ccodeu003E if (parent != ) {u003Cu002Fcodeu003Eu003Ccodeu003E c = parent.loadClass(name, false);u003Cu002Fcodeu003Eu003Ccodeu003E } else {u003Cu002Fcodeu003Eu003Ccodeu003E c = findBootstrapClassOr(name);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E } catch (ClassNotFoundException e) {}u003Cu002Fcodeu003Eu003Ccodeu003E if (c == ) {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 如果还找不到,尝试通过findClass方法去寻找u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F findClass是留给开发者自己实现的,也就是说u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 自定义类加载器时,重写此方法即可u003Cu002Fcodeu003Eu003Ccodeu003E c = findClass(name);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E if (resolve) {u003Cu002Fcodeu003Eu003Ccodeu003E resolveClass(c);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E return c;u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E但双亲委派模型并不能解决所有的类加载器问题,比如,Java 提供了很多服务提供者接口(u003Ccodeu003EServiceProviderInterfaceu003Cu002Fcodeu003E,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JNDI、JAXP 等,这些SPI的接口由核心类库提供,却由第三方实现,这样就存在一个问题:SPI 的接口是 Java 核心库的一部分,是由BootstrapClassLoader加载的;SPI实现的Java类一般是由AppClassLoader来加载的。BootstrapClassLoader是无法找到 SPI 的实现类的,因为它只加载Java的核心库。它也不能代理给AppClassLoader,因为它是最顶层的类加载器。也就是说,双亲委派模型并不能解决这个问题。u003Cu002Fpu003Eu003Cpu003E线程上下文类加载器(u003Ccodeu003EContextClassLoaderu003Cu002Fcodeu003E)正好解决了这个问题。从名称上看,可能会误解为它是一种新的类加载器,实际上,它仅仅是Thread类的一个变量而已,可以通过u003Ccodeu003EsetContextClassLoader(ClassLoadercl)u003Cu002Fcodeu003E和u003Ccodeu003EgetContextClassLoaderu003Cu002Fcodeu003E来设置和获取该对象。如果不做任何的设置,Java应用的线程的上下文类加载器默认就是AppClassLoader。在核心类库使用SPI接口时,传递的类加载器使用线程上下文类加载器,就可以成功的加载到SPI实现的类。线程上下文类加载器在很多SPI的实现中都会用到。但在JDBC中,你可能会看到一种更直接的实现方式,比如,JDBC驱动管理u003Ccodeu003Ejava.sql.Driveru003Cu002Fcodeu003E中的u003Ccodeu003EloadInitialDriversu003Cu002Fcodeu003E方法中,你可以直接看到JDK是如何加载驱动的:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Efor (String aDriver : driversList) {u003Cu002Fcodeu003Eu003Ccodeu003E try {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 直接使用AppClassLoaderu003Cu002Fcodeu003Eu003Ccodeu003E Class.forName(aDriver, true, ClassLoader.getSystemClassLoader);u003Cu002Fcodeu003Eu003Ccodeu003E } catch (Exception ex) {u003Cu002Fcodeu003Eu003Ccodeu003E println(“DriverManager.Initialize: load failed: ” + ex);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E其实讲解线程上下文类加载器,最主要是让大家在看到u003Ccodeu003EThread.currentThread.getClassLoaderu003Cu002Fcodeu003E和u003Ccodeu003EThread.currentThread.getContextClassLoaderu003Cu002Fcodeu003E时不会一脸懵逼,这两者除了在许多底层框架中取得的ClassLoader可能会有所不同外,其他大多数业务场景下都是一样的,大家只要知道它是为了解决什么问题而存在的即可。u003Cu002Fpu003Eu003Cpu003E类加载器除了加载class外,还有一个非常重要功能,就是加载资源,它可以从jar包中读取任何资源文件,比如,u003Ccodeu003EClassLoader.getResources(Stringname)u003Cu002Fcodeu003E方法就是用于读取jar包中的资源文件,其代码如下:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Epublic Enumeration<URL> getResources(String name) throws IOException {u003Cu002Fcodeu003Eu003Ccodeu003E Enumeration<URL> tmp = (Enumeration<URL>[]) new Enumeration<?>[2];u003Cu002Fcodeu003Eu003Ccodeu003E if (parent != ) {u003Cu002Fcodeu003Eu003Ccodeu003E tmp[0] = parent.getResources(name);u003Cu002Fcodeu003Eu003Ccodeu003E } else {u003Cu002Fcodeu003Eu003Ccodeu003E tmp[0] = getBootstrapResources(name);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E tmp[1] = findResources(name);u003Cu002Fcodeu003Eu003Ccodeu003E return new CompoundEnumeration<>(tmp);u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E是不是觉得有点眼熟,不错,它的逻辑其实跟类加载的逻辑是一样的,首先判断父类加载器是否为空,不为空则委托父类加载器执行资源查找任务,直到BootstrapClassLoader,最后才轮到自己查找。而不同的类加载器负责扫描不同路径下的jar包,就如同加载class一样,最后会扫描所有的jar包,找到符合条件的资源文件。u003Cemu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E web_resource u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后获取更多干货u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003E类加载器的u003Ccodeu003EfindResources(name)u003Cu002Fcodeu003E方法会遍历其负责加载的所有jar包,找到jar包中名称为name的资源文件,这里的资源可以是任何文件,甚至是.class文件,比如下面的示例,用于查找Array.class文件:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Eu002Fu002F 寻找Array.class文件u003Cu002Fcodeu003Eu003Ccodeu003Epublic static void main(String[] args) throws Exception{u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F Array.class的完整路径u003Cu002Fcodeu003Eu003Ccodeu003E String name = “javau002Fsqlu002FArray.class”;u003Cu002Fcodeu003Eu003Ccodeu003E Enumeration<URL> urls = Thread.currentThread.getContextClassLoader.getResources(name);u003Cu002Fcodeu003Eu003Ccodeu003E while (urls.hasMoreElements) {u003Cu002Fcodeu003Eu003Ccodeu003E URL url = urls.nextElement;u003Cu002Fcodeu003Eu003Ccodeu003E System.out.println(url.toString);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E运行后可以得到如下结果:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003E$JAVA_HOMEu002Fjreu002Flibu002Frt.jar!u002Fjavau002Fsqlu002FArray.classu003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cu002Fpreu003Eu003Cpu003E根据资源文件的URL,可以构造相应的文件来读取资源内容。u003Cu002Fpu003Eu003Cpu003E看到这里,你可能会感到挺奇怪的,你不是要详解u003Ccodeu003ESpringFactoriesLoaderu003Cu002Fcodeu003E吗?上来讲了一堆ClassLoader是几个意思?看下它的源码你就知道了:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003Epublic static final String FACTORIES_RESOURCE_LOCATION = “META-INFu002Fspring.factories”;u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F spring.factories文件的格式为:key=value1,value2,value3u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 从所有的jar包中找到META-INFu002Fspring.factories文件u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 然后从文件中解析出key=factoryClass类名称的所有value值u003Cu002Fcodeu003Eu003Ccodeu003Epublic static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {u003Cu002Fcodeu003Eu003Ccodeu003E String factoryClassName = factoryClass.getName;u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 取得资源文件的URLu003Cu002Fcodeu003Eu003Ccodeu003E Enumeration<URL> urls = (classLoader != ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));u003Cu002Fcodeu003Eu003Ccodeu003E List<String> result = new ArrayList<String>;u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 遍历所有的URLu003Cu002Fcodeu003Eu003Ccodeu003E while (urls.hasMoreElements) {u003Cu002Fcodeu003Eu003Ccodeu003E URL url = urls.nextElement;u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 根据资源文件URL解析properties文件u003Cu002Fcodeu003Eu003Ccodeu003E Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));u003Cu002Fcodeu003Eu003Ccodeu003E String factoryClassNames = properties.getProperty(factoryClassName);u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 组装数据,并返回u003Cu002Fcodeu003Eu003Ccodeu003E result.addAll(Arrays.asList(StringUtilsu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E.comu003Cu002Fiu003EmaDelimitedListToStringArray(factoryClassNames)));u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E return result;u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cu002Fpreu003Eu003Cpu003E有了前面关于ClassLoader的知识,再来理解这段代码,是不是感觉豁然开朗:从u003Ccodeu003ECLASSPATHu003Cu002Fcodeu003E下的每个Jar包中搜寻所有u003Ccodeu003EMETA-INFu002Fspring.factoriesu003Cu002Fcodeu003E配置文件,然后将解析properties文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。来简单看下u003Ccodeu003Espring.factoriesu003Cu002Fcodeu003E文件的内容吧:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003Eu002Fu002F 来自 org.springframework.boot.autoconfigure下的META-INFu002Fspring.factoriesu003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F EnableAutoConfiguration后文会讲到,它用于开启Spring Boot自动配置功能u003Cu002Fcodeu003Eu003Ccodeu003Eorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\u003Cu002Fcodeu003Eu003Ccodeu003Eorg.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\u003Cu002Fcodeu003Eu003Ccodeu003Eorg.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\u003Cu002Fcodeu003Eu003Ccodeu003Eorg.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cu002Fpreu003Eu003Cpu003E执行u003Ccodeu003EloadFactoryNames(EnableAutoConfiguration.class,classLoader)u003Cu002Fcodeu003E后,得到对应的一组u003Ccodeu003E@Configurationu003Cu002Fcodeu003E类, 我们就可以通过反射实例化这些类然后注入到IOC容器中,最后容器里就有了一系列标注了u003Ccodeu003E@Configurationu003Cu002Fcodeu003E的JavaConfig形式的配置类。u003Cu002Fpu003Eu003Cpu003E这就是u003Ccodeu003ESpringFactoriesLoaderu003Cu002Fcodeu003E,它本质上属于Spring框架私有的一种扩展方案,类似于SPI,Spring Boot在Spring基础上的很多核心功能都是基于此,希望大家可以理解。u003Cemu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E web_resource u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后获取更多干货u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003Eu003Cstrongu003E四、另一件武器:Spring容器的事件监听机制u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003E过去,事件监听机制多用于图形界面编程,比如:u003Cstrongu003E点击u003Cu002Fstrongu003E按钮、在文本框u003Cstrongu003E输入u003Cu002Fstrongu003E内容等操作被称为事件,而当事件触发时,应用程序作出一定的响应则表示应用监听了这个事件,而在服务器端,事件的监听机制更多的用于异步通知以及监控和异常处理。Java提供了实现事件监听机制的两个基础类:自定义事件类型扩展自u003Ccodeu003Ejava.util.EventObjectu003Cu002Fcodeu003E、事件的监听器扩展自u003Ccodeu003Ejava.util.EventListeneru003Cu002Fcodeu003E。来看一个简单的实例:简单的监控一个方法的耗时。u003Cu002Fpu003Eu003Cpu003E首先定义事件类型,通常的做法是扩展EventObject,随着事件的发生,相应的状态通常都封装在此类中:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003Epublic class MethodMonitorEvent extends EventObject {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 时间戳,用于记录方法开始执行的时间u003Cu002Fcodeu003Eu003Ccodeu003E public long timestamp;u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E public MethodMonitorEvent(Object source) {u003Cu002Fcodeu003Eu003Ccodeu003E super(source);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cu002Fpreu003Eu003Cpu003E事件发布之后,相应的监听器即可对该类型的事件进行处理,我们可以在方法开始执行之前发布一个begin事件,在方法执行结束之后发布一个end事件,相应地,事件监听器需要提供方法对这两种情况下接收到的事件进行处理:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003Eu002Fu002F 1、定义事件监听接口u003Cu002Fcodeu003Eu003Ccodeu003Epublic interface MethodMonitorEventListener extends EventListener {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 处理方法执行之前发布的事件u003Cu002Fcodeu003Eu003Ccodeu003E public void onMethodBegin(MethodMonitorEvent event);u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 处理方法结束时发布的事件u003Cu002Fcodeu003Eu003Ccodeu003E public void onMethodEnd(MethodMonitorEvent event);u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 2、事件监听接口的实现:如何处理u003Cu002Fcodeu003Eu003Ccodeu003Epublic class AbstractMethodMonitorEventListener implements MethodMonitorEventListener {u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E @Overrideu003Cu002Fcodeu003Eu003Ccodeu003E public void onMethodBegin(MethodMonitorEvent event) {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 记录方法开始执行时的时间u003Cu002Fcodeu003Eu003Ccodeu003E event.timestamp = System.currentTimeMillis;u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E @Overrideu003Cu002Fcodeu003Eu003Ccodeu003E public void onMethodEnd(MethodMonitorEvent event) {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 计算方法耗时u003Cu002Fcodeu003Eu003Ccodeu003E long duration = System.currentTimeMillis – event.timestamp;u003Cu002Fcodeu003Eu003Ccodeu003E System.out.println(“耗时:” + duration);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cu002Fpreu003Eu003Cpu003E事件监听器接口针对不同的事件发布实际提供相应的处理方法定义,最重要的是,其方法只接收MethodMonitorEvent参数,说明这个监听器类只负责监听器对应的事件并进行处理。有了事件和监听器,剩下的就是发布事件,然后让相应的监听器监听并处理。通常情况,我们会有一个事件发布者,它本身作为事件源,在合适的时机,将相应的事件发布给对应的事件监听器:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003Epublic class MethodMonitorEventPublisher {u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E private List<MethodMonitorEventListener> listeners = new ArrayList<MethodMonitorEventListener>;u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E public void methodMonitor {u003Cu002Fcodeu003Eu003Ccodeu003E MethodMonitorEvent eventObject = new MethodMonitorEvent(this);u003Cu002Fcodeu003Eu003Ccodeu003E publishEvent(“begin”,eventObject);u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 模拟方法执行:休眠5秒钟u003Cu002Fcodeu003Eu003Ccodeu003E TimeUnit.SECONDS.sleep(5);u003Cu002Fcodeu003Eu003Ccodeu003E publishEvent(“end”,eventObject);u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E private void publishEvent(String status,MethodMonitorEvent event) {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 避免在事件处理期间,监听器被移除,这里为了安全做一个复制操作u003Cu002Fcodeu003Eu003Ccodeu003E List<MethodMonitorEventListener> copyListeners = new ArrayList<MethodMonitorEventListener>(listeners);u003Cu002Fcodeu003Eu003Ccodeu003E for (MethodMonitorEventListener listener : copyListeners) {u003Cu002Fcodeu003Eu003Ccodeu003E if (“begin”.equals(status)) {u003Cu002Fcodeu003Eu003Ccodeu003E listener.onMethodBegin(event);u003Cu002Fcodeu003Eu003Ccodeu003E } else {u003Cu002Fcodeu003Eu003Ccodeu003E listener.onMethodEnd(event);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E public static void main(String[] args) {u003Cu002Fcodeu003Eu003Ccodeu003E MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher;u003Cu002Fcodeu003Eu003Ccodeu003E publisher.addEventListener(new AbstractMethodMonitorEventListener);u003Cu002Fcodeu003Eu003Ccodeu003E publisher.methodMonitor;u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 省略实现u003Cu002Fcodeu003Eu003Ccodeu003E public void addEventListener(MethodMonitorEventListener listener) {}u003Cu002Fcodeu003Eu003Ccodeu003E public void removeEventListener(MethodMonitorEventListener listener) {}u003Cu002Fcodeu003Eu003Ccodeu003E public void removeAllListeners {}u003Cu002Fcodeu003Eu003Cu002Fpreu003E对于事件发布者(事件源)通常需要u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E两点:u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E1. 在合适的时机发布事件。此例中的methodMonitor方法是事件发布的源头,其在方法执行之前和结束之后两个时间点发布MethodMonitorEvent事件,每个时间点发布的事件都会传给相应的监听器进行处理。在具体实现时需要注意的是,事件发布是顺序执行,为了不影响处理性能,事件监听器的处理逻辑应尽量简单。u003Cu002Fpu003Eu003Cpu003E2. 事件监听器的管理。publisher类中提供了事件监听器的注册与移除方法,这样u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E客户端u003Cu002Fiu003E可以根据实际情况决定是否需要注册新的监听器或者移除某个监听器。如果这里没有提供remove方法,那么注册的监听器示例将一直被MethodMonitorEventPublisher引用,即使已经废弃不用了,也依然在发布者的监听器列表中,这会导致隐性的内存泄漏。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch2 toutiao-origin=”h3″u003Eu003Cstrongu003ESpring容器内的事件监听机制u003Cu002Fstrongu003Eu003Cu002Fh2u003Eu003Cpu003ESpring的ApplicationContext容器内部中的所有事件类型均继承自u003Ccodeu003Eorg.springframework.context.AppliationEventu003Cu002Fcodeu003E,容器中的所有监听器都实现u003Ccodeu003Eorg.springframework.context.ApplicationListeneru003Cu002Fcodeu003E接口,并且以bean的形式注册在容器中。一旦在容器内发布ApplicationEvent及其子类型的事件,注册到容器的ApplicationListener就会对这些事件进行处理。u003Cu002Fpu003Eu003Cpu003E你应该已经猜到是怎么回事了。u003Cu002Fpu003Eu003Cpu003EApplicationEvent继承自EventObject,Spring提供了一些默认的实现,比如:u003Ccodeu003EContextClosedEventu003Cu002Fcodeu003E表示容器在即将关闭时发布的事件类型,u003Ccodeu003EContextRefreshedEventu003Cu002Fcodeu003E表示容器在初始化或者刷新的时候发布的事件类型……u003Cu002Fpu003Eu003Cpu003E容器内部使用ApplicationListener作为事件监听器接口定义,它继承自EventListener。ApplicationContext容器在启动时,会自动识别并加载EventListener类型的bean,一旦容器内有事件发布,将通知这些注册到容器的EventListener。u003Cu002Fpu003Eu003Cpu003EApplicationContext接口继承了ApplicationEventPublisher接口,该接口提供了u003Ccodeu003EvoidpublishEvent(ApplicationEventevent)u003Cu002Fcodeu003E方法定义,不难看出,ApplicationContext容器担当的就是事件发布者的角色。如果有兴趣可以查看u003Ccodeu003EAbstractApplicationContext.publishEvent(ApplicationEventevent)u003Cu002Fcodeu003E方法的源码:ApplicationContext将事件的发布以及监听器的管理工作委托给u003Ccodeu003EApplicationEventMulticasteru003Cu002Fcodeu003E接口的实现类。在容器启动时,会检查容器内是否存在名为u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003ElicationEventMulticaster的ApplicationEventMulticaster对象实例。如果有就使用其提供的实现,没有就默认初始化一个SimpleApplicationEventMulticaster作为实现。u003Cu002Fpu003Eu003Cpu003E最后,如果我们业务需要在容器内部发布事件,只需要为其注入ApplicationEventPublisher依赖即可:实现ApplicationEventPublisherAware接口或者ApplicationContextAware接口(Aware接口相关内容请回顾上文)。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003Eu003Cstrongu003E五、出神入化:揭秘自动配置原理u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003E典型的Spring Boot应用的启动类一般均位于u003Ccodeu003Esrcu002Fmainu002Fjavau003Cu002Fcodeu003E根路径下,比如u003Ccodeu003EMoonApplicationu003Cu002Fcodeu003E类:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003E@SpringBootApplicationu003Cu002Fcodeu003Eu003Ccodeu003Epublic class MoonApplication {u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E public static void main(String[] args) {u003Cu002Fcodeu003Eu003Ccodeu003E SpringApplication.run(MoonApplication.class, args);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cu002Fpreu003Eu003Cpu003E其中u003Ccodeu003E@SpringBootApplicationu003Cu002Fcodeu003E开启组件扫描和自动配置,而u003Ccodeu003ESpringApplication.runu003Cu002Fcodeu003E则负责启动引导应用程序。u003Ccodeu003E@SpringBootApplicationu003Cu002Fcodeu003E是一个复合u003Ccodeu003EAnnotationu003Cu002Fcodeu003E,它将三个有用的注解组合在一起:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003E@Target(ElementType.TYPE)u003Cu002Fcodeu003Eu003Ccodeu003E@Retention(RetentionPolicy.RUNTIME)u003Cu002Fcodeu003Eu003Ccodeu003E@Documentedu003Cu002Fcodeu003Eu003Ccodeu003E@Inheritedu003Cu002Fcodeu003Eu003Ccodeu003E@SpringBootConfigurationu003Cu002Fcodeu003Eu003Ccodeu003E@EnableAutoConfigurationu003Cu002Fcodeu003Eu003Ccodeu003E@ComponentScan(excludeFilters = {u003Cu002Fcodeu003Eu003Ccodeu003E @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),u003Cu002Fcodeu003Eu003Ccodeu003E @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })u003Cu002Fcodeu003Eu003Ccodeu003Epublic @interface SpringBootApplication {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ……u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Ccodeu003E@SpringBootConfigurationu003Cu002Fcodeu003E就是u003Ccodeu003E@Configurationu003Cu002Fcodeu003E,它是Spring框架的注解,标明该类是一个u003Ccodeu003EJavaConfigu003Cu002Fcodeu003E配置类。而u003Ccodeu003E@ComponentScanu003Cu002Fcodeu003E启用组件扫描,前文已经详细讲解过,这里着重u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003Eu003Ccodeu003E@EnableAutoConfigurationu003Cu002Fcodeu003E。u003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003E@EnableAutoConfigurationu003Cu002Fcodeu003E注解表示开启Spring Boot自动配置功能,Spring Boot会根据应用的依赖、自定义的bean、classpath下有没有某个类 等等因素来猜测你需要的bean,然后注册到IOC容器中。那u003Ccodeu003E@EnableAutoConfigurationu003Cu002Fcodeu003E是如何推算出你的需求?首先看下它的定义:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003E@Target(ElementType.TYPE)u003Cu002Fcodeu003Eu003Ccodeu003E@Retention(RetentionPolicy.RUNTIME)u003Cu002Fcodeu003Eu003Ccodeu003E@Documentedu003Cu002Fcodeu003Eu003Ccodeu003E@Inheritedu003Cu002Fcodeu003Eu003Ccodeu003E@AutoConfigurationPackageu003Cu002Fcodeu003Eu003Ccodeu003E@Import(EnableAutoConfigurationImportSelector.class)u003Cu002Fcodeu003Eu003Ccodeu003Epublic @interface EnableAutoConfiguration {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ……u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cu002Fpreu003Eu003Cpu003E你的u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E点应该在u003Ccodeu003E@Import(EnableAutoConfigurationImportSelector.class)u003Cu002Fcodeu003E上了,前文说过,u003Ccodeu003E@Importu003Cu002Fcodeu003E注解用于导入类,并将这个类作为一个bean的定义注册到容器中,这里它将把u003Ccodeu003EEnableAutoConfigurationImportSelectoru003Cu002Fcodeu003E作为bean注入到容器中,而这个类会将所有符合条件的@Configuration配置都加载到容器中,看看它的代码:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003Epublic String selectImports(AnnotationMetadata annotationMetadata) {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 省略了大部分代码,保留一句核心代码u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 注意:SpringBoot最近版本中,这句代码被封装在一个单独的方法中u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F SpringFactoriesLoader相关知识请参考前文u003Cu002Fcodeu003Eu003Ccodeu003E List<String> factories = new ArrayList<String>(new LinkedHashSet<String>(u003Cu002Fcodeu003Eu003Ccodeu003E SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)));u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cu002Fpreu003Eu003Cpu003E这个类会扫描所有的jar包,将所有符合条件的@Configuration配置类注入的容器中,何为符合条件,看看u003Ccodeu003EMETA-INFu002Fspring.factoriesu003Cu002Fcodeu003E的文件内容:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003Eu002Fu002F 来自 org.springframework.boot.autoconfigure下的META-INFu002Fspring.factoriesu003Cu002Fcodeu003Eu003Ccodeu003Eu002Fu002F 配置的key = EnableAutoConfiguration,与代码中一致u003Cu002Fcodeu003Eu003Ccodeu003Eorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\u003Cu002Fcodeu003Eu003Ccodeu003Eorg.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\u003Cu002Fcodeu003Eu003Ccodeu003Eorg.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\u003Cu002Fcodeu003Eu003Ccodeu003Eorg.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\u003Cu002Fcodeu003Eu003Ccodeu003E…..u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cu002Fpreu003Eu003Cpu003E以u003Ccodeu003EDataSourceAutoConfigurationu003Cu002Fcodeu003E为例,看看Spring Boot是如何自动配置的:u003Cu002Fpu003Eu003Cpreu003Eu003Cpreu003Eu003Ccodeu003E@Configurationu003Cu002Fcodeu003Eu003Ccodeu003E@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })u003Cu002Fcodeu003Eu003Ccodeu003E@EnableConfigurationProperties(DataSourceProperties.class)u003Cu002Fcodeu003Eu003Ccodeu003E@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })u003Cu002Fcodeu003Eu003Ccodeu003Epublic class DataSourceAutoConfiguration {u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cu002Fpreu003Eu003Cpu003E分别说一说:u003Cu002Fpu003Eu003Cpu003Eu003Ccodeu003E@ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class})u003Cu002Fcodeu003E:当Classpath中存在DataSource或者EmbeddedDatabaseType类时才启用这个配置,否则这个配置将被忽略。u003Ccodeu003E@EnableConfigurationProperties(DataSourceProperties.class)u003Cu002Fcodeu003E:将DataSource的默认配置类注入到IOC容器中,DataSourceproperties定义为:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eu002Fu002F 提供对datasource配置信息的支持,所有的配置前缀为:spring.datasourceu003Cu002Fcodeu003Eu003Ccodeu003E@ConfigurationProperties(prefix = “spring.datasource”)u003Cu002Fcodeu003Eu003Ccodeu003Epublic class DataSourceProperties {u003Cu002Fcodeu003Eu003Ccodeu003E private ClassLoader classLoader;u003Cu002Fcodeu003Eu003Ccodeu003E private Environment environment;u003Cu002Fcodeu003Eu003Ccodeu003E private String name = “testdb”;u003Cu002Fcodeu003Eu003Ccodeu003E ……u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E@Import({Registrar.class,DataSourcePoolMetadataProvidersConfiguration.class}):导入其他额外的配置,就以 DataSourcePoolMetadataProvidersConfiguration为例吧。u003Cemu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E web_resource u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后获取更多干货u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003E@Configurationu003Cu002Fcodeu003Eu003Ccodeu003Epublic class DataSourcePoolMetadataProvidersConfiguration {u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E @Configurationu003Cu002Fcodeu003Eu003Ccodeu003E @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)u003Cu002Fcodeu003Eu003Ccodeu003E static class TomcatDataSourcePoolMetadataProviderConfiguration {u003Cu002Fcodeu003Eu003Ccodeu003E @Beanu003Cu002Fcodeu003Eu003Ccodeu003E public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider {u003Cu002Fcodeu003Eu003Ccodeu003E …..u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E ……u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003EDataSourcePoolMetadataProvidersConfiguration是数据库连接池提供者的一个配置类,即Classpath中存在u003Ccodeu003Eorg.apache.tomcat.jdbc.pool.DataSource.classu003Cu002Fcodeu003E,则使用tomcat-jdbc连接池,如果Classpath中存在u003Ccodeu003EHikariDataSource.classu003Cu002Fcodeu003E则使用Hikari连接池。u003Cu002Fpu003Eu003Cpu003E这里仅描述了DataSourceAutoConfiguration的冰山一角,但足以说明Spring Boot如何利用条件话配置来实现自动配置的。回顾一下,u003Ccodeu003E@EnableAutoConfigurationu003Cu002Fcodeu003E中导入了EnableAutoConfigurationImportSelector类,而这个类的u003Ccodeu003EselectImportsu003Cu002Fcodeu003E通过SpringFactoriesLoader得到了大量的配置类,而每一个配置类则根据条件化配置来做出决策,以实现自动配置。u003Cu002Fpu003Eu003Cpu003E整个流程很清晰,但漏了一个大问题:u003Ccodeu003EEnableAutoConfigurationImportSelector.selectImportsu003Cu002Fcodeu003E是何时执行的?其实这个方法会在容器启动过程中执行:u003Ccodeu003EAbstractApplicationContext.refreshu003Cu002Fcodeu003E,更多的细节在下一小节中说明。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003Eu003Cstrongu003E六、启动引导:Spring Boot应用启动的秘密u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Ch2 toutiao-origin=”h3″u003Eu003Cstrongu003E6.1 SpringApplication初始化u003Cu002Fstrongu003Eu003Cu002Fh2u003Eu003Cpu003ESpringBoot整个启动流程分为两个步骤:初始化一个SpringApplication对象、执行该对象的run方法。看下SpringApplication的初始化流程,SpringApplication的构造方法中调用initialize(Object[] sources)方法,其代码如下:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eprivate void initialize(Object[] sources) {u003Cu002Fcodeu003Eu003Ccodeu003E if (sources != && sources.length > 0) {u003Cu002Fcodeu003Eu003Ccodeu003E this.sources.addAll(Arrays.asList(sources));u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 判断是否是Web项目u003Cu002Fcodeu003Eu003Ccodeu003E this.webEnvironment = deduceWebEnvironment;u003Cu002Fcodeu003Eu003Ccodeu003E setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));u003Cu002Fcodeu003Eu003Ccodeu003E setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 找到入口类u003Cu002Fcodeu003Eu003Ccodeu003E this.mainApplicationClass = deduceMainApplicationClass;u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E初始化流程中最重要的就是通过SpringFactoriesLoader找到u003Ccodeu003Espring.factoriesu003Cu002Fcodeu003E文件中配置的u003Ccodeu003EApplicationContextInitializeru003Cu002Fcodeu003E和u003Ccodeu003EApplicationListeneru003Cu002Fcodeu003E两个接口的实现类名称,以便后期构造相应的实例。u003Ccodeu003EApplicationContextInitializeru003Cu002Fcodeu003E的主要目的是在u003Ccodeu003EConfigurableApplicationContextu003Cu002Fcodeu003E做refresh之前,对ConfigurableApplicationContext实例做进一步的设置或处理。ConfigurableApplicationContext继承自ApplicationContext,其主要提供了对ApplicationContext进行设置的能力。u003Cu002Fpu003Eu003Cpu003E实现一个ApplicationContextInitializer非常简单,因为它只有一个方法,但大多数情况下我们没有必要自定义一个ApplicationContextInitializer,即便是Spring Boot框架,它默认也只是注册了两个实现,毕竟Spring的容器已经非常成熟和稳定,你没有必要来改变它。u003Cu002Fpu003Eu003Cpu003E而u003Ccodeu003EApplicationListeneru003Cu002Fcodeu003E的目的就没什么好说的了,它是Spring框架对Java事件监听机制的一种框架实现,具体内容在前文Spring事件监听机制这个小节有详细讲解。这里主要说说,如果你想为Spring Boot应用添加监听器,该如何实现?u003Cu002Fpu003Eu003Cpu003ESpring Boot提供两种方式来添加自定义监听器:u003Cu002Fpu003Eu003Cpu003E1. 通过u003Ccodeu003ESpringApplication.addListeners(ApplicationListener<?>…listeners)u003Cu002Fcodeu003E或者u003Ccodeu003ESpringApplication.setListeners(Collection<?extendsApplicationListener<?>>listeners)u003Cu002Fcodeu003E两个方法来添加一个或者多个自定义监听器u003Cu002Fpu003Eu003Cpu003E2. 既然SpringApplication的初始化流程中已经从u003Ccodeu003Espring.factoriesu003Cu002Fcodeu003E中获取到u003Ccodeu003EApplicationListeneru003Cu002Fcodeu003E的实现类,那么我们直接在自己的jar包的u003Ccodeu003EMETA-INFu002Fspring.factoriesu003Cu002Fcodeu003E文件中新增配置即可:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eu003Ccodeu003Eorg.springframework.context.ApplicationListener=\u003Cbru003Ecn.moondev.listeners.xxxxListener\u003Cu002Fcodeu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E关于SpringApplication的初始化,我们就说这么多。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch2 toutiao-origin=”h3″u003Eu003Cstrongu003E6.2 Spring Boot启动流程u003Cu002Fstrongu003Eu003Cu002Fh2u003Eu003Cpu003ESpring Boot应用的整个启动流程都封装在SpringApplication.run方法中,其整个流程真的是太长太长了,但本质上就是在Spring容器启动的基础上做了大量的扩展,按照这个思路来看看源码:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Epublic ConfigurableApplicationContext run(String… args) {u003Cu002Fcodeu003Eu003Ccodeu003E StopWatch stopWatch = new StopWatch;u003Cu002Fcodeu003Eu003Ccodeu003E stopWatch.start;u003Cu002Fcodeu003Eu003Ccodeu003E ConfigurableApplicationContext context = ;u003Cu002Fcodeu003Eu003Ccodeu003E FailureAnalyzers analyzers = ;u003Cu002Fcodeu003Eu003Ccodeu003E configureHeadlessProperty;u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ①u003Cu002Fcodeu003Eu003Ccodeu003E SpringApplicationRunListeners listeners = getRunListeners(args);u003Cu002Fcodeu003Eu003Ccodeu003E listeners.starting;u003Cu002Fcodeu003Eu003Ccodeu003E try {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ②u003Cu002Fcodeu003Eu003Ccodeu003E ApplicationArguments u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003ElicationArguments = new DefaultApplicationArguments(args);u003Cu002Fcodeu003Eu003Ccodeu003E ConfigurableEnvironment environment = prepareEnvironment(listeners,u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003ElicationArguments);u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ③u003Cu002Fcodeu003Eu003Ccodeu003E Banner printedBanner = printBanner(environment);u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ④u003Cu002Fcodeu003Eu003Ccodeu003E context = createApplicationContext;u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ⑤u003Cu002Fcodeu003Eu003Ccodeu003E analyzers = new FailureAnalyzers(context);u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ⑥u003Cu002Fcodeu003Eu003Ccodeu003E prepareContext(context, environment, listeners, u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003ElicationArguments,printedBanner);u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ⑦u003Cu002Fcodeu003Eu003Ccodeu003E refreshContext(context);u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ⑧u003Cu002Fcodeu003Eu003Ccodeu003E afterRefresh(context, u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003ElicationArguments);u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F ⑨u003Cu002Fcodeu003Eu003Ccodeu003E listeners.finished(context, );u003Cu002Fcodeu003Eu003Ccodeu003E stopWatch.stop;u003Cu002Fcodeu003Eu003Ccodeu003E return context;u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E catch (Throwable ex) {u003Cu002Fcodeu003Eu003Ccodeu003E handleRunFailure(context, listeners, analyzers, ex);u003Cu002Fcodeu003Eu003Ccodeu003E throw new IllegalStateException(ex);u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Ccodeu003E }u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E1. 通过SpringFactoriesLoader查找并加载所有的u003Ccodeu003ESpringApplicationRunListenersu003Cu002Fcodeu003E,通过调用starting方法通知所有的SpringApplicationRunListeners:应用开始启动了。SpringApplicationRunListeners其本质上就是一个事件发布者,它在SpringBoot应用启动的不同时间点发布不同应用事件类型(ApplicationEvent),如果有哪些事件监听者(ApplicationListener)对这些事件感兴趣,则可以接收并且处理。还记得初始化流程中,SpringApplication加载了一系列ApplicationListener吗?这个启动流程中没有发现有发布事件的代码,其实都已经在SpringApplicationRunListeners这儿实现了。u003Cu002Fpu003Eu003Cpu003E简单的分析一下其实现流程,首先看下SpringApplicationRunListener的源码:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Epublic interface SpringApplicationRunListener {u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E u002Fu002F 运行run方法时立即调用此方法,可以用户非常早期的初始化工作u003Cu002Fcodeu003Eu003Ccodeu003E void starting;u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F Environment准备好后,并且ApplicationContext创建之前调用u003Cu002Fcodeu003Eu003Ccodeu003E void environmentPrepared(ConfigurableEnvironment environment);u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E u002Fu002F ApplicationContext创建好后立即调用u003Cu002Fcodeu003Eu003Ccodeu003E void contextPrepared(ConfigurableApplicationContext context);u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E u002Fu002F ApplicationContext加载完成,在refresh之前调用u003Cu002Fcodeu003Eu003Ccodeu003E void contextLoaded(ConfigurableApplicationContext context);u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E u002Fu002F 当run方法结束之前调用u003Cu002Fcodeu003Eu003Ccodeu003E void finished(ConfigurableApplicationContext context, Throwable exception);u003Cu002Fcodeu003Eu003Cbru003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003ESpringApplicationRunListener只有一个实现类:u003Ccodeu003EEventPublishingRunListeneru003Cu002Fcodeu003E。①处的代码只会获取到一个EventPublishingRunListener的实例,我们来看看starting方法的内容:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Epublic void starting {u003Cu002Fcodeu003Eu003Ccodeu003E u002Fu002F 发布一个ApplicationStartedEventu003Cu002Fcodeu003Eu003Ccodeu003E this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003Eappu003Cu002Fiu003Elication, this.args));u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E顺着这个逻辑,你可以在②处的u003Ccodeu003EprepareEnvironmentu003Cu002Fcodeu003E方法的源码中找到u003Ccodeu003Elisteners.environmentPrepared(environment);u003Cu002Fcodeu003E即SpringApplicationRunListener接口的第二个方法,那不出你所料,u003Ccodeu003EenvironmentPreparedu003Cu002Fcodeu003E又发布了另外一个事件u003Ccodeu003EApplicationEnvironmentPreparedEventu003Cu002Fcodeu003E。接下来会发生什么,就不用我多说了吧。u003Cu002Fpu003Eu003Cpu003E2. 创建并配置当前应用将要使用的u003Ccodeu003EEnvironmentu003Cu002Fcodeu003E,Environment用于描述应用程序当前的运行环境,其抽象了两个方面的内容:配置文件(profile)和属性(properties),开发经验丰富的同学对这两个东西一定不会陌生:不同的环境(eg:生产环境、预发布环境)可以使用不同的配置文件,而属性则可以从配置文件、环境变量、命令行参数等来源获取。因此,当Environment准备好后,在整个应用的任何时候,都可以从Environment中获取资源。u003Cemu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E web_resource u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后获取更多干货u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003E总结起来,2 处的两句代码,主u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003E要完u003Cu002Fiu003E成以下几件事:u003Cu002Fpu003Eu003Culu003Eu003Cliu003E判断Environment是否存在,不存在就创建(如果是web项目就创建u003Ccodeu003EStandardServletEnvironmentu003Cu002Fcodeu003E,否则创建u003Ccodeu003EStandardEnvironmentu003Cu002Fcodeu003E)u003Cu002Fliu003Eu003Cliu003Eu003Cpu003E配置Environment:配置profile以及propertiesu003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003E调用SpringApplicationRunListener的u003Ccodeu003EenvironmentPreparedu003Cu002Fcodeu003E方法,通知事件监听者:应用的Environment已经准备好u003Cu002Fliu003Eu003Cu002Fulu003Eu003Cpu003E3. SpringBoot应用在启动时会输出这样的东西:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003E . ____ _ __ _ _u003Cu002Fcodeu003Eu003Ccodeu003E u002F\\ u002F ___’_ __ _ _(_)_ __ __ _ \ \ \ \u003Cu002Fcodeu003Eu003Ccodeu003E( \___ | ‘_ | ‘_| | ‘_ \u002F _` | \ \ \ \u003Cu002Fcodeu003Eu003Ccodeu003E \\u002F ___)| |_)| | | | | || (_| | ) ) ) )u003Cu002Fcodeu003Eu003Ccodeu003E ‘ |____| .__|_| |_|_| |_\__, | u002F u002F u002F u002Fu003Cu002Fcodeu003Eu003Ccodeu003E =========|_|==============|___u002F=u002F_u002F_u002F_u002Fu003Cu002Fcodeu003Eu003Ccodeu003E :: Spring Boot :: (v1.5.6.RELEASE)u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E如果想把这个东西改成自己的涂鸦,你可以研究以下Banner的实现,这个任务就留给你们吧。u003Cu002Fpu003Eu003Cpu003E4. 根据是否是web项目,来创建不同的ApplicationContext容器。u003Cu002Fpu003Eu003Cpu003E5. 创建一系列u003Ccodeu003EFailureAnalyzeru003Cu002Fcodeu003E,创建流程依然是通过SpringFactoriesLoader获取到所有实现FailureAnalyzer接口的class,然后在创建对应的实例。FailureAnalyzer用于分析故障并提供相关诊断信息。u003Cu002Fpu003Eu003Cpu003E6. 初始化ApplicationContext,主u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003E要完u003Cu002Fiu003E成以下工作:u003Cu002Fpu003Eu003Culu003Eu003Cliu003Eu003Cpu003E将准备好的Environment设置给ApplicationContextu003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003E遍历调用所有的ApplicationContextInitializer的u003Ccodeu003Einitializeu003Cu002Fcodeu003E方法来对已经创建好的ApplicationContext进行进一步的处理u003Cu002Fliu003Eu003Cliu003E调用SpringApplicationRunListener的u003Ccodeu003EcontextPreparedu003Cu002Fcodeu003E方法,通知所有的监听者:ApplicationContext已经准备完毕u003Cu002Fliu003Eu003Cliu003Eu003Cpu003E将所有的bean加载到容器中u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003E调用SpringApplicationRunListener的u003Ccodeu003EcontextLoadedu003Cu002Fcodeu003E方法,通知所有的监听者:ApplicationContext已经装载完毕u003Cu002Fliu003Eu003Cu002Fulu003Eu003Cpu003E7. 调用ApplicationContext的u003Ccodeu003Erefreshu003Cu002Fcodeu003E方法,完成IoC容器可用的最后一道工序。从名字上理解为刷新容器,那何为刷新?就是插手容器的启动,联系一下第一小节的内容。那如何刷新呢?且看下面代码:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eu002Fu002F 摘自refresh方法中一句代码u003Cu002Fcodeu003Eu003Ccodeu003EinvokeBeanFactoryPostProcessors(beanFactory);u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E看看这个方法的实现:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eprotected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {u003Cu002Fcodeu003Eu003Ccodeu003E PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors);u003Cu002Fcodeu003Eu003Ccodeu003E ……u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E获取到所有的 BeanFactoryPostProcessor来对容器做一些额外的操作。BeanFactoryPostProcessor允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做一些额外的操作。这里的getBeanFactoryPostProcessors方法可以获取到3个Processor:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003EConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessoru003Cu002Fcodeu003Eu003Ccodeu003ESharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessoru003Cu002Fcodeu003Eu003Ccodeu003EConfigFileApplicationListener$PropertySourceOrderingPostProcessoru003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E不是有那么多BeanFactoryPostProcessor的实现类,为什么这儿只有这3个?因为在初始化流程获取到的各种ApplicationContextInitializer和ApplicationListener中,只有上文3个做了类似于如下操作:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Epublic void initialize(ConfigurableApplicationContext context) {u003Cu002Fcodeu003Eu003Ccodeu003E context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks));u003Cu002Fcodeu003Eu003Ccodeu003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E然后你就可以进入到u003Ccodeu003EPostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessorsu003Cu002Fcodeu003E方法了,这个方法除了会遍历上面的3个BeanFactoryPostProcessor处理外,还会获取类型为u003Ccodeu003EBeanDefinitionRegistryPostProcessoru003Cu002Fcodeu003E的bean:u003Ccodeu003Eorg.springframework.context.annotation.internalConfigurationAnnotationProcessoru003Cu002Fcodeu003E,对应的Class为u003Ccodeu003EConfigurationClassPostProcessoru003Cu002Fcodeu003E。u003Ccodeu003EConfigurationClassPostProcessoru003Cu002Fcodeu003E用于解析处理各种注解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。当处理u003Ccodeu003E@importu003Cu002Fcodeu003E注解的时候,就会调用<自动配置>这一小节中的u003Ccodeu003EEnableAutoConfigurationImportSelector.selectImportsu003Cu002Fcodeu003E来完成自动配置功能。其他的这里不再多讲,如果你有兴趣,可以查阅参考资料6。u003Cu002Fpu003Eu003Cpu003E8. 查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们。u003Cu002Fpu003Eu003Cpu003E9. 执行所有SpringApplicationRunListener的finished方法。u003Cu002Fpu003Eu003Cpu003E这就是Spring Boot的整个启动流程,其核心就是在Spring容器初始化并启动的基础上加入各种扩展点,这些扩展点包括:ApplicationContextInitializer、ApplicationListener以及各种BeanFactoryPostProcessor等等。你对整个流程的细节不必太过u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E,甚至没弄明白也没有关系,你只要理解这些扩展点是在何时如何工作的,能让它们为你所用即可。u003Cu002Fpu003Eu003Cpu003E整个启动流程确实非常复杂,可以查询参考资料中的部分章节和内容,对照着源码,多看看,我想最终你都能弄清楚的。言而总之,Spring才是核心,理解清楚Spring容器的启动流程,那Spring Boot启动流程就不在话下了。u003Cu002Fpu003Eu003Cpu003E来源:51ctou003Cu002Fpu003Eu003Cpu003E作者:CHEN川u003Cu002Fpu003Eu003Cpu003E原文:https:u002Fu002Fblog.51ctou003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E.comu003Cu002Fiu003Eu002Fluecscu002F1964056u003Cu002Fpu003Eu003Cpu003E-END-u003Cu002Fpu003Eu003Cpu003Eu003Cemu003E[1] 王福强 著;u003Cu002Femu003Eu003Cemu003ESpringBoot揭秘:u003Cu002Femu003Eu003Cemu003E快速构建微服务体系; 机械工业出版社, 2016u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cemu003E[2] 王福强 著;u003Cu002Femu003Eu003Cemu003ESpring揭秘; 人民邮件出版社, 2009u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cemu003E[3] Craig Walls 著;u003Cu002Femu003Eu003Cemu003E丁雪丰 译;u003Cu002Femu003Eu003Cemu003ESpring Boot实战;u003Cu002Femu003Eu003Cemu003E中国工信出版集团 人民邮电出版社,2016u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cemu003E[4] 深入探讨 Java 类加载器u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cemu003E[5] spring boot实战:u003Cu002Femu003Eu003Cemu003E自动配置原理分析u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cemu003E[6] spring boot实战:u003Cu002Femu003Eu003Cemu003ESpring boot Bean加载源码分析u003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003E如果看到这里,说明你喜欢这篇文章,帮忙u003Cstrongu003E转发u003Cu002Fstrongu003E一下吧,感谢。u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E搜索u003Cu002Fiu003E「web_resource」,u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E关注u003Cu002Fiu003E后即可获取每日一题的推送。u003Cstrongu003E推u003Cu002Fstrongu003Eu003Cstrongu003E荐u003Cu002Fstrongu003Eu003Cstrongu003E阅u003Cu002Fstrongu003Eu003Cstrongu003E读u003Cu002Fstrongu003Eu003Cstrongu003E1.u003Cu002Fstrongu003E把 14 亿中国人拉到一个u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E微信u003Cu002Fiu003E群 ?u003Cemu003E2. u003Cu002Femu003ESpring 中的 18 个注解,你会几个?u003Cstrongu003E3. u003Cu002Fstrongu003E寓教于乐,用玩游戏的方式学习 Gitu003Cstrongu003E4. u003Cu002Fstrongu003E在浏览器输入 URL 回车之后发生了什么?u003Cstrongu003E5.u003Cu002Fstrongu003EJava 最常见的 208 道面试题u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003EJava后端:u003Cu002Fstrongu003Eu003Cstrongu003E专注于Java技术u003Cu002Fstrongu003Eu003Cu002Fpu003E”

原文始发于:干货!没学过 Spring Boot?送你一份超详细的知识清单

主题测试文章,只做测试使用。发布者:℅傍ㄖ免沦陷dε鬼,转转请注明出处:http://www.cxybcw.com/18062.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code