天天看點

深入了解SpringBoot的過濾條件--AutoConfigureautoconfigure Module源碼追蹤

     我們知道在Spring及SpringBoot裡按條件建立Bean的核心是

Condition

接口與

Conditional

注解,其實在SpringBoot裡還有一種AutoConfigure也可以來過濾配置,隻不過使用這種技術,能夠讓SpringBoot更快速的啟動,那麼下面我們就來看一下具體怎麼實作的。

autoconfigure Module

     SpringBoot使用一個Annotation的處理器來收集一些自動裝配的條件,那麼這些條件可以在

META-INF/spring-autoconfigure-metadata.properties

進行配置。SpringBoot會将收集好的

@Configuration

進行一次過濾進而剔除不滿足條件的配置類。

示範示例

     在我們建立好的SpringBoot項目裡添加一個

AutoConfiguration

:

package com.ys.zhshop.member.config;

import com.ys.zhshop.member.service.MemberRegisterService;
import org.springframework.context.annotation.Bean;

public class MemberAutoConfiguration {

    @Bean
    public MemberRegisterService registerService() {
        return new MemberRegisterService();
    }
}
           

     在MemberRegisterService裡的構造函數輸出一段内容看看Spring是否幫我們初始化

     緊接着在

META-INF/spring.factories

裡配置對應的引導:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.ys.zhshop.member.config.MemberAutoConfiguration           

     随後我們需要在META-INF目錄下建立一個

spring-autoconfigure-metadata.properties

檔案,内容如下:

com.ys.zhshop.member.config.MemberAutoConfiguration.ConditionalOnClass=java.lang.Strings           

     格式:

自動配置的類全名.條件=值

     在這裡我們先指定一個類路徑下不存在的Java類,啟動後并沒有相關資訊的輸出,那麼把其值改成

java.land.String

,那麼我們啟動可以發現:

深入了解SpringBoot的過濾條件--AutoConfigureautoconfigure Module源碼追蹤

     在這裡,我們可以在控制台看到構造函數輸出的值,這就說明我們的Bean的的确确被建立了

     下面我貼出一個spring-cloud-netflix-core下的配置,主要來看看這些條件該怎麼寫,大家如果想使用可以參考人家的來配置:

#Mon Jun 18 19:13:37 UTC 2018
org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration.ConditionalOnClass=com.netflix.hystrix.Hystrix,org.springframework.boot.actuate.health.HealthIndicator
org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration=
org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration=
org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration.Configuration=
org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration=
org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration.AutoConfigureAfter=org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration
org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration.Configuration=
org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration.Configuration=
org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration.ConditionalOnClass=com.netflix.hystrix.Hystrix,org.springframework.security.core.context.SecurityContext
           

     根據官網說法,使用這種配置方式可以有效的降低SpringBoot的啟動時間,因為通過這種過濾方式能減少

@Configuration

類的數量,進而降低初始化Bean時的耗時,官網原話描述如下:

Spring Boot uses an annotation processor to collect the conditions on auto-configurations in a metadata file (META-INF/spring-autoconfigure-metadata.properties). If that file is present, it is used to eagerly filter auto-configurations that do not match, which will improve startup time.

源碼追蹤

     我們再次打開自動化配置的核心類

AutoConfigurationImportSelector

,看看它的

selectImport

方法:

@Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);//1
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);//2
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);//3
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return StringUtils.toStringArray(configurations);
    }
           
  1. 在代碼1處建立用于加載spring-autoconfigure-metadata.properties裡的中繼資料,在這裡我們可以看到其指定檔案的位置:
protected static final String PATH = "META-INF/"
            + "spring-autoconfigure-metadata.properties";           

2.在代碼2處主要通過SpringFactoriesLoader加載

spring.factories

裡的

EnableAutoConfiguration

3.根據AutoConfigurationMetaData進行一次過濾:

private List<String> filter(List<String> configurations,
            AutoConfigurationMetadata autoConfigurationMetadata) {
        long startTime = System.nanoTime();
        String[] candidates = StringUtils.toStringArray(configurations);
        boolean[] skip = new boolean[candidates.length];
        boolean skipped = false;
        for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
            invokeAwareMethods(filter);
            boolean[] match = filter.match(candidates, autoConfigurationMetadata);//代碼1
            for (int i = 0; i < match.length; i++) {
                if (!match[i]) {
                    skip[i] = true;
                    skipped = true;
                }
            }
        }
        if (!skipped) {
            return configurations;
        }
        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);
    }           

我們在這裡主要關注代碼1。

OnClassCondition

AutoConfigurationImportFilter

接口的實作類,我這裡貼一下與主題相關的代碼:

@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends SpringBootCondition
        implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {


@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 ConditionOutcome[] getOutcomes(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;
        }
}           

上述代碼雖然多,但最終還是要調用

AutoConfigurationMetadata

的getSet方法,我們繼續追蹤一下這個接口的實作類,它是位于

AutoConfigurationMetadataLoader

的内部類:

/**
     * {@link AutoConfigurationMetadata} implementation backed by a properties file.
     */
    private static class PropertiesAutoConfigurationMetadata
            implements AutoConfigurationMetadata {
    
      //....省略其他代碼

      @Override
        public Set<String> getSet(String className, String key) {
            return getSet(className, key, null);
        }

        @Override
        public Set<String> getSet(String className, String key,
                Set<String> defaultValue) {
            String value = get(className, key);
            return (value != null ? StringUtils.commaDelimitedListToSet(value)
                    : defaultValue);
        }

        @Override
        public String get(String className, String key) {
            return get(className, key, null);
        }

        @Override
        public String get(String className, String key, String defaultValue) {
            String value = this.properties.getProperty(className + "." + key);
            return (value != null ? value : defaultValue);
        }
  }           

最後我們可以發現其get方法還是通過

java.util.Properties

的getProperty方法來擷取的值