精尽 Spring Boot 源码分析 —— Condition

1. 概述

在前面的文章,我们已经看过 Spring Boot 如何实现自动配置的功能,但是,实际场景下,这显然不够。为什么呢?因为每个框架的配置,需要满足一定的条件,才应该进行自动配置。这时候,我们很自然就可以想到 Spring Boot 的 Condition 功能。不过呢,Condition 功能并不是 Spring Boot 所独有,而是在 Spring Framework 中就已经提供了。那么,究竟是什么样的关系呢,我们在 「2. Condition 演进史」 来瞅瞅。

2. Condition 演进史

2.1 Profile 的出场

在 Spring3.1 的版本,为了满足不同环境注册不同的 Bean ,引入了 @Profile 注解。例如:

@Configuration
public class DataSourceConfiguration {@Bean@Profile("DEV")public DataSource devDataSource() {// ... 单机 MySQL}@Bean@Profile("PROD")public DataSource prodDataSource() {// ... 集群 MySQL}}
  • 在测试环境下,我们注册单机 MySQL 的 DataSource Bean 。
  • 在生产环境下,我们注册集群 MySQL 的 DataSource Bean 。

org.springframework.context.annotation.@Profile ,代码如下:

// Profile.java@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {/*** The set of profiles for which the annotated component should be registered.*/String[] value();}
  • 这是 Spring5 版本的 @Profile 注解的代码。它已经是经过 Condition 改造的实现。详细的,我们放在 「2.2 Condition」 。
  • 让我们在来看一眼 Spring3 版本的 @Profile 注解的代码。如下:

    // Profile.java@Retention(RetentionPolicy.RUNTIME)
    @Target({ANNOTATION_TYPE, // @Profile may be used as a meta-annotationTYPE             // In conjunction with @Component and its derivatives
    })
    public @interface Profile {static final String CANDIDATE_PROFILES_ATTRIB_NAME = "value";String[] value();
    }
    
    • 可以大体猜出,此时并没有将 Profile 作为 Condition 的一种情况。

2.2 Condition 的出现

在 Spring4 的版本,正式出现 Condition 功能,体现在 org.springframework.context.annotation.Condition 接口,代码如下:

// Condition.java@FunctionalInterface
public interface Condition {boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);}
  • 很简洁的一个接口,只有一个 #matches(...) 方法,用于判断是佛匹配。从参数中就可以看出,它是和注解配合,而这个注解便是 @Conditional 。

org.springframework.context.annotation.@Conditional 注解,也是在 Spring4 的版本,一起出现。代码如下:

// Conditional.java@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {Class<? extends Condition>[] value();}
  • 可以注解在方法、或者在上,表示需要满足的条件(Condition)。
  • 在 「2.1 Profile 的出现」 小节中,我们已经看到 @Profile 上,有 @Conditional(ProfileCondition.class) 的注解,表示使用 org.springframework.context.annotation.ProfileCondition 作为条件。
  • 当然,我们也可以直接在 Configuration 类上使用。例如:

    @Configuration
    public class TestConfiguration {@Bean@Conditional(XXXCondition.class)public Object xxxObject() {return new Object();}}
    
    • 即,创建 #xxxObject() 方法对应的 Bean 对象,需要满足 XXXCondition 条件。

在 Spring5 中,艿艿整理了下目前提供的 Condition 实现类,如下图:Condition 实现类

  • 显然,默认提供的 Condition 实现类非常少。

2.3 SpringBootCondition 的进击

为了满足更加丰富的 Condition(条件)的需要,Spring Boot 进一步拓展了更多的实现类,如下图所示:

Spring Boot Condition 实现类

  • org.springframework.boot.autoconfigure.condition.SpringBootCondition ,是 Spring Boot 实现 Condition 的抽象类,且是 Spring Boot 所有 Condition 实现类的基类。
  • 分别对应如下注解:
    • @ConditionalOnBean:当容器里有指定 Bean 的条件下。
    • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下。
    • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean 。
    • @ConditionalOnClass:当类路径下有指定类的条件下。
    • @ConditionalOnMissingClass:当类路径下没有指定类的条件下。
    • @ConditionalOnProperty:指定的属性是否有指定的值
    • @ConditionalOnResource:类路径是否有指定的值
    • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件。
    • @ConditionalOnJava:基于 Java 版本作为判断条件
    • @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
    • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
    • @ConditionalOnWebApplication:当前项目是 Web项 目的条件下。

2.4 小结

到了此处,我们基本算是理清了 Condition 的整个演进构成:

  • @Profile 注解,在 Spring3.1 提出,可以作为是 Condition 的雏形。
  • Condition 接口,在 Spring4 提出,是 Condition 的正式出现。
  • SpringCondition 抽象类,在 Spring Boot 实现,是对 Condition 进一步拓展。

下面,我们就正式开始撸 Condition 相关的源码落。

3. Condition 如何生效?

在上面的文章中,我们已经看到,@Conditional 注解,可以添加:

  • 类级别上
  • 方法级别上

添加到注解上,相当于添加到类级别或者方法级别上。

并且,一般情况下我们和配置类(Configuration)一起使用,但是实际上,我们也可以添加到普通的 Bean 类上。例如:

// DemoController.java@Controller
@RequestMapping("/demo")
@Conditional(TestCondition.class)
public class DemoController {@ResponseBody@RequestMapping("/hello")public String hello() {return "world";}}

那么,究竟 Condition 是如何生效的呢?分成两种情况:

  • 方式一,配置类。添加到配置类(Configuration)上面。
  • 方式二,创建 Bean 对象。添加到配置类(Configuration)、或者 Bean Class 的上面。

    本质上,方式二上的两种,都是创建 Bean 对象,所以统一处理方式即可。

假设,我们在 TestConfiguration 这个示例下进行测试,看看具体的调用链。代码如下:

// TestConfiguration.java@Configuration
@Conditional(TestCondition.class) // 艿艿自己编写的 Condition 实现类,方式测试调试
public class TestConfiguration {@Bean@Conditional(TestCondition.class)public Object testObject() {return new Object();}}// TestCondition.java
public class TestCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return true;}}

本小节,不会讲特别细的源码。

3.1 方式一:配置类

在 TestCondition 的 #matches(...) 方法中,打个断点。看看方式一情况下的具体的表现。如下图所示:调用栈

  • 通过调用 Condition#matches(...) 方法,判断该是否匹配。如果不匹配,内部所有方法,都无法创建 Bean 对象。

3.2 方式二:创建 Bean 对象

在 TestCondition 的 #matches(...) 方法中,打个断点。看看方式二情况下的具体的表现。如下图所示:调用栈

  • 通过调用 Condition#matches(...) 方法,判断是否匹配。如果吧匹配,则不从该方法加载 BeanDefinition 。这样,就不会创建对应的 Bean 对象了。

3.3 小结

至此,我们已经看到 Condition 如何生效。还是相对比较简单的。

下面,我们一起来看看 SpringBootCondition 如何实现它的进击。

4. ProfileCondition

艿艿:先插播下 ProfileCondition 的实现代码。

org.springframework.context.annotation.ProfileCondition ,实现 Condition 接口,给 @Profile 使用的 Condition 实现类。代码如下:

