AutoConfiguration事件注册
在完成了以上步骤的过滤、筛选之后,我们最终获得了要进行自动配置的类的集合,在将该集合返回之前,在 AutoConfigurationlmportSelector 类中完 成的最后一步操作就是相关事件的封装和广播,相关代码如下。
以上代码首先通过 SpringFactoriesLoader 类提供的 loadFactories 方法将 spring.factories中配置的接口 AutoConfigurationlmportListener 的实现类加载出来。然后,将筛选出的自动配置类集合和被排除的自动配置类集合封装成 AutoConfigurationImportEvent 事件对象,并传入该事件对象通过监听器提供的 onAutoConfigurationlmportEvent 方法,最后进行事件广播。关于事件及事件监听相关的内容不在此过多展开。
spring.factories 中自动配置监听器相关配置代码如下。
@Conditional 条件注解
前 面 我 们 完 成 了 自 动 配 置 类 的 读 取 和 筛 选 , 在 这 个 过 程 中 已经涉及了像@Conditional-OnClass 这 样 的 条 件 注 解 。打 开 每 一 个 自 动 配 置 类 , 我 们 都 会 看 到@Conditional 或其衍生的条件注解。下面就先认识一 下 @Conditional 注解。
认识条件注解
@Conditional 注解是由 Spring 4.0 版本弓|入的新特性,可根据是否满足指定的条件来决定是否进行 Bean 的实例化及装配,比如,设定当类路径下包含某个 jar 包的时候才会对注解的类进行实例化操作。总之,就是根据一-些特定条件来控制 Bean 实例化的行为,@Conditional 注解代码如下。
@Conditional 注解唯一的元素 属性是接口 Condition 的数组,只有在数组中指定的所有Condition 的 matches 方法都返回 true 的情况下,被注解的类才会被加载。我们前面讲到的OnClassCondition 类就是 Condition 的子类之一,相关代码如下。
matches 方法的第一个参数为 ConditionContext, 可通过该接口提供的方法来获得 Spring应用的上下文信息,ConditionContext 接口定义如下。
matches 方法的第二个 参数为 AnnotatedTypeMetadata,该接口提供了访问特定类或方法的注解功能,并且不需要加载类,可以用来检查带有@Bean 注解的方法上是否还有其他注解, AnnotatedTypeMetadata 接口定 义如下。
该接口的 isAnnotated 方法能够提供判断带有@Bean 注解的方法上是否还有其他注解的功能。其他方法提供不同形式的获取@Bean 注解的方法上其他注解的属性信息。
条件注解的衍生注解
.@ConditionalOnBean: 在容器中有指定 Bean 的条件下。
.@ConditionalOnClass: 在 classpath 类路径下有指定类的条件下。
.@ConditionalOnCloudPlatform: 当指定的云平台处于 active 状态时。
.@ConditionalOnExpression: 基于 SpEL 表达式的条件判断。
.@ConditionalOnJawa:基于 JVM 版本作为判断条件。
. @ConditionalOnJndi: 在 JNDI 存在的条件下查找指定的位置。
.@ConditionalOnMissingBean:当容器里没有指定 Bean 的条件时。
.@ConditionalOnMissingClass: 当类路径下没有指定类的条件时。
.@ConditionalOnNotWebApplication: 在项目不是一个 Web 项目的条件下。
. @ConditionalOnProperty: 在指定的属性有指定值的条件下。
.@ConditionalOnResource: 类路径是否有指定的值。
.@ConditionalOnSingleCandidate: 当指定的 Bean 在容器中只有一个或者有多个但是指定了首选的 Bean 时。
.@ConditionalOnWebApplication:在项目是一个 Web 项目的条件下。
如果仔细观察这些注解的源码,你会发现它们其实都组合了@Conditional 注解,不同之处是它们在注解中指定的条件( Condition)不同。下面我们以@ConditionalOnWebApplication为例来对衍生 条件注解进行一个简单的分析。
@ConditionalOnWebApplication 注解的源代码中组合了@Conditional 注解,并且指定了对应的 Condition 为 OnWebApplicationCondition 。
OnWebApplicationCondition 类的结构与前面讲到的 OnClassCondition-样,都继承自SpringBootCondition 并 实 现 AutoConfigurationlmportFilter 接 口 。关 于 实 现AutoConfigurationImportFilter 接口的 match 方法在前面已经讲解过,这里重点讲解关于继承 SpringBootCondition 和实现 Condition 接口的功能。
图 2-6 展示了以 OnWebApplicationCondition 为例的衍生注解的关系结构,其中省略了之前章节讲过的 Filter 相关内容,重点描述了 Condition 的功能和方法。
上一节我们已经学习了 Condition 接口的源码,那么抽象类 SpringBootCondition 是如何实现该方法的呢?相关源代码如下。
在抽象类 SpringBootCondition 中实现了 matches 方法,而该方法中最核心的部分是通过调用新定义的抽象方法 getMatchOutcome 并交由子类来实现,在 matches 方法中根据子类返回的结果判断是否匹配。下面我们来看 OnWebApplicationCondition 的源代码是如何实现相关功能的。
可 以 看 出 , 是 否 匹 配 是 由 两 个 条 件 决 定 的 : 被 注 解 的 类 或 方 法 是 否 包 含ConditionalOn-WebApplication 注解,是否为 Web 应用。
.如 果包含 ConditionalOn WebApplication 注解,并且不是 Web 应用,那么返回不匹配。
.如果不包含 ConditionalOnWebApplication 注解,并且是 Web 应用,那么返回不匹配。
.其他情况,返回匹配。
下面我们以 SERVLET Web 应用为例,看相关源代码是如何判断是否为 Web 应用的。REACTIVE Web 应用和其他类型的 Web 应用可参照学习。
首 先 在 isWebApplication 方 法 中 讲 行 Web 应用类型的推断 。这 里 使 用AnnotatedTypeMetadata 的 getAnnotationAttributes 方 法 获 取 所 有 关 于@ConditionalOnWebApplication 的注解属性。返回值为 null 说明未配置任何属性,默认为Type.ANY,如果配置属性,会获得 type 属性对应的值。
如果返回值为 Type.SERVLET,调用 isServletWebApplication 方 法来进行判断。该方法的判断有以下条件。
:GenericWebApplicationContext 类是否在类路径下。
.容器内是否存在注册名称为 session 的 scope。
.容器的 Environment 是否为 ConfigurableWebEnvironment。
.容器的 ResourceLoader 是否为 WebApplicationContext.
在完成了以上判断之后,得出的最终结果封装为 ConditionOutcome 对象返回,并在抽象类SpringBootCondition 的 matches 方法中完成判断, 返回最终结果。
实例解析
在了解整个 Spring Boot 的运作原理之后,我们以 Spring Boot 内置的 http 编码功能为例,分析一下整个自动配置的过程。
在常规的 Web 项目中该配置位于 web .xml,通过来进行配置。
在常规的 Web 项目中该配置位于 web.xml,通过来进行配置。
而在 Spring Boot 中通过内置的 HttpEncodingAutoConfiguration 来完成这一功能。下面我们具体分析一下该功能都涉及哪些配置和实现。
根据前面讲的操作流程,我们先来看一下 META-INF/spring.factories 中对该自动配置的注册。
当完成注册之后,在加载的过程中会使用元数据的配置进行过滤,对应的配置内容在META-INF/spring-autoconfigure-metadata.properties 文件中。
org. springframework. boot . autoconfigure . web. servlet .HttpEncodingAutoConfigur
ation. Conditional0nClass=org . springfr amework . web. filter. CharacterEncodingFilter
在 过 滤 的 过 程 中 要 判 断 自 动 配 置 类 HttpEncodingAutoConfiguration 是 否 被@ConditionalOnClass 注解,源代码如下。
很明显,它被@ConditionalOnClass 注解, 并且指定实例化的条件为类路径下必须有CharacterEncodingFilter 存在。再看一下该类的其他注解。
.@Configuration:指定该类作为配置项来进行实例化操作。
.@ EnableConfigurationProperties:参数为 HttpProperties.class,开启属性注入,会将参数中的 HttpProperties 注入该类。
. @ConditionalOnWebApplication: 参数为 Type .SERVLET, 说明该类只有在基于 servlet的 Web 应用中才会被实例化。
.@ConditionalOnClass:参数为 CharacterEncodingFilter.class,只有该参数存在,才会被实例化。
@ConditionalOnProperty:指定配置文件内 spring .ttp.encoding 对应的值,如果为 enabled才会进行实例化,没有配置则默认为 true。
.@ConditionalOnMissingBean: 注释于方法上,与@Bean 配合,当容器中没有该 Bean 的实例化对象时才会进行实例化。
其中 HttpProperties 类的属性值对应着 application.yml 或 application.properties的配通过注解@ConfigurationProperties(prefix="sprig.http")实现的属性注入。关于属性注入,后面章节会详细讲解,这里我们先看一下源代码和对应的配置文件参数。@ConfigurationProperties(prefix = "spring.http" )
而在 application.properties 中,我们会进行如下对应配置:
小结
本章围绕SpringBoot的核心功能展开,带大家从总体上了解 Spring Boot 自动配置的原理以及自动配置核心组件的运作过程。只有掌握了这些基础的组建内容及其功能,我们在后续集成其他三方类库的自动配置时,才能够更加清晰地了解它们都运用了自动配置的哪些功能。本章需重点学习自动配置原理、@EnableAutoConfiguration、@Import、ImportSelector、@Conditional 以及示例解析部分的内容。
领取专属 10元无门槛券
私享最新 技术干货