Spring Boot 源码学习系列
上篇博文带大家从 Spring Boot
源码深入详解了 OnBeanCondition,那本篇也同样从源码入手,带大家深入了解 OnWebApplicationCondition 的过滤匹配实现。
在开始本篇的内容介绍之前,我们先来看看往期的系列文章【有需要的朋友,欢迎关注系列专栏】:
Spring Boot 源码学习 |
Spring Boot 项目介绍 |
Spring Boot 核心运行原理介绍 |
【Spring Boot 源码学习】@EnableAutoConfiguration 注解 |
【Spring Boot 源码学习】@SpringBootApplication 注解 |
【Spring Boot 源码学习】走近 AutoConfigurationImportSelector |
【Spring Boot 源码学习】自动装配流程源码解析(上) |
【Spring Boot 源码学习】自动装配流程源码解析(下) |
【Spring Boot 源码学习】深入 FilteringSpringBootCondition |
【Spring Boot 源码学习】OnClassCondition 详解 |
【Spring Boot 源码学习】OnBeanCondition 详解 |
本篇我们重点详解 OnWebApplicationCondition
的实现,参见如下:
鉴于前面博文的了解,我们知道 OnWebApplicationCondition
也是 FilteringSpringBootCondition
的子类,所以这里同样也是从 getOutcomes
方法源码来分析【Spring Boot 2.7.9】:
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootCondition {
// ...
@Override
protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
outcomes[i] = getOutcome(
autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"));
}
}
return outcomes;
}
// ...
}
上述逻辑很容易理解,遍历自动配置数组 autoConfigurationClasses
,循环如下:
首先,从 autoConfigurationClasses
中获取自动配置数据 autoConfigurationClass
;
然后,调用 AutoConfigurationMetadata
接口的 get(String className, String key)
方法来获取与 autoConfigurationClass
关联的名为 "ConditionalOnWebApplication"
的条件属性值【即应用类型枚举值】;
应用类型枚举可以查看 @ConditionalOnWebApplication
注解获取,如下所示:
最后,调用 getOutcome
方法,并传入上述获取的应用类型枚举值 type
:
type
是 SERVLET
, 则判断 org.springframework.web.context.support.GenericWebApplicationContext
是否存在;ConditionOutcome
对象【其中包含 did not find servlet web application classes 的信息 】。type
是 REACTIVE
,则判断 org.springframework.web.reactive.HandlerResult
是否存在;ConditionOutcome
对象【其中包含 did not find reactive web application classes 的信息 】。org.springframework.web.context.support.GenericWebApplicationContext
不存在且 org.springframework.web.reactive.HandlerResult
也不存在,则返回一个未满足过滤匹配条件的 ConditionOutcome
对象【其中包含 did not find reactive or servlet web application classes 的信息 】。null
。同 OnClassCondition
一样,OnWebApplicationCondition
同样实现了 FilteringSpringBootCondition
的父类 SpringBootCondition
中的抽象方法 getMatchOutcome
方法。
有关
SpringBootCondition
的介绍,这里不赘述了,请查看笔者的 【Spring Boot 源码学习】OnClassCondition 详解。
那么,我们进入 getMatchOutcome
方法中查看如下源码【Spring Boot 2.7.9】:
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean required = metadata.isAnnotated(ConditionalOnWebApplication.class.getName());
ConditionOutcome outcome = isWebApplication(context, metadata, required);
if (required && !outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
if (!required && outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
return ConditionOutcome.match(outcome.getConditionMessage());
}
我们来分析一下相关逻辑:
首先,通过调用 AnnotatedTypeMetadata
接口的 isAnnotated
方法,判断元数据中是否存在 @ConditionalOnWebApplication
注解【当应用程序为 Web 应用程序时,该条件注解用来匹配】。如果返回 true
,表示元数据中存在指定注解;否则,返回 false
。
然后,调用 isWebApplication
方法来获取条件匹配结果 outcome
【有关内容查看 第 3 小节】;
如果 required
为 true
【即存在 @ConditionalOnWebApplication
注解】,并且 条件结果不匹配,则返回一个新的 ConditionOutcome
对象,标记为不匹配,并带有原始的消息。
如果 required
为 false
【即不存在 @ConditionalOnWebApplication
注解】,并且 条件结果匹配,则同样返回一个新的 ConditionOutcome
对象,标记为不匹配,并带有原始的消息。
最后,上述两个条件判断都不满足,则将返回一个匹配的 ConditionOutcome
对象,并带有原始的消息。
下面,我们进入 isWebApplication
方法中:
private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata,
boolean required) {
switch (deduceType(metadata)) {
case SERVLET:
return isServletWebApplication(context);
case REACTIVE:
return isReactiveWebApplication(context);
default:
return isAnyWebApplication(context, required);
}
}
上述的逻辑也很简单:
首先,通过 deduceType
方法获取可获取的应用类型;查看其源码可知,如果存在 @ConditionalOnWebApplication
注解,则获取其对应的 type
属性;否则默认返回 Type.ANY
【即任何 Web
应用程序都将匹配】。
private Type deduceType(AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnWebApplication.class.getName());
if (attributes != null) {
return (Type) attributes.get("type");
}
return Type.ANY;
}
如果是 Type.SERVLET
,则调用 isServletWebApplication
方法返回条件匹配结果。
如果是 Type.REACTIVE
,则调用 isReactiveWebApplication
方法返回条件匹配结果。
如果不是上述两个应用类型,则默认调用 isAnyWebApplication
方法返回条件匹配结果。
我们直接翻看 isServletWebApplication
方法的源码,如下:
private ConditionOutcome isServletWebApplication(ConditionContext context) {
ConditionMessage.Builder message = ConditionMessage.forCondition("");
if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) {
return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
}
if (context.getBeanFactory() != null) {
String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
if (ObjectUtils.containsElement(scopes, "session")) {
return ConditionOutcome.match(message.foundExactly("'session' scope"));
}
}
if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
return ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"));
}
if (context.getResourceLoader() instanceof WebApplicationContext) {
return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
}
return ConditionOutcome.noMatch(message.because("not a servlet web application"));
}
我们来详细分析一下:
org.springframework.web.context.support.GenericWebApplicationContext
?
"did not find servlet web application classes"
。context
中 BeanFactory
不为空,则获取所有注册的 scope
名称,并检查其中是否包含 "session"
。如果包含,则返回匹配的结果,并附带消息 "found session scope"
。context
中 Environment
是 ConfigurableWebEnvironment
的实例,则将返回匹配的结果,并附带消息 "found ConfigurableWebEnvironment"
。context
中 ResourceLoader
是 WebApplicationContext
的实例,那么将返回匹配的结果,并附带消息 "found WebApplicationContext"
。"not a servlet web application"
。同样,我们也先来查看下 isReactiveWebApplication
方法的源码,如下:
private ConditionOutcome isReactiveWebApplication(ConditionContext context) {
ConditionMessage.Builder message = ConditionMessage.forCondition("");
if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, context.getClassLoader())) {
return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll());
}
if (context.getEnvironment() instanceof ConfigurableReactiveWebEnvironment) {
return ConditionOutcome.match(message.foundExactly("ConfigurableReactiveWebEnvironment"));
}
if (context.getResourceLoader() instanceof ReactiveWebApplicationContext) {
return ConditionOutcome.match(message.foundExactly("ReactiveWebApplicationContext"));
}
return ConditionOutcome.noMatch(message.because("not a reactive web application"));
}
通过上述 isServletWebApplication
方法中的分析,我们可以很快总结下:
org.springframework.web.reactive.HandlerResult
?
"did not find reactive web application classes"
。context
中 Environment
是 ConfigurableReactiveWebEnvironment
的实例,则将返回匹配的结果,并附带消息 "found ConfigurableReactiveWebEnvironment"
。context
中 ResourceLoader
是 ReactiveWebApplicationContext
的实例,那么将返回匹配的结果,并附带消息 "found ReactiveWebApplicationContext"
。"not a reactive web application"
。还是一样,我们先来看看 isAnyWebApplication
方法的源码,如下:
private ConditionOutcome isAnyWebApplication(ConditionContext context, boolean required) {
ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class,
required ? "(required)" : "");
ConditionOutcome servletOutcome = isServletWebApplication(context);
if (servletOutcome.isMatch() && required) {
return new ConditionOutcome(servletOutcome.isMatch(), message.because(servletOutcome.getMessage()));
}
ConditionOutcome reactiveOutcome = isReactiveWebApplication(context);
if (reactiveOutcome.isMatch() && required) {
return new ConditionOutcome(reactiveOutcome.isMatch(), message.because(reactiveOutcome.getMessage()));
}
return new ConditionOutcome(servletOutcome.isMatch() || reactiveOutcome.isMatch(),
message.because(servletOutcome.getMessage()).append("and").append(reactiveOutcome.getMessage()));
}
这里就更简单了,总结如下:
Servlet Web
应用程序的条件结果匹配并且 required
为 true
,则返回一个包含匹配状态和相关消息的 ConditionOutcome
对象。Reactive Web
应用程序的条件结果匹配并且 required
为 true
,则同样返回一个包含匹配状态和相关消息的 ConditionOutcome
对象。required
为 false
,则返回一个新的 ConditionOutcome
对象,它包含 servletOutcome.isMatch() || reactiveOutcome.isMatch()
的匹配状态 和 servletOutcome
与 reactiveOutcome
两者拼接的消息。本篇 Huazie 带大家从源码角度深入了解了自动配置过滤匹配子类 OnWebApplicationCondition
,至此 Spring Boot 中有关自动配置过滤匹配的三个实现已经介绍完毕,当然有关过滤匹配条件的内容还没结束,下一篇笔者将介绍 @Conditional
条件注解。