// ProfileCondition.javaclass ProfileCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// 获得 @Profile 注解的属性MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());// 如果非空,进行判断if (attrs != null) {// 遍历所有 @Profile 的 value 属性for (Object value : attrs.get("value")) {// 判断 environment 有符合的 Profile ,则返回 true ,表示匹配if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {return true;}}// 如果没有,则返回 falsereturn false;}// 如果为空,就表示满足条件return true;}}
  • 核心逻辑,获得 @Profile 的 value 属性,和 environment 是否有匹配的。如果有,则表示匹配。

5. SpringBootCondition

org.springframework.boot.autoconfigure.condition.SpringBootCondition ,实现 Condition 接口,Spring Boot Condition 的抽象基类,主要用于提供相应的日志,帮助开发者判断哪些被进行加载。如下是其上的类注释:

/*** Base of all {@link Condition} implementations used with Spring Boot. Provides sensible* logging to help the user diagnose what classes are loaded.*/

5.1 matches

实现 #matches(ConditionContext context, AnnotatedTypeMetadata metadata) 方法,实现匹配逻辑。代码如下:

// SpringBootCondition.java@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// <1> 获得注解的是方法名还是类名String classOrMethodName = getClassOrMethodName(metadata);try {// <2> 条件匹配结果ConditionOutcome outcome = getMatchOutcome(context, metadata);// <3> 打印结果logOutcome(classOrMethodName, outcome);// <4> 记录recordEvaluation(context, classOrMethodName, outcome);// <5> 返回是否匹配return outcome.isMatch();} catch (NoClassDefFoundError ex) {throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "+ ex.getMessage() + " not "+ "found. Make sure your own configuration does not rely on "+ "that class. This can also happen if you are "+ "@ComponentScanning a springframework package (e.g. if you "+ "put a @ComponentScan in the default package by mistake)",ex);} catch (RuntimeException ex) {throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);}
}
  • <1> 处,调用 #getClassOrMethodName(AnnotatedTypeMetadata metadata) 方法,获得注解的是方法名还是类名。代码如下:
    // SpringBootCondition.javaprivate static String getClassOrMethodName(AnnotatedTypeMetadata metadata) {// 类if (metadata instanceof ClassMetadata) {ClassMetadata classMetadata = (ClassMetadata) metadata;return classMetadata.getClassName();}// 方法MethodMetadata methodMetadata = (MethodMetadata) metadata;return methodMetadata.getDeclaringClassName() + "#" + methodMetadata.getMethodName();
    }
    
  • <2> 处,调用 #getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) 抽象方法,执行匹配,返回匹配结果。这是一个抽象方法,由子类进行实现。
    • org.springframework.boot.autoconfigure.condition.ConditionOutcome ,匹配结果。
    • org.springframework.boot.autoconfigure.condition.ConditionMessage ,匹配消息。
    • 以上的类,自己瞅瞅。简单~
  • <3> 处,调用 #logOutcome(String classOrMethodName, ConditionOutcome outcome) 方法,打印结果日志。代码如下:

    // SpringBootCondition.javaprotected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) {if (this.logger.isTraceEnabled()) {this.logger.trace(getLogMessage(classOrMethodName, outcome));}
    }private StringBuilder getLogMessage(String classOrMethodName, ConditionOutcome outcome) {StringBuilder message = new StringBuilder();message.append("Condition ");message.append(ClassUtils.getShortName(getClass()));message.append(" on ");message.append(classOrMethodName);message.append(outcome.isMatch() ? " matched" : " did not match");if (StringUtils.hasLength(outcome.getMessage())) {message.append(" due to ");message.append(outcome.getMessage());}return message;
    }
    
  • <4> 处,调用 #recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) 方法,记录到 ConditionEvaluationReport 。代码如下:

    // SpringBootCondition.javaprivate void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) {if (context.getBeanFactory() != null) {ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this, outcome);}
    }
    
    • 关于 org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport 类,先不详细看,避免研究过深。
  • <5> 处,返回是否匹配。

5.2 anyMatches

#anyMatches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition... conditions) 方法,判断是否匹配指定的 Condition 们中的任一一个。代码如下:

艿艿:总感觉这个方法,应该是个静态方法才合适。所以,胖友即酱油看看即可。

// SpringBootCondition.javaprotected final boolean anyMatches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition... conditions) {// 遍历 Conditionfor (Condition condition : conditions) {// 执行匹配if (matches(context, metadata, condition)) {return true;}}return false;
}
  • 遍历 conditions 数组,调用 #matches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition condition) 方法,执行匹配。代码如下:
    // SpringBootCondition.javaprotected final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition condition) {// 如果是 SpringBootCondition 类型,执行 SpringBootCondition 的直接匹配方法(无需日志)if (condition instanceof SpringBootCondition) {return ((SpringBootCondition) condition).getMatchOutcome(context, metadata).isMatch();}return condition.matches(context, metadata);
    }
    

总的来说,SpringBootCondition 这个类,没啥好说,重点还是在子类。

6. SpringBootCondition 的实现类

我们在回忆下,SpringBootCondition 的实现类,主要如下图:Spring Boot Condition 实现类

显然,我们不会去看每一个类的 SpringBootCondition 的实现类。所以呢,艿艿也不会每个类都写。

旁白君:偷懒都偷的如此猥琐,哈哈哈哈。

6.1 OnPropertyCondition

艿艿:来来来,先看一个容易的(捏个软柿子)。

org.springframework.boot.autoconfigure.condition.OnPropertyCondition ,继承 SpringBootCondition 抽象类,给 @ConditionalOnProperty 使用的 Condition 实现类。

如果胖友不熟悉 @ConditionalOnProperty 注解,赶紧打开 《@ConditionalOnProperty 来控制 Configuration 是否生效》 学习 3 分钟~不能再多了。

6.1.1 getMatchOutcome

#getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) 方法,获得匹配结果。代码如下:

// OnPropertyCondition.java@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {// <1> 获得 @ConditionalOnProperty 注解的属性List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));// <2> 存储匹配和不匹配的结果消息结果List<ConditionMessage> noMatch = new ArrayList<>();List<ConditionMessage> match = new ArrayList<>();// <3> 遍历 annotationAttributes 属性数组,逐个判断是否匹配,并添加到结果for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());}// <4.1> 如果有不匹配的,则返回不匹配if (!noMatch.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));}// <4.2> 如果都匹配,则返回匹配return ConditionOutcome.match(ConditionMessage.of(match));
}
  • <1> 处,调用 #annotationAttributesFromMultiValueMap(MultiValueMap<String, Object> multiValueMap) 方法,获得 @ConditionalOnProperty 注解的属性。代码如下:

    // OnPropertyCondition.javaprivate List<AnnotationAttributes> annotationAttributesFromMultiValueMap(MultiValueMap<String, Object> multiValueMap) {List<Map<String, Object>> maps = new ArrayList<>();multiValueMap.forEach((key, value) -> {for (int i = 0; i < value.size(); i++) {Map<String, Object> map;if (i < maps.size()) {map = maps.get(i);} else {map = new HashMap<>();maps.add(map);}map.put(key, value.get(i));}});List<AnnotationAttributes> annotationAttributes = new ArrayList<>(maps.size());for (Map<String, Object> map : maps) {annotationAttributes.add(AnnotationAttributes.fromMap(map));}return annotationAttributes;
    }
    
    • 懒的看整个代码实现的过程,可以直接看最终执行的结果图:`annotationAttributes`
  • <2> 处,存储匹配和不匹配的结果消息结果。

  • <3> 处,遍历 annotationAttributes 属性数组,逐个调用 #determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) 方法,判断是否匹配,并添加到结果。详细解析,见 「6.1.2 determineOutcome」 。
  • <4.1> 处,如果有不匹配的,则返回不匹配。返回结果示例如下:不匹配
  • <4.2> 处,如果都匹配,则返回匹配。返回结果示例如下:匹配

