SpringBoot内置http编码功能为例分析自动配置过程
作者:快盘下载 人气: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 条件注解、实例解析;
下篇文章给大家讲解的是SpringBoot构造流程源码分析;觉得文章不错的朋友可以转发此文关注小编;感谢大家的支持!本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。
加载全部内容