SpringBoot之条件注解


title: SpringBoot之条件注解 tags:

  • SpringBoot
  • Conditional
  • 条件注解
  • ConditionalOnClass categories: springboot date: 2017-11-27 23:28:37

背景

之前写过关于Spring和Maven的profile的区别 maven profile VS spring profile

我们可以通过上述的profile来完成各种各样的不同配置

Spring在新版本也提供了更加细粒度的方案【Condition】

举例

SpringBoot通过各种jar的引入自动完成了相关bean的初始化和配置 除了依赖AutoConfigSpringBoot之自动配置之外还依赖了强大的条件注解

我们可以看到如下注解【如下这些注解便可完成按照条件进行注入】

    /**
     * Indicates that a component is only eligible for registration when all
     * {@linkplain #value specified conditions} match.
     *
     * <p>A <em>condition</em> is any state that can be determined programmatically
     * before the bean definition is due to be registered (see {@link Condition} for details).
     *
     * <p>The {@code @Conditional} annotation may be used in any of the following ways:
     * <ul>
     * <li>as a type-level annotation on any class directly or indirectly annotated with
     * {@code @Component}, including {@link Configuration @Configuration} classes</li>
     * <li>as a meta-annotation, for the purpose of composing custom stereotype
     * annotations</li>
     * <li>as a method-level annotation on any {@link Bean @Bean} method</li>
     * </ul>
     *
     * <p>If a {@code @Configuration} class is marked with {@code @Conditional},
     * all of the {@code @Bean} methods, {@link Import @Import} annotations, and
     * {@link ComponentScan @ComponentScan} annotations associated with that
     * class will be subject to the conditions.
     *
     * <p><strong>NOTE</strong>: Inheritance of {@code @Conditional} annotations
     * is not supported; any conditions from superclasses or from overridden
     * methods will not be considered. In order to enforce these semantics,
     * {@code @Conditional} itself is not declared as
     * {@link java.lang.annotation.Inherited @Inherited}; furthermore, any
     * custom <em>composed annotation</em> that is meta-annotated with
     * {@code @Conditional} must not be declared as {@code @Inherited}.
     *
     * @author Phillip Webb
     * @author Sam Brannen
     * @since 4.0
     * @see Condition
     */
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Conditional {
     
       /**
        * All {@link Condition}s that must {@linkplain Condition#matches match}
        * in order for the component to be registered.
        */
       Class<? extends Condition>[] value();
     
    }
复制代码

  • ConditionalOnBean: 当且仅当指定的bean classes and/or bean names在当前容器中,才创建标记上该注解的类的实例
  • ConditionalOnMissingBean: 当且仅当指定的bean classes and/or bean names不存在当前容器中,才创建标记上该注解的类的实例,有指定忽略ignored的参数存在,可以忽略Class、Type等
  • ConditionalOnClass:当且仅当ClassPath存在指定的Class时,才创建标记上该注解的类的实例
  • ConditionalOnMissingClass:当且仅当ClassPath不存在指定的Class时,创建标记上该注解的类的实例
  • ConditionalOnProperty:当且仅当Application.properties存在指定的配置项时,创建标记上了该注解的类的实例
  • ConditionalOnJava:指定JDK的版本
  • ConditionalOnExpression:表达式用${..}=false等来表示
  • ConditionalOnJndi:JNDI存在该项时创建
  • ConditionalOnResource:在classpath下存在指定的resource时创建
  • ConditionalOnSingleCandidate:Conditional
    that only matches when the specified bean class is already contained in the BeanFactory
    and a single candidate can be determined.The condition will also match if multiple matching bean instances are already contained in the BeanFactory
    but a primary candidate has been defined; essentially, the condition match if auto-wiring a bean with the defined type will succeed.
  • ConditionalOnWebApplication:在web环境下创建

我们以最基本的ConditionalOnClass来做解释


    /**
     * {@link Conditional} that only matches when the specified classes are on the classpath.
     *
     * @author Phillip Webb
     */
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(OnClassCondition.class)
    public @interface ConditionalOnClass {
     
       /**
        * The classes that must be present. Since this annotation is parsed by loading class
        * bytecode, it is safe to specify classes here that may ultimately not be on the
        * classpath, only if this annotation is directly on the affected component and
        * <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
        * use this annotation as a meta-annotation, only use the {@link #name} attribute.
        * @return the classes that must be present
        */
       Class<?>[] value() default {};
     
       /**
        * The classes names that must be present.
        * @return the class names that must be present.
        */
       String[] name() default {};
     
    }
复制代码

