AutoConfiguration事件注册

在完成了以上步骤的过滤、筛选之后,我们最终获得了要进行自动配置的类的集合,在将该集合返回之前,在
AutoConfigurationlmportSelector 类中完 成的最后一步操作就是相关事件的封装和广播,相关代码如下。

private void fireAutoConfigurationImportEvents(List<String> configurations ,
Set<String> exclusions) {
List<AutoConfigurationImportListener> listeners = getAutoConfigurat ionImp
ortL isteners();
if (!listeners. isEmpty()) {
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(t
his,
onfigurations, exclusions);
for (AutoConfigurationImportListener listener : listeners) {
invokeAwareMethods (listener);
listener . onAutoConf igurationImportEvent(event);
}
}
protected List<AutoConfigurationImportListener> getAutoConfigurat ionImportL
is-
teners() {
return SpringFactoriesLoader . loadFactories (AutoConf igurationImportL istene
r.class ,
this . beanClassLoader);
}

以上代码首先通过 SpringFactoriesLoader 类提供的 loadFactories 方法将 spring.factories中配置的接口
AutoConfigurationlmportListener 的实现类加载出来。然后,将筛选出的自动配置类集合和被排除的自动配置类集合封装成 AutoConfigurationImportEvent 事件对象,并传入该事件对象通过监听器提供的 onAutoConfigurationlmportEvent 方法,最后进行事件广播。关于事件及事件监听相关的内容不在此过多展开。

spring.factories 中自动配置监听器相关配置代码如下。

org. springframework . boot . autoconfigure . AutoConfigurat ionImportL istener=org .
springframework . boot . autoconfigure . condition . ConditionEvaluat ionReportAuto
ConfigurationImportListener

@Conditional 条件注解

前 面 我 们 完 成 了 自 动 配 置 类 的 读 取 和 筛 选 , 在 这 个 过 程 中 已经涉及了像@Conditional-OnClass 这 样 的 条 件 注 解 。 打 开 每 一 个 自 动 配 置 类 , 我 们 都 会 看 到@Conditional 或其衍生的条件注解。下面就先认识一 下 @Conditional 注解。

认识条件注解

@Conditional 注解是由 Spring 4.0 版本弓|入的新特性,可根据是否满足指定的条件来决定是否进行 Bean 的实例化及装配,比如,设定当类路径下包含某个 jar 包的时候才会对注解的类进行实例化操作。总之,就是根据一-些特定条件来控制 Bean 实例化的行为,@Conditional 注解代码如下。

@Target({ElementType. TYPE, ElementType .METHOD})
@Retent ion(RetentionPolicy . RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[ ] value();
}

@Conditional 注解唯一的元素 属性是接口 Condition 的数组,只有在数组中指定的所有Condition 的 matches 方法都返回 true 的情况下,被注解的类才会被加载。我们前面讲到的OnClassCondition 类就是 Condition 的子类之一,相关代码如下。

@FunctionalInterface
public interface Condition {
//决定条件是否匹配
boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata);}

matches 方法的第一个参数为 ConditionContext, 可通过该接口提供的方法来获得 Spring应用的上下文信息,ConditionContext 接口定义如下。

public interface ConditionContext {
//返 BeanDefinitionRegistry 注册表,可以检 查 Bean 的定义
BeanDefinitionRegistry getRegistry();
//返回 ConfigurableL is tableBeanFactory, 可以检查 Bean 是否已经存在, 进-步检查
Bean
属性
@Nullable
ConfigurableL istableBeanFactory getBeanFactory();
//返回 Environment,可以获得 当前应用环境变量,检测当前环境变量是否存在
Environment getEnvironment();
//返 ResourceLoader,用于读取或检查所加载的资源
ResourceLoader getResourceLoader();
//返回 ClassLoader,用于检查类是否存在
@Nullable
ClassLoader getClassLoader();
}

matches 方法的第二个 参数为 AnnotatedTypeMetadata,该接口提供了访问特定类或方法的注解功能,并且不需要加载类,可以用来检查带有@Bean 注解的方法上是否还有其他注解, AnnotatedTypeMetadata 接口定 义如下。

public interface AnnotatedTypeMetadata {
boolean isAnnotated(String annotationName);
@Nullable
Map<String, Object> getAnnotat ionAttributes (String annotationName) ;
@Nullable
Map<String, object> getAnnotationAttributes (String annotat ionName, boolean
classValuesAsString);
@Nullable
MultiValueMap<String, object> getAllAnnotat ionAttributes (String annotationName);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes (String annotationName,
boolean classValuesAsString);
}

该接口的 isAnnotated 方法能够提供判断带有@Bean 注解的方法上是否还有其他注解的功能。其他方法提供不同形式的获取@Bean 注解的方法上其他注解的属性信息。