6.1.2 determineOutcome

#determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) 方法,判断是否匹配。代码如下:

// OnPropertyCondition.javaprivate ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {// <1> 解析成 Spec 对象Spec spec = new Spec(annotationAttributes);// <2> 创建结果数组List<String> missingProperties = new ArrayList<>();List<String> nonMatchingProperties = new ArrayList<>();// <3> 收集是否不匹配的信息,到 missingProperties、nonMatchingProperties 中spec.collectProperties(resolver, missingProperties, nonMatchingProperties);// <4.1> 如果有属性缺失,则返回不匹配if (!missingProperties.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).didNotFind("property", "properties").items(Style.QUOTE, missingProperties));}// <4.2> 如果有属性不匹配,则返回不匹配if (!nonMatchingProperties.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).found("different value in property", "different value in properties").items(Style.QUOTE, nonMatchingProperties));}// <4.3> 返回匹配return ConditionOutcome.match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
}
  • <1> 处,解析成 Spec 对象。Spec 是 OnPropertyCondition 的内部静态类。代码如下:

    // OnPropertyCondition#Spec.javaprivate static class Spec {/*** 属性前缀*/private final String prefix;/*** 是否有指定值*/private final String havingValue;/*** 属性名*/private final String[] names;/*** 如果属性不存在,是否认为是匹配的。** 如果为 false 时,就认为属性丢失,即不匹配。*/private final boolean matchIfMissing;Spec(AnnotationAttributes annotationAttributes) {String prefix = annotationAttributes.getString("prefix").trim();if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) {prefix = prefix + ".";}this.prefix = prefix;this.havingValue = annotationAttributes.getString("havingValue");this.names = getNames(annotationAttributes);this.matchIfMissing = annotationAttributes.getBoolean("matchIfMissing");}// 从 value 或者 name 属性种,获得值private String[] getNames(Map<String, Object> annotationAttributes) {String[] value = (String[]) annotationAttributes.get("value");String[] name = (String[]) annotationAttributes.get("name");Assert.state(value.length > 0 || name.length > 0, "The name or value attribute of @ConditionalOnProperty must be specified");Assert.state(value.length == 0 || name.length == 0, "The name and value attributes of @ConditionalOnProperty are exclusive");return (value.length > 0) ? value : name;}// ... 省略其它方法先~
    }
    
  • <2> 处,创建结果数组。

  • <3> 处,调用 Spec#collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) 方法,收集是否不匹配的信息,到 missingPropertiesnonMatchingProperties 中。代码如下:

    // OnPropertyCondition#Spec.java
    private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {// 遍历 names 数组for (String name : this.names) {// 获得完整的 keyString key = this.prefix + name;// 如果存在指定属性if (resolver.containsProperty(key)) {// 匹配值是否匹配if (!isMatch(resolver.getProperty(key), this.havingValue)) {nonMatching.add(name);}// 如果不存在指定属性} else {// 如果属性为空,并且 matchIfMissing 为 false ,则添加到 missing 中if (!this.matchIfMissing) {missing.add(name);}}}
    }private boolean isMatch(String value, String requiredValue) {// 如果 requiredValue 非空,则进行匹配if (StringUtils.hasLength(requiredValue)) {return requiredValue.equalsIgnoreCase(value);}// 如果 requiredValue 为空,要求值不为 falsereturn !"false".equalsIgnoreCase(value);
    }
    
    • 匹配的逻辑,胖友自己瞅瞅。可能比较绕的逻辑是,matchIfMissing 那块,也就看两眼就明白。
  • <4.1> 处,如果有属性缺失,则返回不匹配。
  • <4.2> 处,如果有属性不匹配,则返回不匹配。
  • <4.3> 处,返回匹配。

6.2 其它实现类

SpringBootCondition 的其它实现类,胖友可以自己去瞅瞅啦。当然,有部分实现类,我们会在 「8. FilteringSpringBootCondition」 分享。

7. AutoConfigurationImportFilter

在 《精尽 Spring Boot 源码分析 —— 自动配置》 一文中,我们埋了一个 AutoConfigurationImportSelector#filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) 方法的坑,没有进行详细解析。所以呢,这一节我们将填掉这个坑。

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter 接口,用于过滤掉无需自动引入的自动配置类(Configuration)。正如其类上的注释:

// AutoConfigurationImportFilter.java/*** Filter that can be registered in {@code spring.factories} to limit the* auto-configuration classes considered. This interface is designed to allow fast removal* of auto-configuration classes before their bytecode is even read.* @since 1.5.0*/
  • 重点是 "fast removal of auto-configuration classes before their bytecode is even read" 。因为自动配置类可能会很多,如果无需使用,而将字节码读取到内存中,这个是一种浪费。

AutoConfigurationImportFilter 的代码如下:

// AutoConfigurationImportFilter.java@FunctionalInterface
public interface AutoConfigurationImportFilter {/*** Apply the filter to the given auto-configuration class candidates.* @param autoConfigurationClasses the auto-configuration classes being considered.* This array may contain {@code null} elements. Implementations should not change the* values in this array.* @param autoConfigurationMetadata access to the meta-data generated by the* auto-configure annotation processor* @return a boolean array indicating which of the auto-configuration classes should* be imported. The returned array must be the same size as the incoming* {@code autoConfigurationClasses} parameter. Entries containing {@code false} will* not be imported.*/boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);}
  • 将传入的 autoConfigurationClasses 配置类们,根据 autoConfigurationMetadata 的元数据(主要是注解信息),进行匹配,判断是否需要引入,然后返回的 boolean[] 结果。
  • 并且,boolean[] 结果和 autoConfigurationClasses 配置类们是一一对应的关系噢。假设 autoConfigurationClasses[0] 对应的 boolean[0] 为 false ,表示无需引入,反之则需要引入。

7.1 AutoConfigurationImportFilter 类图

AutoConfigurationImportFilter 的子类如下图所示:AutoConfigurationImportFilter 类图

  • 从图中,我们很容易就看出,AutoConfigurationImportFilter 的最终实现类,都是构建在 SpringBootCondition 之上。😈 不过这也很正常,因为 Condition 本身提供的一个功能,就是作为配置类(Configuration)是否能够符合条件被引入。

7.2 FilteringSpringBootCondition

org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition ,继承 SpringBootCondition 抽象类,实现 AutoConfigurationImportFilter、BeanFactoryAware、BeanClassLoaderAware 接口,作为具有 AutoConfigurationImportFilter 功能的 SpringBootCondition 的抽象基类。