该条件的判断逻辑如下

    /*
     * Copyright 2012-2017 the original author or authors.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
     
    package org.springframework.boot.autoconfigure.condition;
     
    import java.security.AccessControlException;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.List;
    import java.util.Set;
     
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.BeanClassLoaderAware;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.BeanFactoryAware;
    import org.springframework.beans.factory.config.ConfigurableBeanFactory;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter;
    import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
    import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    import org.springframework.util.ClassUtils;
    import org.springframework.util.MultiValueMap;
     
    /**
     * {@link Condition} and {@link AutoConfigurationImportFilter} that checks for the
     * presence or absence of specific classes.
     *
     * @author Phillip Webb
     * @see ConditionalOnClass
     * @see ConditionalOnMissingClass
     */
    @Order(Ordered.HIGHEST_PRECEDENCE)
    class OnClassCondition extends SpringBootCondition
          implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
     
       private BeanFactory beanFactory;
     
       private ClassLoader beanClassLoader;
     
       @Override
       public boolean[] match(String[] autoConfigurationClasses,
             AutoConfigurationMetadata autoConfigurationMetadata) {
          ConditionEvaluationReport report = getConditionEvaluationReport();
          ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses,
                autoConfigurationMetadata);
          boolean[] match = new boolean[outcomes.length];
          for (int i = 0; i < outcomes.length; i++) {
             match[i] = (outcomes[i] == null || outcomes[i].isMatch());
             if (!match[i] && outcomes[i] != null) {
                logOutcome(autoConfigurationClasses[i], outcomes[i]);
                if (report != null) {
                   report.recordConditionEvaluation(autoConfigurationClasses[i], this,
                         outcomes[i]);
                }
             }
          }
          return match;
       }
     
       private ConditionEvaluationReport getConditionEvaluationReport() {
          if (this.beanFactory != null
                && this.beanFactory instanceof ConfigurableBeanFactory) {
             return ConditionEvaluationReport
                   .get((ConfigurableListableBeanFactory) this.beanFactory);
          }
          return null;
       }
     
       private 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
          int split = autoConfigurationClasses.length / 2;
          OutcomesResolver firstHalfResolver = createOutcomesResolver(
                autoConfigurationClasses, 0, split, autoConfigurationMetadata);
          OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
                autoConfigurationClasses, split, autoConfigurationClasses.length,
                autoConfigurationMetadata, this.beanClassLoader);
          ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
          ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
          ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
          System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
          System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
          return outcomes;
       }
     
       private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses,
             int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
          OutcomesResolver outcomesResolver = new StandardOutcomesResolver(
                autoConfigurationClasses, start, end, autoConfigurationMetadata,
                this.beanClassLoader);
          try {
             return new ThreadedOutcomesResolver(outcomesResolver);
          }
          catch (AccessControlException ex) {
             return outcomesResolver;
          }
       }
     
       @Override
       public ConditionOutcome getMatchOutcome(ConditionContext context,
             AnnotatedTypeMetadata metadata) {
          ClassLoader classLoader = context.getClassLoader();
          ConditionMessage matchMessage = ConditionMessage.empty();
          List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
          if (onClasses != null) {
             List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader);
             if (!missing.isEmpty()) {
                return ConditionOutcome
                      .noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
                            .didNotFind("required class", "required classes")
                            .items(Style.QUOTE, missing));
             }
             matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
                   .found("required class", "required classes").items(Style.QUOTE,
                         getMatches(onClasses, MatchType.PRESENT, classLoader));
          }
          List<String> onMissingClasses = getCandidates(metadata,
                ConditionalOnMissingClass.class);
          if (onMissingClasses != null) {
             List<String> present = getMatches(onMissingClasses, MatchType.PRESENT,
                   classLoader);
             if (!present.isEmpty()) {
                return ConditionOutcome.noMatch(
                      ConditionMessage.forCondition(ConditionalOnMissingClass.class)
                            .found("unwanted class", "unwanted classes")
                            .items(Style.QUOTE, present));
             }
             matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
                   .didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE,
                         getMatches(onMissingClasses, MatchType.MISSING, classLoader));
          }
          return ConditionOutcome.match(matchMessage);
       }
     
       private List<String> getCandidates(AnnotatedTypeMetadata metadata,
             Class<?> annotationType) {
          MultiValueMap<String, Object> attributes = metadata
                .getAllAnnotationAttributes(annotationType.getName(), true);
          List<String> candidates = new ArrayList<String>();
          if (attributes == null) {
             return Collections.emptyList();
          }
          addAll(candidates, attributes.get("value"));
          addAll(candidates, attributes.get("name"));
          return candidates;
       }
     
       private void addAll(List<String> list, List<Object> itemsToAdd) {
          if (itemsToAdd != null) {
             for (Object item : itemsToAdd) {
                Collections.addAll(list, (String[]) item);
             }
          }
       }
     
       private List<String> getMatches(Collection<String> candidates, MatchType matchType,
             ClassLoader classLoader) {
          List<String> matches = new ArrayList<String>(candidates.size());
          for (String candidate : candidates) {
             if (matchType.matches(candidate, classLoader)) {
                matches.add(candidate);
             }
          }
          return matches;
       }
     
       @Override
       public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
          this.beanFactory = beanFactory;
       }
     
       @Override
       public void setBeanClassLoader(ClassLoader classLoader) {
          this.beanClassLoader = classLoader;
       }
     
       private enum MatchType {
     
          PRESENT {
     
             @Override
             public boolean matches(String className, ClassLoader classLoader) {
                return isPresent(className, classLoader);
             }
     
          },
     
          MISSING {
     
             @Override
             public boolean matches(String className, ClassLoader classLoader) {
                return !isPresent(className, classLoader);
             }
     
          };
     
          private 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);
          }
     
          public abstract boolean matches(String className, ClassLoader classLoader);
     
       }
     
       private interface OutcomesResolver {
     
          ConditionOutcome[] resolveOutcomes();
     
       }
     
       private static final class ThreadedOutcomesResolver implements OutcomesResolver {
     
          private final Thread thread;
     
          private volatile ConditionOutcome[] outcomes;
     
          private ThreadedOutcomesResolver(final OutcomesResolver outcomesResolver) {
             this.thread = new Thread(new Runnable() {
     
                @Override
                public void run() {
                   ThreadedOutcomesResolver.this.outcomes = outcomesResolver
                         .resolveOutcomes();
                }
     
             });
             this.thread.start();
          }
     
          @Override
          public ConditionOutcome[] resolveOutcomes() {
             try {
                this.thread.join();
             }
             catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
             }
             return this.outcomes;
          }
     
       }
     
       private final class StandardOutcomesResolver implements OutcomesResolver {
     
          private final String[] autoConfigurationClasses;
     
          private final int start;
     
          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;
          }
     
          @Override
          public ConditionOutcome[] resolveOutcomes() {
             return getOutcomes(this.autoConfigurationClasses, this.start, this.end,
                   this.autoConfigurationMetadata);
          }
     
          private ConditionOutcome[] getOutcomes(final String[] autoConfigurationClasses,
                int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
             ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
             for (int i = start; i < end; i++) {
                String autoConfigurationClass = autoConfigurationClasses[i];
                Set<String> candidates = autoConfigurationMetadata
                      .getSet(autoConfigurationClass, "ConditionalOnClass");
                if (candidates != null) {
                   outcomes[i - start] = getOutcome(candidates);
                }
             }
             return outcomes;
          }
     
          private ConditionOutcome getOutcome(Set<String> candidates) {
             try {
                List<String> missing = getMatches(candidates, MatchType.MISSING,
                      this.beanClassLoader);
                if (!missing.isEmpty()) {
                   return ConditionOutcome.noMatch(
                         ConditionMessage.forCondition(ConditionalOnClass.class)
                               .didNotFind("required class", "required classes")
                               .items(Style.QUOTE, missing));
                }
             }
             catch (Exception ex) {
                // We'll get another chance later
             }
             return null;
          }
     
       }
     
    }
复制代码

其中getMatchOutcome方法会返回对应的条件 比如是ConditionalOnClass

    List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
    if (onClasses != null) {
       List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader);
       if (!missing.isEmpty()) {
          return ConditionOutcome
                .noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
                      .didNotFind("required class", "required classes")
                      .items(Style.QUOTE, missing));
       }
       matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
             .found("required class", "required classes").items(Style.QUOTE,
                   getMatches(onClasses, MatchType.PRESENT, classLoader));
    }
复制代码

那么会根据注解在对应的类,如果找到miss的class那么我们直接返回noMatch 其他同样道理

典型的实现如下比如

    @Configuration
    @ConditionalOnClass({ EhCacheCache.class, Ehcache.class, StatisticsGateway.class })
    static class EhCacheCacheStatisticsProviderConfiguration {
     
       @Bean
       public EhCacheStatisticsProvider ehCacheCacheStatisticsProvider() {
          return new EhCacheStatisticsProvider();
       }
     
    }
复制代码

这样就可以在不同的条件下返回不同的Bean作为实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值