条件注解的衍生注解

在 Spring Boot 的 autoconfigure 项目中提供了各类基于@Conditional 注解的衍生注解,它们适用不同的场景并提供了不同的功能。以下相关注解均位于 spring-boot-autoconfigure项目的
org.springframework.boot.autoconfigure.condition 包下。

.@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为例来对衍生 条件注解进行一个简单的分析。

@Target({ ElementType . TYPE,ElementType.METHOD })
@Retent ion(RetentionPolicy . RUNTIME)
@Documented
@Condit ional (OnWebApplicat ionCondition. class)
public @interface Condit iona lOnWebApplication {
//所需的 web 应用类型
Type type() default Type . ANY;
//可选应用类型枚举
enum Type {
//任何类型
ANY,
//基于 servlet 的 web 应用
SERVLET,
//基 Freactive 的 web 应用
REACTIVE
}
}

@
ConditionalOnWebApplication 注解的源代码中组合了@Conditional 注解,并且指定了对应的 Condition 为 OnWebApplicationCondition 。

OnWebApplicationCondition 类的结构与前面讲到的 OnClassCondition-样,都继承自SpringBootCondition 并 实 现
AutoConfigurationlmportFilter 接 口 。 关 于 实 现AutoConfigurationImportFilter 接口的 match 方法在前面已经讲解过,这里重点讲解关于继承 SpringBootCondition 和实现 Condition 接口的功能。

图 2-6 展示了以 OnWebApplicationCondition 为例的衍生注解的关系结构,其中省略了之前章节讲过的 Filter 相关内容,重点描述了 Condition 的功能和方法。

上一节我们已经学习了 Condition 接口的源码,那么抽象类 SpringBootCondition 是如何实现该方法的呢?相关源代码如下。

public abstract class SpringBootCondition implements Condition {
@Override
public final boolean matches (ConditionContext context ,
AnnotatedTypeMetadata metadata) {
ConditionOutcome outcome = getMatchOutcome(context, metadata);
return outcome . isMatch();
}
public abstract ConditionOutcome getMatchOutcome ( ConditionContext context,
AnnotatedTypeMetadata metadata) ;
}

在抽象类 SpringBootCondition 中实现了 matches 方法,而该方法中最核心的部分是通过调用新定义的抽象方法 getMatchOutcome 并交由子类来实现,在 matches 方法中根据子类返回的结果判断是否匹配。下面我们来看 OnWebApplicationCondition 的源代码是如何实现相关功能的。