注意,上面特意加黑的“具有 AutoConfigurationImportFilter 功能”。

FilteringSpringBootCondition 的基本属性,如下:

// FilteringSpringBootCondition.javaprivate BeanFactory beanFactory;
private ClassLoader beanClassLoader;
  • 通过 Spring Aware 机制,进行注入。

7.2.1 match

实现 #match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) 方法,执行批量的匹配,并返回匹配结果。代码如下:

// FilteringSpringBootCondition.java@Override // 来自 AutoConfigurationImportFilter 接口
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {// <1> 获得 ConditionEvaluationReport 对象ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);// <2> 执行批量的匹配,并返回匹配结果ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);// <3.1> 创建 match 数组boolean[] match = new boolean[outcomes.length];// <3.2> 遍历 outcomes 结果数组for (int i = 0; i < outcomes.length; i++) {// <3.2.1> 赋值 match 数组match[i] = (outcomes[i] == null || outcomes[i].isMatch()); // 如果返回结果结果为空,也认为匹配// <3.2.2> 如果不匹配,打印日志和记录。if (!match[i] && outcomes[i] != null) {// 打印日志logOutcome(autoConfigurationClasses[i], outcomes[i]);// 记录if (report != null) {report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);}}}// <3.3> 返回 match 数组return match;
}
  • 从实现上,这个方法和 SpringBootCondition#match(ConditionContext context, AnnotatedTypeMetadata metadata) 方法,基本是一致的。或者说,是它的批量版本。
  • <1> 处,获得 ConditionEvaluationReport 对象。
  • <2> 处,调用 #getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) 抽象方法,执行批量的匹配,并返回匹配结果。这是一个抽象方法,由子类进行实现。
  • <3.1> 处,创建 match 数组。
  • <3.2> 处,遍历 outcomes 结果数组。
    • <3.2.1> 处,赋值 match 数组的当前元素。
    • <3.2.2> 处,如果不匹配,打印日志和记录。其中,#logOutcome(...) 方法,就是调用父类 SpringBootCondition 的方法。
  • <3.3> 处,返回 match 数组。

7.2.2 ClassNameFilter

ClassNameFilter ,是 FilteringSpringBootCondition 的内部类,提供判断类是否存在的功能。代码如下:

// FilteringSpringBootCondition#ClassNameFilter.javaprotected enum ClassNameFilter {/*** 指定类存在*/PRESENT {@Overridepublic boolean matches(String className, ClassLoader classLoader) {return isPresent(className, classLoader);}},/*** 指定类不存在*/MISSING {@Overridepublic boolean matches(String className, ClassLoader classLoader) {return !isPresent(className, classLoader);}};public abstract boolean matches(String className, ClassLoader classLoader);// 判断是否存在public static boolean isPresent(String className, ClassLoader classLoader) {if (classLoader == null) {classLoader = ClassUtils.getDefaultClassLoader();}try {forName(className, classLoader);return true;} catch (Throwable ex) {return false;}}// 加载指定类private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException {if (classLoader != null) {return classLoader.loadClass(className);}return Class.forName(className);}}
  • 里面提供了两个实现类,且是单例。
  • 代码比较简单,胖友 5 秒看懂。

7.2.3 filter

该方法,提供给子类使用。

#filter(Collection<String> classNames, ClassNameFilter classNameFilter, ClassLoader classLoader) 方法,通过使用 ClassNameFilter 类,过滤出符合条件的类名的数组。代码如下:

// FilteringSpringBootCondition.javaprotected List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter, ClassLoader classLoader) {// 如果为空,则返回空结果if (CollectionUtils.isEmpty(classNames)) {return Collections.emptyList();}// 创建 matches 数组List<String> matches = new ArrayList<>(classNames.size());// 遍历 classNames 数组,使用 ClassNameFilter 进行判断,是否匹配。for (String candidate : classNames) {if (classNameFilter.matches(candidate, classLoader)) {matches.add(candidate);}}// 返回return matches;
}

7.2.3 AutoConfigurationImportFilter 的使用

在 AutoConfigurationImportSelector#filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) 方法中,我们可以看到 AutoConfigurationImportFilter 的使用,过滤可以忽略的配置类。代码如下:

// AutoConfigurationImportSelector.javaprivate List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
//      // <0> 这里是艿艿乱加的。
//      if (true) {
//          return configurations;
//        }// <1> 声明需要用到的变量long startTime = System.nanoTime(); // 记录开始时间,用于下面统计消耗的时间String[] candidates = StringUtils.toStringArray(configurations); // 配置类的数组boolean[] skip = new boolean[candidates.length]; // 每个配置类是否需要忽略的数组,通过下标互相索引boolean skipped = false; // 是否有需要忽略的// <2> 遍历 AutoConfigurationImportFilter 数组,逐个匹配for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {// <2.1> 设置 AutoConfigurationImportFilter 的属性们invokeAwareMethods(filter);// <2.2> 执行批量匹配,并返回匹配结果boolean[] match = filter.match(candidates, autoConfigurationMetadata);// <2.3> 遍历匹配结果,判断哪些需要忽略for (int i = 0; i < match.length; i++) {if (!match[i]) { // 如果有不匹配的skip[i] = true;candidates[i] = null; // 标记为空,循环的下一次,就无需匹配它了。skipped = true; // 标记存在不匹配的}}}// <3.1> 如果没有需要忽略的,直接返回 configurations 即可if (!skipped) {return configurations;}// <3.2> 如果存在需要忽略的,构建新的数组,排除掉忽略的List<String> result = new ArrayList<>(candidates.length);for (int i = 0; i < candidates.length; i++) {if (!skip[i]) {result.add(candidates[i]);}}// 打印,消耗的时间,已经排除的数量if (logger.isTraceEnabled()) {int numberFiltered = configurations.size() - result.size();logger.trace("Filtered " + numberFiltered + " auto configuration class in "+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)+ " ms");}// 返回return new ArrayList<>(result);
}
  • <0> 处,这里是艿艿调皮加的。用于测试,如果去掉这块逻辑,是否需有影响。答案当然是,没有影响。这里就先不说原因,胖友自己思考下。实际上,本文也已经提及为什么了。
  • <1> 处,声明需要用到的变量。每个变量,已经添加其对应的注释,不再赘述。
  • <2> 处,调用 #getAutoConfigurationImportFilters() 方法,加载指定类型 AutoConfigurationImportFilter 对应的,在 META-INF/spring.factories 里的类名的数组。代码如下:

    // AutoConfigurationImportSelector.java
    protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
    }
    
    • 例如:`filters`
    • 就是我们看到的 AutoConfigurationImportFilter 的三个最终实现类。
  • <2.1><2.3><2.3> 处,就是对 AutoConfigurationImportFilter#filter(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) 方法的调用,妥妥的。是不是有点顺畅了。

  • <3.1> 处,如果没有需要忽略的,直接返回 configurations 即可。
  • <3.2> 处,如果存在需要忽略的,构建新的数组,排除掉忽略的。

