购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.4 @Conditional条件注解

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

2.4.1 认识条件注解

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


@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(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();

    // 返回ConfigurableListableBeanFactory,可以检查Bean是否已经存在,进一步检查Bean
属性
    @Nullable
    ConfigurableListableBeanFactory 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> getAnnotationAttributes(String annotationName);

    @Nullable
    Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);

    @Nullable
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);

    @Nullable
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}

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

2.4.2 条件注解的衍生注解

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

·@ConditionalOnBean:在容器中有指定Bean的条件下。

·@ConditionalOnClass:在classpath类路径下有指定类的条件下。

·@ConditionalOnCloudPlatform:当指定的云平台处于active状态时。

·@ConditionalOnExpression:基于SpEL表达式的条件判断。

·@ConditionalOnJava:基于JVM版本作为判断条件。

·@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置。

·@ConditionalOnMissingBean:当容器里没有指定Bean的条件时。

·@ConditionalOnMissingClass:当类路径下没有指定类的条件时。

·@ConditionalOnNotWebApplication:在项目不是一个Web项目的条件下。

·@ConditionalOnProperty:在指定的属性有指定值的条件下。

·@ConditionalOnResource:类路径是否有指定的值。

·@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个或者有多个但是指定了首选的Bean时。

·@ConditionalOnWebApplication:在项目是一个Web项目的条件下。

如果仔细观察这些注解的源码,你会发现它们其实都组合了@Conditional注解,不同之处是它们在注解中指定的条件(Condition)不同。下面我们以@ConditionalOnWebApplication为例来对衍生条件注解进行一个简单的分析。


@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {

    // 所需的Web应用类型
    Type type() default Type.ANY;

    // 可选应用类型枚举
    enum Type {
        // 任何类型
        ANY,
        // 基于servlet的Web应用
        SERVLET,
        // 基于reactive的Web应用
        REACTIVE
    }
}

@ConditionalOnWebApplication注解的源代码中组合了@Conditional注解,并且指定了对应的Condition为OnWebApplicationCondition。OnWebApplicationCondition类的结构与前面讲到的OnClassCondition一样,都继承自SpringBootCondition并实现AutoConfigurationImportFilter接口。关于实现AutoConfigurationImportFilter接口的match方法在前面已经讲解过,这里重点讲解关于继承SpringBootCondition和实现Condition接口的功能。

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

图2-6 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(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());
    }
    ...
}

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

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

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

·其他情况,返回匹配。

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


@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootCondition {

    private static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.
web.context.support.GenericWebApplicationContext";
    ...
    // 推断Web应用是否匹配
    private ConditionOutcome isWebApplication(ConditionContext context,
            AnnotatedTypeMetadata metadata, 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 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());
        }
        // 判断BeanFactory是否存在
        if (context.getBeanFactory() != null) {
            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"));
        }
        return ConditionOutcome.noMatch(message.because("not a servlet web-
application"));
    }
    ...
    // 从AnnotatedTypeMetadata中获取type值
    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;
    }
}

首先在isWebApplication方法中进行Web应用类型的推断。这里使用AnnotatedTypeMetadata的getAnnotationAttributes方法获取所有关于@ConditionalOnWebApplication的注解属性。返回值为null说明未配置任何属性,默认为Type.ANY,如果配置属性,会获得type属性对应的值。

如果返回值为Type.SERVLET,调用isServletWebApplication方法来进行判断。该方法的判断有以下条件。

·GenericWebApplicationContext类是否在类路径下。

·容器内是否存在注册名称为session的scope。

·容器的Environment是否为ConfigurableWebEnvironment。

·容器的ResourceLoader是否为WebApplicationContext。

在完成了以上判断之后,得出的最终结果封装为ConditionOutcome对象返回,并在抽象类SpringBootCondition的matches方法中完成判断,返回最终结果。 gYOyxwrSRNnaMosiKwpXCk1qHkaoZ7LPy6jtFr890vDHecZUxqIf+m2SM4yx9YYb

点击中间区域
呼出菜单
上一章
目录
下一章
×