引言
SpringBoot内置了各种Starter的起步依赖,有了Starter我们不需要考虑项目需要什么库,版本会不会冲突等,大大减轻了我们的工作。
比如我们经典的web应用在引入spring-boot-starter-web
后,不需要再考虑spring-web
和spring-webmvc
及它们兼容的版本。
Maven的optional标签
关于RabbitMq在SpringBoot源码中有下面这样一个配置类,如下所示:
|
|
省略了大部分代码,剩余一个条件注解@ConditionalOnClass
,如不了解条件注解请阅读SpringBoot原理(一):自动配置 ,该注解表示
RabbitTemplate这个类存在时自动注入RabbitHealthContributorAutoConfiguration
实例。
看到这里有些同学可能会有疑惑,既然该类可能不存在,直接使用RabbitTemplate.class
,并使用import
关键词导入的类路径不会爆红吗?打包项目时不会报错吗?
像这样书写@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
通过反射加载字符串判断类是否存在不是才正确吗?
先说答案,两种方式都是正确的,为什么第一种方式类路径不爆红呢,我们通过ide点进RabbitTemplate
发现SpringBoot是引入了对应依赖的,那为什么还要判断该类是否存在呢,关键在于引入的方式,引入的pom依赖如下:
|
|
该依赖在spring-boot-actuator-autoconfigure
模块的pom中被引入,该依赖有一个<optional>true</optional>
标签,该标签为true
表示项目打包时不引入该依赖,
这就是可以使用且不暴红的原因。
显然易见,倘若使用到该处代码会发生notFoundClassException,因此要使用rabbitmq需主动引入相关依赖,引入依赖后再在application.yml
配置参数后就能使用@Autowired
获取对应实例。
通过上面一个例子我们说明了<optional>true</optional>
标签的作用,<exclusions></exclusions>
也有类似的功能。
Starter构建原理
我们可以看到SringBoot源码中spring-boot-build/spring-boot-project/spring-boot-starters
目录下有大量的Starter,以最常用的spring-boot-starter-web
起步依赖来说明。
spring-boot-build/spring-boot-project/spring-boot-starters/spring-boot-starter-web
目录下没有代码,只有一个pom.xml,简略内容如下:
|
|
可以看出,spring-boot-starter-web
模块依赖了spring-boot-starter
,spring-boot-starter-tomcat
,spring-web
和spring-webmvc
等模块。
spring-boot-starter
模块是绝大部分spring-boot-starter-xxx
模块依赖的基础模块。其中spring-boot-starter
依赖了spring-boot-autoconfigure
,因此
spring-boot-starter-web
间接依赖了spring-boot-autoconfigure
@ConfigurationProperties
经过上面的学习,我们明白了Starter引入依赖就自动配置的原因是条件注解+<optional>true</optional>
标签。
只引入一个Starter依赖是因为Starter本身已配置好对应依赖,例如spring-boot-starter-web
。
这些都是maven的功能,Starter还有一项重要的能力,在application.yml
中配置对应的参数,实例能自动获取到值并配置,比如我们在application.properties
配置文件中配置server.port=8081
,
该值会自动绑定到类ServerProperties
的属性port
上 。这是怎么实现的呢?可以推测与spring-boot-autoconfigure
有很大的相关性。
细心的我们发现类ServerProperties
上有注解@ConfigurationProperties
,与之关联的注解有@EnableConfigurationProperties
,接下来我们研究下两个注解的原理。
@ConfigurationProperties和@EnableConfigurationProperties
ServerProperties
的源码如下:
|
|
可以看到ServerProperties
类上标注的@ConfigurationProperties
注解,配置前缀为server
,ignoreUnknownFields
是否忽略未知值为true
注解@ConfigurationProperties
的源码为:
|
|
该注解有四个字段,其中value
为prefix
的别名,ignoreInvalidFields
表示忽略无效的配置,默认值为false。
@ConfigurationProperties
这个注解的作用就是将外部配置的配置值绑定到其注解的类的属性上,可以作用于配置类或配置类的方法上。
可以发现这个注解没有任何处理逻辑,是一个标志性注解,代码调用入口不在这里。端口和地址是属于服务器配置,与之关联的代码为:
|
|
这是一个ServletWeb相关的配置类,对应的条件注解我们也能明白其作用,重点不在这里,该类上有一个@EnableConfigurationProperties
注解,它的value值就是我们的服务器参数
配置类ServerProperties.class
,这个注解的作用应该就是为@ConfigurationProperties
注解绑定值提供支持。它是如何起作用的呢?
@EnableConfigurationProperties
的源码如下:
|
|
该注解上有一个重要注解@Import(EnableConfigurationPropertiesRegistrar.class)
,对于这种代码是否有些熟悉呢,如果你详细了解
SpringBoot原理(一):自动配置中@SpringBootApplication
注解
那部分。
@Import
导入的类EnableConfigurationPropertiesRegistrar
的源码为:
|
|
ImportBeanDefinitionRegistrar
接口是SpringBoot对外提供的扩展接口,被导入时会调用该接口的方法,我们知道一个普通的bean被配置到容器有两个大的步骤,
第一步被@Component
、@bean
、Import
注解的类和META-INF/spring.factories
中配置的类会被包装成BeanDefinition
实例存储到beanFactory
中,
只有一些特殊的bean比如后置处理器会被提前实例化;第二步调用最后的finishBeanFactoryInitialization
方法才是将BeanDefinition
实例实例化为真正的bean
ImportBeanDefinitionRegistrar
接口会在第一步扫描到配置类时注册为BeanDefinition
实例,同时也会获取该类上的@Import
导入的ImportBeanDefinitionRegistrar
实现类,并执行对应的方法,
可以在此时额外注册一些BeanDefinition
实例(如果你对这部分感兴趣,请继续阅读SpringBoot原理系列文章)
回到代码,这里在将x
注册为BeanDefinition
实例时,会执行实现类EnableConfigurationPropertiesRegistrar
的
registerBeanDefinitions
方法,对应源码为:
|
|
这里就不进行具体分析,该方法首先注册一个后置处理器BeanPostProcessor
的实例ConfigurationPropertiesBindingPostProcessor
,然后会扫描项目中的@EnableConfigurationProperties
注解,例如@EnableConfigurationProperties(ServerProperties.class)
等,
将它们注册成BeanDefinition
实例
在将BeanDefinition
实例真正实例化时会执行这个后置处理器为注解中的class的bean实例绑定值,如ServerProperties.class
实例
真正绑定的逻辑在ConfigurationPropertiesBindingPostProcessor
这里后置处理器中,它只重写了后置处理器的一个接口postProcessBeforeInitialization
,初始化之前进行绑定,源码如下:
|
|
核心就在这个bind方法,请自行分析
小结
通过上面的介绍我们了解了Starter特性的原理,但我们可能对两种不同的后置处理器BeanFactoryPostProcessor
和BeanPostProcessor
在SpirngBoot启动过程中的调用时机,
不了解一个注解(@Component
、@bean
等等)标识的类何时注册成BeanDefinition
实例,何时将BeanDefinition
实例变为真正的bean。
如果你对上诉问题感兴趣,请结合SpringBoot的源码和 SpringBoot原理(一):自动配置 、 SpringBoot原理(三):启动流程分析 、 SpringBoot原理(四):常用注解分析 进行调试学习。