当然,加载到的自动化配置类(Configuration)也会存在使用 @ConditionalOnProperty 等其它条件注解,但是不会在此处被过滤掉。艿艿猜测的原因,可能配置会从外部加载,此时暂时不太好判断。😈 不一定正确,可以星球讨论一波哟。

8. FilteringSpringBootCondition 的实现类

8.1 OnClassCondition

org.springframework.boot.autoconfigure.condition.OnClassCondition ,继承 FilteringSpringBootCondition 抽象类,给 @ConditionalOnClass@ConditionalOnMissingClass 使用的 Condition 实现类。

8.1.1 getOutcomes

实现 #getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) 方法,代码如下:

// OnClassCondition.java@Override // 来自 FilteringSpringBootCondition 抽象类
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {// Split the work and perform half in a background thread. Using a single// additional thread seems to offer the best performance. More threads make// things worse// <1> 在后台线程中将工作一分为二。原因是:// * 使用单一附加线程,似乎提供了最好的性能。// * 多个线程,使事情变得更糟int split = autoConfigurationClasses.length / 2;// <2.1> 将前一半,创建一个 OutcomesResolver 对象(新线程)OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split, autoConfigurationMetadata);// <2.2> 将后一半,创建一个 OutcomesResolver 对象OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split, autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());// 执行解析(匹配)ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes(); // <3.1>ConditionOutcome[] firstHalf =  firstHalfResolver.resolveOutcomes(); // <3.2>// <4> 创建 outcomes 结果数组,然后合并结果,最后返回ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);return outcomes;
}
  • <1> 处,考虑到配置类(Configuration)配置的 @ConditionalOnClass@ConditionalOnMissingClass 注解中的类可能比较多,所以采用多线程提升效率。但是经过测试,分成两个线程,效率是最好的,所以这里才出现了 autoConfigurationClasses.length / 2 代码。
  • <2.1> 处,调用 #createOutcomesResolver(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) 方法,创建一个 OutcomesResolver 对象。代码如下:

    // OnClassCondition.javaprivate OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, start, end, autoConfigurationMetadata, getBeanClassLoader());try {return new ThreadedOutcomesResolver(outcomesResolver);} catch (AccessControlException ex) {return outcomesResolver;}
    }
    
    • 首先,创建了一个 StandardOutcomesResolver 对象 outcomesResolver 。
    • 然后,创建了 ThreadedOutcomesResolver 对象,将 outcomesResolver 包装在其中。注意噢,下文我们会看到,ThreadedOutcomesResolver 是启动了一个新线程,执行 StandardOutcomesResolver 的逻辑。
  • <2.2> 处,将后一半,创建一个 StandardOutcomesResolver 对象。

  • 😈 注意哟,创建的 StandardOutcomesResolver、ThreadedOutcomesResolver 对象,都是 OutcomesResolver 的子类。
  • <3.1> 处,调用后一半的 StandardOutcomesResolver#resolveOutcomes() 方法,执行解析(匹配)。
  • <3.2> 处,调用前一半的 ThreadedOutcomesResolver#resolveOutcomes() 方法,执行解析(匹配)。在 ThreadedOutcomesResolver 的实现里,会使用 Thread#join() 方法,保证新起的线程,能完成它的任务。这也是为什么,ThreadedOutcomesResolver 后执行的原因。
  • <4> 处,创建 outcomes 结果数组,然后合并结果,最后返回。

8.1.2 OutcomesResolver

OutcomesResolver ,是 OnClassCondition 的内部接口,结果解析器接口。代码如下:

// OnClassCondition#OutcomesResolver.javaprivate interface OutcomesResolver {/*** 执行解析** @return 解析结果*/ConditionOutcome[] resolveOutcomes();}

它的实现类有:

  • 「8.1.3 ThreadedOutcomesResolver」
  • 「8.1.4 StandardOutcomesResolver」

8.1.3 ThreadedOutcomesResolver

ThreadedOutcomesResolver ,是 OnClassCondition 的内部类,实现 OutcomesResolver 接口,开启线程,执行 OutcomesResolver 的逻辑。代码如下:

// OnClassCondition#ThreadedOutcomesResolver.javaprivate static final class ThreadedOutcomesResolver implements OutcomesResolver {/*** 新起的线程*/private final Thread thread;/*** 条件匹配结果*/private volatile ConditionOutcome[] outcomes;private ThreadedOutcomesResolver(OutcomesResolver outcomesResolver) {// <1.1> 创建线程this.thread = new Thread(() -> this.outcomes = outcomesResolver.resolveOutcomes());// <1.2> 启动线程this.thread.start();}@Overridepublic ConditionOutcome[] resolveOutcomes() {// <2.1> 等待线程执行结束try {this.thread.join();} catch (InterruptedException ex) {Thread.currentThread().interrupt();}// <2.2> 返回结果return this.outcomes;}}
  • <1.1><1.2> 处,在构建方法中,创建新的线程,并启动线程,从而调用 OutcomesResolver#resolveOutcomes() 方法,执行匹配逻辑。
  • <2.1> 处,等待线程执行结束。
  • <2.2> 处,返回结果。
  • 😈 是不是这里一看,就明白 「8.1.1 getOutcomes」 中,是这样的调用顺序了。

8.1.4 StandardOutcomesResolver

StandardOutcomesResolver ,是 OnClassCondition 的内部类,实现 OutcomesResolver 接口,标准的 StandardOutcomesResolver 实现类。

8.1.4.1 构造方法

// OnClassCondition#StandardOutcomesResolver.javaprivate final class StandardOutcomesResolver implements OutcomesResolver {/*** 所有的配置类的数组*/private final String[] autoConfigurationClasses;/*** 匹配的 {@link #autoConfigurationClasses} 开始位置*/private final int start;/*** 匹配的 {@link #autoConfigurationClasses} 结束位置*/private final int end;private final AutoConfigurationMetadata autoConfigurationMetadata;private final ClassLoader beanClassLoader;private StandardOutcomesResolver(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata, ClassLoader beanClassLoader) {this.autoConfigurationClasses = autoConfigurationClasses;this.start = start;this.end = end;this.autoConfigurationMetadata = autoConfigurationMetadata;this.beanClassLoader = beanClassLoader;}// ... 省略无关的方法先
}

8.1.4.2 resolveOutcomes

#resolveOutcomes() 方法,执行批量匹配,并返回结果。代码如下:

// OnClassCondition#StandardOutcomesResolver.java@Override
public ConditionOutcome[] resolveOutcomes() {return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {// 创建 ConditionOutcome 结构数组ConditionOutcome[] outcomes = new ConditionOutcome[end - start];// 遍历 autoConfigurationClasses 数组,从 start 到 endfor (int i = start; i < end; i++) {String autoConfigurationClass = autoConfigurationClasses[i];if (autoConfigurationClass != null) {// <1> 获得指定自动配置类的 @ConditionalOnClass 注解的要求类String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");if (candidates != null) {// 执行匹配outcomes[i - start] = getOutcome(candidates);}}}return outcomes;
}
  • <1> 处,获得指定自动配置类的 @ConditionalOnClass 注解的要求类。例如下图:`candidates` 示例
  • <2> 处,调用 #getOutcome(String candidates) 方法,执行匹配。代码如下:

    // OnClassCondition#StandardOutcomesResolver.javaprivate ConditionOutcome getOutcome(String candidates) {try {// 如果没有 , ,说明只有一个,直接匹配即可if (!candidates.contains(",")) {return getOutcome(candidates, this.beanClassLoader); // <3>}// 如果有 , ,说明有多个,逐个匹配for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {// 执行匹配ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader); // <3>// 如果存在不匹配,则返回该结果if (outcome != null) {return outcome;}}} catch (Exception ex) {// We'll get another chance later}return null;
    }
    
    • <3> 处,调用 #getOutcome(String className, ClassLoader classLoader) 方法,执行匹配。代码如下:

      // OnClassCondition#StandardOutcomesResolver.javaprivate ConditionOutcome getOutcome(String className, ClassLoader classLoader) {if (ClassNameFilter.MISSING.matches(className, classLoader)) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class).didNotFind("required class").items(Style.QUOTE, className));}return null;
      }
      
      • 通过使用 ClassNameFilter.MISSING 来,进行匹配类是否不存在。

看到此处,我们会发现 「8.1.1 getOutcomes」 的整个逻辑,暂时只做了 @ConditionalOnClass 注解的条件匹配,还有一个 @ConditionalOnMissingClass 注解呢?答案在 「8.1.5 getMatchOutcome」 。

8.1.5 getMatchOutcome

#getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) 方法,执行 @ConditionalOnClass 和 @ConditionalOnMissingClass 注解的匹配。代码如下:

// OnClassCondition.java@Override // 来自 SpringBootCondition 抽象类
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {// <1> 声明变量ClassLoader classLoader = context.getClassLoader();ConditionMessage matchMessage = ConditionMessage.empty(); // 匹配的信息// <2> 获得 `@ConditionalOnClass` 注解的属性List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);if (onClasses != null) {// 执行匹配,看看是否有缺失的List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);// 如果有不匹配的,返回不匹配信息if (!missing.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class).didNotFind("required class", "required classes").items(Style.QUOTE, missing));}// 如果匹配,添加到 matchMessage 中matchMessage = matchMessage.andCondition(ConditionalOnClass.class).found("required class", "required classes").items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));}// <3> 获得 `@ConditionalOnMissingClass` 注解的属性List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);if (onMissingClasses != null) {// 执行匹配,看看是有多余的List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);// 如果有不匹配的,返回不匹配信息if (!present.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));}// 如果匹配,添加到 matchMessage 中matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));}// <4> 返回匹配的结果return ConditionOutcome.match(matchMessage);
}
  • <1> 处,声明变量。
  • <2> 处,获得 @ConditionalOnClass 注解的属性。后续的,通过使用 「7.2.3 filter」 方法,看看是否有缺失的类。
  • <3> 处,获得 @ConditionalOnMissingClass 注解的属性。后续的,通过使用 「7.2.3 filter」 方法,看看是否有多余的类。
  • <4> 处,返回匹配的结果。

8.2 OnWebApplicationCondition

org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition ,继承 FilteringSpringBootCondition 抽象类,给 @ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication 使用的 Condition 实现类。

8.2.1 getOutcomes

#getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) 方法,代码如下:

// OnWebApplicationCondition.java@Override // 来自 FilteringSpringBootCondition 抽象类
protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {// <1> 创建 outcomes 结果数组ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];// <2> 遍历 autoConfigurationClasses 数组,执行匹配for (int i = 0; i < outcomes.length; i++) {// 获得配置类String autoConfigurationClass = autoConfigurationClasses[i];if (autoConfigurationClass != null) {// 执行匹配outcomes[i] = getOutcome(autoConfigurationMetadata.get(autoConfigurationClass,  "ConditionalOnWebApplication")); // 获得 @ConditionalOnWebApplication 注解}}return outcomes;
}
  • <1> 处,创建 outcomes 结果数组。
  • <2> 处,遍历 autoConfigurationClasses 数组,调用 #getOutcome(String type) 方法,执行匹配。代码如下:

    // OnWebApplicationCondition.javaprivate static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";
    private static final String REACTIVE_WEB_APPLICATION_CLASS = "org.springframework.web.reactive.HandlerResult";  private ConditionOutcome getOutcome(String type) {if (type == null) {return null;}ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class);// <2.1> 如果要求 SERVLET 类型,结果不存在 SERVLET_WEB_APPLICATION_CLASS 类,返回不匹配if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) {return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());}}// <2.2> 如果要求 REACTIVE 类型,结果不存在 REACTIVE_WEB_APPLICATION_CLASS 类,返回不匹配if (ConditionalOnWebApplication.Type.REACTIVE.name().equals(type)) {if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll());}}// <2.3> 如果 SERVLET_WEB_APPLICATION_CLASS 和 REACTIVE_WEB_APPLICATION_CLASS 都不存在,返回不匹配if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())&& !ClassUtils.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {return ConditionOutcome.noMatch(message.didNotFind("reactive or servlet web application classes").atAll());}return null;
    }
    
    • <2.1> 处,如果要求 SERVLET 类型,结果不存在 SERVLET_WEB_APPLICATION_CLASS 类,返回不匹配。
    • <2.2> 处,如果要求 REACTIVE 类型,结果不存在 REACTIVE_WEB_APPLICATION_CLASS 类,返回不匹配。
    • <2.3> 处,如果 SERVLET_WEB_APPLICATION_CLASS 和 REACTIVE_WEB_APPLICATION_CLASS 都不存在,返回不匹配。

8.2.2 getMatchOutcome

#getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) 方法,代码如下:

// OnWebApplicationCondition.java@Override // 来自 SpringBootCondition 抽象类
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {// <1> 通过是否有 @ConditionalOnWebApplication 注解,判断是否要求在 Web 环境下boolean required = metadata.isAnnotated(ConditionalOnWebApplication.class.getName());// <2> 判断是否匹配 Web 环境ConditionOutcome outcome = isWebApplication(context, metadata, required);// <3.1> 如果要求,结果不匹配 Web 环境,返回最终不匹配if (required && !outcome.isMatch()) {return ConditionOutcome.noMatch(outcome.getConditionMessage());}// <3.2> 如果不要求,结果匹配 Web 环境,返回最终不匹配if (!required && outcome.isMatch()) {return ConditionOutcome.noMatch(outcome.getConditionMessage());}// <3.3> 返回匹配return ConditionOutcome.match(outcome.getConditionMessage());
}
  • <1> 处,通过是否有 @ConditionalOnWebApplication 注解,判断是否要求在 Web 环境下。为什么能这么判断呢?因为 @ConditionalOnNotWebApplication 注解,也能走进这个方法,但是如果没有 @ConditionalOnWebApplication 注解,就意味着有 @ConditionalOnNotWebApplication 注解,也就是不要求 Web 环境。😈 是不是有点绕~
  • <2> 处,调用 #isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata, boolean required) 方法,判断是否匹配 Web 环境。详细解析,见 「8.2.3 isWebApplication」 。
  • <3.1> 处,如果要求,结果不匹配 Web 环境,返回最终不匹配。
  • <3.2> 处,如果不要求,结果匹配 Web 环境,返回最终不匹配。
  • <3.3> 处,返回匹配。

8.2.3 isWebApplication

#isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata, boolean required) 方法,判断是否匹配 Web 环境。代码如下:

// OnWebApplicationCondition.javaprivate ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata, boolean required) {switch (deduceType(metadata)) { // <1> 获得要求的 Web 类型case SERVLET:return isServletWebApplication(context); // <2.1> 判断是否 Servlet Web 环境case REACTIVE:return isReactiveWebApplication(context); // <2.2> 判断是否 Reactive Web 环境default:return isAnyWebApplication(context, required); // <2.3> 判断是否为任意 Web 环境}
}
  • <1> 处,调用 #deduceType(AnnotatedTypeMetadata metadata) 方法,获得要求的 Web 类型。代码如下:

    // OnWebApplicationCondition.javaprivate Type deduceType(AnnotatedTypeMetadata metadata) {Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnWebApplication.class.getName());if (attributes != null) {return (Type) attributes.get("type");}return Type.ANY;
    }
    
  • <2.1> 处,调用 #isServletWebApplication(context) 方法,判断是否 Servlet Web 环境。代码如下:

    // OnWebApplicationCondition.javaprivate ConditionOutcome isServletWebApplication(ConditionContext context) {ConditionMessage.Builder message = ConditionMessage.forCondition("");// 如果不存在 SERVLET_WEB_APPLICATION_CLASS 类,返回不匹配if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) {return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());}if (context.getBeanFactory() != null) {// 如果不存在 session scope ,返回不匹配String[] scopes = context.getBeanFactory().getRegisteredScopeNames();if (ObjectUtils.containsElement(scopes, "session")) {return ConditionOutcome.match(message.foundExactly("'session' scope"));}}// 如果 environment 是 ConfigurableWebEnvironment 类型,返回匹配!!!if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {return ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"));}// 如果 resourceLoader 是 WebApplicationContext 类型,返回匹配!!!if (context.getResourceLoader() instanceof WebApplicationContext) {return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));}// 如果 resourceLoader 不是 WebApplicationContext 类型,返回不匹配return ConditionOutcome.noMatch(message.because("not a servlet web application"));
    }
    
  • <2.2> 处,调用 #isReactiveWebApplication(ConditionContext context) 方法,代码如下:

    // OnWebApplicationCondition.javaprivate ConditionOutcome isReactiveWebApplication(ConditionContext context) {ConditionMessage.Builder message = ConditionMessage.forCondition("");// 如果不存在 REACTIVE_WEB_APPLICATION_CLASS 类,返回不匹配if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, context.getClassLoader())) {return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll());}// 如果 environment 是 ConfigurableReactiveWebEnvironment 类型,返回匹配if (context.getEnvironment() instanceof ConfigurableReactiveWebEnvironment) {return ConditionOutcome.match(message.foundExactly("ConfigurableReactiveWebEnvironment"));}// 如果 resourceLoader 是 ConfigurableReactiveWebEnvironment 类型,返回匹配if (context.getResourceLoader() instanceof ReactiveWebApplicationContext) {return ConditionOutcome.match(message.foundExactly("ReactiveWebApplicationContext"));}// 返回不匹配return ConditionOutcome.noMatch(message.because("not a reactive web application"));
    }
    
  • <2.3> 处,调用 #isAnyWebApplication(ConditionContext context, boolean required) 方法,代码如下:

    // OnWebApplicationCondition.javaprivate ConditionOutcome isAnyWebApplication(ConditionContext context, boolean required) {ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class, required ? "(required)" : "");// 如果是 Servlet 环境,并且要求 WEB 环境,返回匹配ConditionOutcome servletOutcome = isServletWebApplication(context);if (servletOutcome.isMatch() && required) {return new ConditionOutcome(servletOutcome.isMatch(), message.because(servletOutcome.getMessage()));}// 如果是 Reactive 环境,并且要求 WEB 环境,返回匹配ConditionOutcome reactiveOutcome = isReactiveWebApplication(context);if (reactiveOutcome.isMatch() && required) {return new ConditionOutcome(reactiveOutcome.isMatch(), message.because(reactiveOutcome.getMessage()));}// 根据情况,返回是否匹配return new ConditionOutcome(servletOutcome.isMatch() || reactiveOutcome.isMatch(), // 要求 Servlet 环境 or Reactive 环境,其中有一个匹配message.because(servletOutcome.getMessage()).append("and").append(reactiveOutcome.getMessage()));
    }
    

8.3 OnBeanCondition

org.springframework.boot.autoconfigure.condition.OnBeanCondition ,继承 FilteringSpringBootCondition 抽象类,给 @ConditionalOnBean@ConditionalOnMissingBean@ConditionalOnSingleCandidate 使用的 Condition 实现类。

艿艿就暂时先不写了,因为这个类有点复杂,我想偷懒,哈哈哈。当然,感兴趣的胖友,可以看看 《SpringBoot @ConditionalOnBean、@ConditionalOnMissingBean 注解源码分析与示例》 一文。

666. 彩蛋

本文以为是一篇半天就能解决的博客,结果写了一天半。希望尽可能覆盖到大多数细节。😈

参考和推荐如下文章:

  • dm_vincent
    • 《[Spring Boot] 3. Spring Boot实现自动配置的基础》
    • 《[Spring Boot] 4. Spring Boot实现自动配置的原理》
  • oldflame-Jm 《Spring boot源码分析-Conditional(12)》
  • 一个努力的码农 《spring boot 源码解析19-@Conditional注解详解》
  • 快乐崇拜 《Spring Boot 源码深入分析》

    有木发现,艿艿写的比他详细很多很多。

