springboot抽象类可以依赖注⼊吗_全⽹最全SpringBoot⼲货知
识总结(超详细。。。
前⾔:本⽂⾮常长,建议先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 d
这段代码仅为了说明BeanFactory底层的⼤致⼯作流程.实际情况会更加复杂,⽐如bean之间的依赖关系可能定义在外部配置⽂件
(XML/Properties)中、也可能是注解⽅式。
Spring IoC容器的整个⼯作流程⼤致可以分为两个阶段:
①、容器启动阶段
容器启动时,会通过某种途径加载Configuration MetaData。除了代码⽅式⽐较直接外,在⼤部分情况下,容器需要依赖某些⼯具类,⽐
如:BeanDefinitionReader,BeanDefinitionReader会对加载的Configuration MetaData进⾏解析和分析,并将分析后的信息组装为相
应的BeanDefinition,最后把这些保存了bean定义的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器的启动⼯作就完
成了。
这个阶段主要完成⼀些准备性⼯作,更侧重于bean对象管理信息的收集,当然⼀些验证性或者辅助性的⼯作也在这⼀阶段完成。
来看⼀个简单的例⼦吧,过往,所有的bean都定义在XML配置⽂件中,下⾯的代码将模拟
BeanFactory如何从配置⽂件中加载bean的定义以及依赖关系:
scala怎么用// 通常为BeanDefinitionRegistry的实现类,这⾥以DeFaultListabeBeanFactory为例 BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory(); // Xm ②、Bean的实例化阶段
经过第⼀阶段,所有bean定义都通过BeanDefinition的⽅式注册到BeanDefinitionRegistry中当某个请求通过容器的getBean⽅法请求某ffoutput是什么文件
个对象,或者因为依赖关系容器需要隐式的调⽤getBean时,就会触发第⼆阶段的活动:容器会⾸先检查所请求的对象之前是否已经实例化
完成。
如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注⼊依赖。
当该对象装配完毕后,容器会⽴即将其返回给请求⽅法使⽤。BeanFactory只是Spring IoC容器的⼀种实现,如果没有特殊指定,它采⽤
采⽤延迟初始化策略:只有当访问容器中的某个对象时,才对该对象进⾏初始化和依赖注⼊操作。
⽽在实际场景下,我们更多的使⽤另外⼀种类型的容器:ApplicationContext,它构建在BeanFactory之上,属于更⾼级的容器,除了具
有BeanFactory的所有能⼒之外,还提供对事件监听机制以及国际化的⽀持等。它管理的bean,在容器启动时全部完成初始化和依赖注⼊
操作。
1.2、Spring容器扩展机制
IoC容器负责管理容器中所有bean的⽣命周期,⽽在bean⽣命周期的不同阶段,Spring提供了不同的扩展点来改变bean的命运。在容器的
启动阶段,BeanFactoryPostProcessor允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做⼀些额外
的操作,⽐如修改bean定义的某些属性或者增加其他信息等。
如果要⾃定义扩展类,通常需要实现.
编程猫教程大全org.springframework.fig.BeanFactoryPostProcessor接⼝,与此同时,因为容器中可能有多个BeanFactoryPostProcessor,可能还需要实现Ordered接⼝,以保证BeanFactoryPostProcessor按照顺
序执⾏。
Spring提供了为数不多的BeanFactoryPostProcessor实现.我们以PropertyPlaceholderConfigurer来说明其⼤致的⼯作流程。
在Spring项⽬的XML配置⽂件中,经常可以看到许多配置项的值使⽤占位符,⽽将占位符所代表的值单独配置到独⽴的properties⽂件,
这样可以将散落在不同XML⽂件中的配置集中管理,⽽且也⽅便运维根据不同的环境进⾏配置不同的值。
这个⾮常实⽤的功能就是由PropertyPlaceholderConfigurer负责实现的。
根据前⽂,当BeanFactory在第⼀阶段加载完所有配置信息时,BeanFactory中保存的对象的属性还是以占位符⽅式存在的,⽐如
${sql.url}。properties文件用什么打开
当PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应⽤时,它会使⽤properties配置⽂件中的值来替换相应的BeanDefinition中占位符所表⽰的属性值。当需要实例化bean时,bean定义中的属性值就已经被替换成我们配置的值。当然其实现⽐上⾯
描述的要复杂⼀些,这⾥仅说明其⼤致⼯作原理,更详细的实现可以参考其源码。
与之相似的,还有BeanPostProcessor,其存在于对象实例化阶段。跟BeanFactoryPostProcessor类似,它会处理容器内所有符合条件
并且已经实例化后的对象。
简单的对⽐,BeanFactoryPostProcessor处理bean的定义,⽽BeanPostProcessor则处理bean完成实例化后的对象。
BeanPostProcessor定义了两个接⼝:
// 通常为BeanDefinitionRegistry的实现类,这⾥以DeFaultListabeBeanFactory为例 BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory(); // Xm 为了理解这两个⽅法执⾏的时机,简单的了解下bean的整个⽣命周期:
Bean的实例化过程(来⾃:Spring揭秘)
postProcessBeforeInitialization()⽅法与postProcessAfterInitialization()分别对应图中前置处理和后置处理两个步骤将执⾏的⽅法。这
两个⽅法中都传⼊了bean对象实例的引⽤,为扩展容器的对象实例化过程提供了很⼤便利,在这⼉⼏乎可以对传⼊的实例执⾏任何操作。
注解、AOP等功能的实现均⼤量使⽤了BeanPostProcessor,⽐如有⼀个⾃定义注解,你完全可以实现BeanPostProcessor的接⼝,在
其中判断bean对象的脑袋上是否有该注解,如果有,你可以对这个bean实例执⾏任何操作,想想是不是⾮常的简单?
再来看⼀个更常见的例⼦,在Spring中经常能够看到各种各样的Aware接⼝,其作⽤就是在对象实例化完成以后将Aware接⼝定义中规定
的依赖注⼊到当前实例中。
⽐如最常见的ApplicationContextAware接⼝,实现了这个接⼝的类都可以获取到⼀个ApplicationContext对象。当容器中每个对象的实
例化过程⾛到BeanPostProcessor前置处理这⼀步时,容器会检测到之前注册到容器的ApplicationContextAwareProcessor,然后就会
调⽤其postProcessBeforeInitialization()⽅法,检查并设置Aware相关依赖。
看看代码吧,是不是很简单:
// 代码来⾃:t.support.ApplicationContextAwareProcessor // 其postProcessBeforeInitialization⽅法调⽤了invokeAwareInterfaces⽅法 p
最后总结⼀下,本⼩节内容和你⼀起回顾了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的定义:
⽽基于JavaConfig的配置形式是这样的:
@Configuration public class MoonBookConfiguration { // 任何标志了@Bean的⽅法,其返回值将作为⼀个bean注册到Spring的IOC容器中 // ⽅法名默认成为该如果两个bean之间有依赖关系的话,在XML配置中应该是这样:
java接口例子⽽在JavaConfig中则是这样:
@Configuration public class MoonBookConfiguration { // 如果⼀个bean依赖另⼀个bean,则直接调⽤对应JavaConfig类中依赖bean的创建⽅法即可 // 这⾥直接
你可能注意到这个⽰例中,有两个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 { @
需要注意的是,在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, AnnotatedTypeM
当你⽤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提供的部分条件化注解:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论