【Spring Boot 源码学习】OnWebApplicationCondition 详解

简介: 【1月更文挑战第5天】本篇同大家一起从源码角度了解 OnWebApplicationCondition

《Spring Boot 源码学习系列》

微信图片_20231031101034.png

引言

上篇博文带大家从 Spring Boot 源码深入详解了 OnBeanCondition,那本篇也同样从源码入手,带大家深入了解 OnWebApplicationCondition 的过滤匹配实现。

主要内容

本篇我们重点详解 OnWebApplicationCondition 的实现,参见如下:

ea2147b669614f27acab7d5605a505cb.png

1. getOutcomes 方法

鉴于前面博文的了解,我们知道 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 注解获取,如下所示:

    image.png

  • 最后,调用 getOutcome 方法,并传入上述获取的应用类型枚举值 type
    image.png

    • 如果 typeSERVLET, 则判断 org.springframework.web.context.support.GenericWebApplicationContext 是否存在;
      如果不存在,则返回一个未满足过滤匹配条件的 ConditionOutcome 对象【其中包含 did not find servlet web application classes 的信息 】。
    • 如果 typeREACTIVE,则判断 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

2. getMatchOutcome 方法

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 小节】;

  • 如果 requiredtrue【即存在 @ConditionalOnWebApplication 注解】,并且 条件结果不匹配,则返回一个新的 ConditionOutcome 对象,标记为不匹配,并带有原始的消息。
  • 如果 requiredfalse【即不存在 @ConditionalOnWebApplication 注解】,并且 条件结果匹配,则同样返回一个新的 ConditionOutcome 对象,标记为不匹配,并带有原始的消息。
  • 最后,上述两个条件判断都不满足,则将返回一个匹配的 ConditionOutcome 对象,并带有原始的消息。

    3. isWebApplication 方法

下面,我们进入 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 方法返回条件匹配结果。

3.1 isServletWebApplication 方法

我们直接翻看 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"
  • 如果条件上下文 contextBeanFactory 不为空,则获取所有注册的 scope 名称,并检查其中是否包含 "session"。如果包含,则返回匹配的结果,并附带消息 "found session scope"
  • 如果条件上下文 contextEnvironmentConfigurableWebEnvironment 的实例,则将返回匹配的结果,并附带消息 "found ConfigurableWebEnvironment"
  • 如果条件上下文 contextResourceLoaderWebApplicationContext 的实例,那么将返回匹配的结果,并附带消息 "found WebApplicationContext"
  • 如果上述的条件都不满足,则最后将返回不匹配的结果,并附带消息 "not a servlet web application"

3.2 isReactiveWebApplication 方法

同样,我们也先来查看下 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"
  • 如果条件上下文 contextEnvironmentConfigurableReactiveWebEnvironment 的实例,则将返回匹配的结果,并附带消息 "found ConfigurableReactiveWebEnvironment"
  • 如果条件上下文 contextResourceLoaderReactiveWebApplicationContext 的实例,那么将返回匹配的结果,并附带消息 "found ReactiveWebApplicationContext"
  • 如果上述的条件都不满足,则最后将返回不匹配的结果,并附带消息 "not a reactive web application"

3.3 isAnyWebApplication 方法

还是一样,我们先来看看 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()));
}

这里就更简单了,总结如下:

  • 首先,通过调用 isServletWebApplication 方法获取条件匹配结果;
    • 如果 Servlet Web 应用程序的条件结果匹配并且 requiredtrue,则返回一个包含匹配状态和相关消息的 ConditionOutcome 对象。
  • 接着,通过调用 isReactiveWebApplication 方法获取条件匹配结果;
    • 如果 Reactive Web 应用程序的条件结果匹配并且 requiredtrue,则同样返回一个包含匹配状态和相关消息的 ConditionOutcome 对象。
  • 最后,如果上述两种情况都不满足 或者 requiredfalse ,则返回一个新的 ConditionOutcome 对象,它包含 servletOutcome.isMatch() || reactiveOutcome.isMatch() 的匹配状态 和 servletOutcomereactiveOutcome 两者拼接的消息。

总结

本篇 Huazie 带大家从源码角度深入了解了自动配置过滤匹配子类 OnWebApplicationCondition ,至此 Spring Boot 中有关自动配置过滤匹配的三个实现已经介绍完毕,当然有关过滤匹配条件的内容还没结束,下一篇笔者将介绍 @Conditional 条件注解。

目录
相关文章
|
6月前
|
监控 Java 应用服务中间件
微服务——SpringBoot使用归纳——为什么学习Spring Boot
本文主要探讨为什么学习Spring Boot。从Spring官方定位来看,Spring Boot旨在快速启动和运行项目,简化配置与编码。其优点包括:1) 良好的基因,继承了Spring框架的优点;2) 简化编码,通过starter依赖减少手动配置;3) 简化配置,采用Java Config方式替代繁琐的XML配置;4) 简化部署,内嵌Tomcat支持一键式启动;5) 简化监控,提供运行期性能参数获取功能。此外,从未来发展趋势看,微服务架构逐渐成为主流,而Spring Boot作为官方推荐技术,与Spring Cloud配合使用,将成为未来发展的重要方向。
193 0
微服务——SpringBoot使用归纳——为什么学习Spring Boot
|
30天前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
5月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
376 70
|
5月前
|
Java Spring
Spring框架的学习与应用
总的来说,Spring框架是Java开发中的一把强大的工具。通过理解其核心概念,通过实践来学习和掌握,你可以充分利用Spring框架的强大功能,提高你的开发效率和代码质量。
145 20
|
10月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
261 2
|
6月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
114 0
|
8月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
304 7
|
9月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
203 2
|
10月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
215 9

热门文章

最新文章