Yestar123456
发布了24 篇原创文章 · 获赞 0 · 访问量 86
私信关注
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 微信小程序获取元素宽高、窗口高度

    微信小程序获取元素宽高、窗口高度//窗口的高度wx.getSystemInfo({success: (res, rect) => {this.setData({winH: res.windowHeight})}})const query = wx.createSelectorQuery()query.select(.lpsw).boundingClientRect()query.selectViewport().scrollOffset()query.exec…...

    2024/4/27 3:21:24
  2. 精尽 Spring Boot 源码分析 —— ServletWebServerApplicationContext

    精尽 Spring Boot 源码分析 —— ServletWebServerApplicationContext1. 概述在 《精尽 Spring Boot 源码分析 —— SpringApplication》 一文中,我们看到 SpringApplication#createApplicationContext() 方法,根据不同的 Web 应用类型,创建不同的 Spring 容器。代码如下:/…...

    2024/4/27 2:13:37
  3. 精尽 Spring Boot 源码分析 —— ReactiveWebServerApplicationContext

    精尽 Spring Boot 源码分析 —— ReactiveWebServerApplicationContext1. 概述本文接 《精尽 Spring Boot 源码分析 —— ServletWebServerApplicationContext》 一文,我们来分享 ReactiveWebServerApplicationContext 类,它提供 Reactive Web 环境的 Spring 容器。Annotatio…...

    2024/4/27 5:17:46
  4. 达梦数据库准备工作

    安装好Linux操作系统: [root@monitor ~]# cat /etc/redhat-release Red Hat Enterprise Linux Server release 6.10 (Santiago) [root@monitor ~]# 关闭防火墙 和 Selinux: [root@monitor ~]# service iptables stop [root@monitor ~]# chkconfig iptables off [root@monitor…...

    2024/4/20 15:57:00
  5. 精尽 Spring Boot 源码分析 —— 日志系统

    精尽 Spring Boot 源码分析 —— 日志系统1. 概述在使用 Spring Boot 时,默认就已经提供了日志功能,使用 Logback 作为默认的日志框架。本文,我们就来一起研究下,Spring Boot 是如何自动初始化好日志系统的。不了解 Spring Boot 日志功能的胖友,可以先看看 《一起来学 Spr…...

    2024/4/19 12:14:03
  6. 架构设计029 画图 实战四

    pass点赞收藏分享文章举报ailinyingai发布了295 篇原创文章 获赞 6 访问量 4万+他的留言板关注...

    2024/4/27 1:06:42
  7. Java中各种锁机制的介绍

    本文内容 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下: 1.公平锁 / 非公平锁 2.可重入锁 / 不可重入锁 3.独享锁 / 共享锁 4.互斥锁 / 读写锁 5.乐观锁 / 悲观锁 6.分段锁 7.偏向锁 / 轻量级锁 / 重量级锁 8.自旋锁…...

    2024/4/24 6:57:13
  8. 精尽 Spring Boot 源码分析 —— AutoConfigurationMetadataLoader

    精尽 Spring Boot 源码分析 —— AutoConfigurationMetadataLoader1. 概述本文,我们来补充 《精尽 Spring Boot 源码分析 —— 自动配置》 文章,并未详细解析的 AutoConfigurationMetadataLoader 。在 SpringApplication 中,我们可以看到 AutoConfigurationImportSelector.A…...

    2024/4/27 4:24:14
  9. 精尽 Spring Boot 源码分析 —— SpringFactoriesLoader

    精尽 Spring Boot 源码分析 —— SpringFactoriesLoader1. 概述本文,我们来补充 《精尽 Spring Boot 源码分析 —— SpringApplication》 文章,并未详细解析的 SpringFactoriesLoader 。spring.factories 配置文件,我们可以认为是 Spring 自己的一套 SPI 机制的配置文件,在…...

    2024/4/19 21:49:48
  10. 图像处理工程师的基本要求有哪些

    既然学了人工智能这个专业,研究生期间主要方向是机器学习,计算机视觉,图像处理。所以很想了解现在这个领域的就业方向及相关要求。 今天在“增强视觉 | 计算机视觉 增强现实”上看到一则招聘智能图像/视频处理工程师的广告,岗位要求如下: 动手能力强,熟练掌握C/C++/Matla…...

    2024/4/24 5:04:36
  11. GIT commit问题 No errors and 30 warnings found. Would you like to review them?

    点赞收藏分享文章举报胖带鱼发布了21 篇原创文章 获赞 0 访问量 193私信关注...

    2024/4/27 7:13:18
  12. 转载之Java代码优化细节

    可以提高千倍效率的Java代码小技巧来源:www.cnblogs.com/Qian123/p/6046096.html前言代码优化 ,一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小…...

    2024/4/23 4:24:35
  13. Babybluetooth框架分析

    点赞收藏分享文章举报humiaor发布了34 篇原创文章 获赞 8 访问量 4万+私信关注...

    2024/4/24 18:44:03
  14. Bash - style

    dd点赞收藏分享文章举报wojiarucs发布了5 篇原创文章 获赞 0 访问量 22私信关注...

    2024/4/25 6:00:09
  15. JAVA专业术语面试100问

    前言: 面试技巧另外开篇再说,先上面试干货吧。 Redis、 消息队列、 SQL 不要走开,关注后更精彩! 1、面向对象的特点有哪些? 抽象、继承、封装、多态。 2、接口和抽象类有什么联系和区别? 3、重载和重写有什么区别? 4、java有哪些基本数据类型? 5、数组有没有length()方…...

    2024/4/24 22:17:45
  16. TortoiseSVN提示Error: Unable to connect to a repository at URL 的解决方法

    右键->SVN,设置里将缓存清空,一般情况下是可以正常连接了点赞收藏分享文章举报MakeGreatEffort发布了127 篇原创文章 获赞 174 访问量 108万+他的留言板关注...

    2024/4/27 5:16:32
  17. java序列化详解

    遇到这个 Java Serializable 序列化这个接口,我们可能会有如下的问题 a,什么叫序列化和反序列化 b,作用。为啥要实现这个 Serializable 接口,也就是为啥要序列化 c,serialVersionUID 这个的值到底是在怎么设置的,有什么用。有的是1L,有的是一长串数字,迷惑ing。 我刚刚…...

    2024/4/26 11:50:38
  18. Android使用https

    注:所有转载文章只作为学习记录,无其他想法。 前言 HI,欢迎来到裴智飞的《每周一博》。今天是九月第三周,我给大家介绍一下安卓如何使用https。 HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取…...

    2024/4/27 2:23:00
  19. 【转载】基于redis的分布式锁

    前言分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。本篇博客将介绍第二种方式,基于Redis实现分布式锁。虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博…...

    2024/4/26 22:14:50
  20. linux安装oracle时出现乱码已解决

    llinux安装oracle出现乱码解决 原因分析:oracle安装包提供的jdk内缺少中文字体 解决办法:往oracle安装包提供的jdk内拷贝中文字体zysong.ttf 一、操作系统:centos7,oracle版本:11gr2 在解压出oracle安装包后,找到database/stage/Components/oracle.jdk/1.5.0.17.0/1/Data…...

    2024/4/26 4:35:41

最新文章

  1. vue中@click.prevent函数的使用

    一个困扰我很久的问题&#xff0c;后端使用djangoDRF框架开发api&#xff0c;前端使用vueaxioselement plus写的登录功能&#xff0c;后端已经设置了允许跨域&#xff0c;使用postman请求接口正常&#xff0c;但是使用浏览器登录时&#xff0c;后端返回[25/Apr/2024 18:13:13,1…...

    2024/4/27 10:24:16
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 解析大语言模型训练三阶段

    大语言模型的训练过程一般包括3个阶段&#xff1a;预训练&#xff08;Pre-training&#xff09;、SFT&#xff08;有监督的微调&#xff0c;Supervised-Finetuning&#xff09;以及RLHF&#xff08;基于人类反馈的强化学习&#xff0c;Reinforcement Learning from Human Feedb…...

    2024/4/23 6:25:26
  4. 实景三维在数字乡村建设中的重要作用

    随着科技的飞速发展&#xff0c;数字乡村建设已成为推动乡村振兴、实现农村现代化的重要途径。实景三维技术作为数字乡村建设的重要支撑&#xff0c;正逐渐在各个领域发挥着不可或缺的作用。本文将从实景三维技术在数字乡村中的应用场景、优势及未来展望等方面进行探讨&#xf…...

    2024/4/26 0:59:18
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/26 18:09:39
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/26 20:12:18
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/27 4:00:35
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/25 18:39:22
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/25 18:39:22
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/26 21:56:58
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/27 9:01:45
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/26 16:00:35
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/25 18:39:16
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/26 22:01:59
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/25 2:10:52
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/25 18:39:00
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/25 18:38:58
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/27 8:32:30
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57