@Order(Ordered . HIGHEST_ PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context ,
AnnotatedTypeMetadata metadata) {
boolean required = metadata . isAnnotated (Condit ionalOnWebApplication.cla
55.
getName());
ConditionOutcome outcome = isWebApplication(context, metadata, require
d);
if (required && !outcome. isMatch()) {
return ConditionOutcome . noMatch(outcome. getConditionMessage());
if (!required && outcome . isMatch()) {
return ConditionOutcome . noMatch(outcome . getConditionMessage());
return ConditionOutcome . match(outcome . getConditionMessage());
}

可 以 看 出 , 是 否 匹 配 是 由 两 个 条 件 决 定 的 : 被 注 解 的 类 或 方 法 是 否 包 含
ConditionalOn-WebApplication 注解,是否为 Web 应用。

.如 果包含 ConditionalOn WebApplication 注解,并且不是 Web 应用,那么返回不匹配。

.如果不包含
ConditionalOnWebApplication 注解,并且是 Web 应用,那么返回不匹配。

.其他情况,返回匹配。

下面我们以 SERVLET Web 应用为例,看相关源代码是如何判断是否为 Web 应用的。REACTIVE Web 应用和其他类型的 Web 应用可参照学习。

@Order (Ordered .HIGHEST PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootConditionprivate static final String SERVLET_ WEB_ APPLICATION_ CLASS = "org. springfr
amework .
web. . context . support . Gener icWebApplicationContext";
//推断 web 应用是否匹配
private ConditionOutcome isWebApplication(ConditionContext context,
AnnotatedTypeMetadata metadat
a, boolean required) {
switch (deduceType(metadata)) {
case SERVLET:
//是否为 SERVLET
return isServletWebApplication(context);
case REACTIVE:
'是否为 REACTIVE
return isReactiveWebApplication(context);
default:
//其他
return isAnyWebApplication(context, required);
}
private ConditionOutcome isServletWebApplication(ConditionContext contex
t) {
ConditionMessage . Builder message = ConditionMessage . forCondition("");
//判断常量定义类是否存在
if (!ClassNameFilter . isPresent(SERVLET_ WEB_ APPLICATION_ CLASS,
context . getClassLoader())) {
return ConditionOutcome . noMatch(
message. didNotFind("servlet web application classes").atAll());
}
//判断 BeanFactory 是否存在
if (context . getBeanFactory() != null) {
String[] scopes = context. getBeanFactory() . getRegisteredScopeNames();
if (ObjectUtils . containsElement(scopes, "session")) {
return ConditionOutcome . match(message . foundExactly("'session' scop
e"));
}
//判断 Environment 的类型是否为 Configurabl evebEnvironment 类型
if (context . getEnvironment() instanceof ConfigurableWebEnvironment) {return ConditionOutcome
.match(message . foundExactly("ConfigurableWebEnvironment"));
//判断 ResourceLoader 的类型是否为 webAppl icat ionContext 类型
if (context . getResourceLoader() instanceof WebApplicat ionContext) {
return ConditionOutcome . match(message . foundExactly( "WebApplicationCon
return ConditionOut come . noMatch(message . because("not a servlet web-
application"));
// MAnnotatedTypeMetad
ata 中获取 type 值
private Type deduceType
(AnnotatedTypeMetadata metadata) {
Map<String, 0bject> a
ttributes = metadata
. getAnnotat ionAttri
butes(Condit iona lOnWebApplicat ion. class . getName());
if (attributes != nul
1) {
return (Type) attri
butes.get("type");
return Type . ANY;
}
}

首 先 在 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,通过<filter>来进行配置。

在常规的 Web 项目中该配置位于 web.xml,通过<filter>来进行配置。

<filter>
<filter-name>encodingFilter</ filter-name>
<filter-class>org . springframework. web. filter . CharacterEncodingFilter
</filter-class>
<init - param>
<param- name> encoding</ param- name>
<param-value>UTF - 8</ param-value>
</init-param>
<init-param>
<param-name> forceEncoding< / param- name>
<param-value>true</param-value>
</init- param>
</filter>

而在 Spring Boot 中通过内置的
HttpEncodingAutoConfiguration 来完成这一功能。 下面我们具体分析一下该功能都涉及哪些配置和实现。

根据前面讲的操作流程,我们先来看一下 META-INF/spring.factories 中对该自动配置的注册。

# Auto Configure
org. springframework. boot . autoconfigure . EnableAutoConfiguration=\
org. springframework . boot . autoconfigure .web . servlet . HttpEncodingAutoConfigur
ation,\

当完成注册之后,在加载的过程中会使用元数据的配置进行过滤,对应的配置内容在
META-INF/spring-autoconfigure-metadata.properties 文件中。

org. springframework. boot . autoconfigure . web. servlet .HttpEncodingAutoConfigur

ation. Conditional0nClass=org . springfr amework . web. filter. CharacterEncodingFilter

在 过 滤 的 过 程 中 要 判 断 自 动 配 置 类
HttpEncodingAutoConfiguration 是 否 被@ConditionalOnClass 注解,源代码如下。

@Configuration
@EnableConfigurat ionProperties (HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication. Type . SERVLE@Conditional0nClass (CharacterEncodingFilter . class)
@ConditionalOnProperty(prefix = "spring. http. encoding", value = "enabled" ,
matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final HttpProperties . Encoding properties ;
public HttpEncodingAutoConf igurat ion(HttpProperties properties) {
this. properties = properties. getEncoding();
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter. setEncoding(this . properties . getCharset() .name());
filter. setForceRequestEncoding( this . properties . shouldForce(Type . REQUES
T));
filter . setForceResponseEncoding( this . properties . shouldForce(Type. RESPON
SE));
return filter;
}
}

很明显,它被@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" )

public class HttpProperties {
...
public static class Encoding {
public static final Charset DEFAULT_ CHARSET = StandardCharsets.UTF_ 8;
private Charset charset = DEFAULT CHARSET;
private Boolean force;
private Boolean forceRequest;
private Boolean forceResponse;
private Map<Locale, Charset> mapping;
...
}
}

而在 application.properties 中,我们会进行如下对应配置:

spring . http. encoding . force=true
spring . http. encoding. charset=UTF-8
spring . http. encoding . force-request=true
...

小结

本章围绕SpringBoot的核心功能展开,带大家从总体上了解 Spring Boot 自动配置的原理以及自动配置核心组件的运作过程。只有掌握了这些基础的组建内容及其功能,我们在后续集成其他三方类库的自动配置时,才能够更加清晰地了解它们都运用了自动配置的哪些功能。本章需重点学习自动配置原理、@EnableAutoConfiguration、@Import、ImportSelector、@Conditional 以及示例解析部分的内容。

本文给大家讲解的内容是AutoConfiguration事件注册和@Conditional 条件注解、实例解析;

  1. 下篇文章给大家讲解的是SpringBoot构造流程源码分析;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

SpringBoot内置http编码功能为例分析自动配置过程相关推荐

  1. SpringBoot内置tomcat出现error:An incompatible version [1.1.32] of the APR based Apache Tomcat Native lib

    SpringBoot内置tomcat出现error:An incompatible version [1.1.32] of the APR based Apache Tomcat Native lib ...

  2. SpringBoot内置tomcat出现APR版本过低解决办法

    SpringBoot内置tomcat出现error:An incompatible version [1.1.32] of the APR based Apache Tomcat Native lib ...

  3. 玩转代码|解决Chrome浏览器内置谷歌翻译功能无法使用问题!

    最近这几天在使用Chrome浏览器的内置谷歌翻译功能时,总是一直停留在不翻译的状态,一开始我还以为是网络波动过几天就好了,过了好几天依旧是这样.去看了新闻才知道谷歌翻译已经退出了中国市场. 根据Tec ...

  4. SpringBoot内置Tomcat支持多大并发量和连接数

    SpringBoot内置Tomcat,再默认设置中,Tomcat的最大线程数是200,最大连接数是10000.支持的并发量是指连接数,200个线程如何处理10000条连接的? Tomcat有两种处理连 ...

  5. 基于SpringBoot监控Java项目,暴漏springboot内置端点

    基于SpringBoot监控Java项目的指标 文章目录 基于SpringBoot监控Java项目的指标 监控java项目有哪些方案 springboot内置端口 prometheus 如何使用 sp ...

  6. flyme7 android彩蛋,Flyme 7内置彩蛋功能:520教你如何脱颖而出

    原标题:Flyme 7内置彩蛋功能:520教你如何脱颖而出 5月20日是一个特殊的日子,逐渐演变为"网络情人节",或许有一部分单身伙伴们会遭到暴击伤害,估计朋友圈会被大捧的玫瑰花刷 ...

  7. Zabbix5系列-监控SpringBoot内置的Tomcat和JVM(二十一)

    Zabbix5系列-监控SpringBoot内置的Tomcat和JVM 一.参考 二.环境 三.开启JMX 四.配置模板 五.测试 六.修改后的模板参考 6.1 Apache Tomcat JMX模板 ...

  8. Photoshop 2023 Beta 内置Ai绘图功能介绍安装教程

    距离Adobe软件公司首次将图像编辑及数字绘画软件Photoshop推出到大众面前已经过去35年,最近该公司又再次书写了属于Photoshop的历史新篇章. 最近,Adobe 宣布 Photoshop ...

  9. ARUBA无线控制器内置网管功能介绍

    ARUBA无线控制器内置网管功能介绍 ARUBA无线控制器内置了业界最为丰富的无线网络管理功能,用户不需要增加任何额外费用就可以实现以下功能: l 图形化的无线网络集中配置 l 图形化的网络实时性能监 ...

最新文章

  1. 反距离加权法高程_干货:企业估值的收益法、成本法和市场法
  2. PCL学习笔记,区域生长分割(region growing segmentation)
  3. imx8m开发板资料
  4. CAN总线在嵌入式Linux下驱动程序的实现
  5. 小程序设置header cookie
  6. linux的常用操作——open函数
  7. python安装失败无法访问_错误:由于环境错误而无法安装包错误:[WinError 5]访问被拒绝:...
  8. poj2352-线段树-start
  9. Android点击图片随机,android 设置图片随机出现-两种方式
  10. 安卓第十三天笔记-服务(Service)
  11. 微信支付之H5页面WAP端接入
  12. WEB前端是干什么的?
  13. stm32呼吸灯c语言程序,STM32之呼吸灯
  14. java影视app对接cms,原生影视App双端对接飞飞CMS
  15. 什么是TailwindCSS
  16. 网易花19亿买地,要在上海建总部大楼,团队至少2000人
  17. New Year Snowmen((贪心)map+优先队列)
  18. 笔记本电脑都有光驱吗?
  19. Xilinx FPGA时钟及I/O接口规划(一)
  20. javaAPI文档中文版(JDK11在线版)无需下载、直接打开

热门文章

  1. Python北理工_turtle绘画
  2. 知识变现海哥:让知识快速变现的三个网站
  3. 动态规划之背包问题---01背包---完全背包---多重背包
  4. 初涉STM32之浅谈时钟使能问题
  5. RecyclerView局部刷新的坑
  6. linux删除文件不需要确定,linux的rm命令-删除文件或目录
  7. iphone6计算机隐藏功能键,iPhone的这些隐藏功能 你知道几个
  8. 被老板怼“离职了还好意思抢红包”,别狗血式退群,高手都用3招
  9. html5 语音唤醒,breeno语音怎么和录屏声音同时用? 进入Breeno语音页面后点击语音唤醒;...
  10. mac用python爬虫下载图片_使用Python爬虫实现自动下载图片