[Spring Boot] 4. Spring Boot自動構成を実現する原理

24032 ワード

エントリ注記クラス@EnableAutoConfiguration
@SpringBootApplication注記には、自動構成のエントリ注記が含まれています.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  // ...
}
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  // ...
}

この注釈のJavadocの内容はまだ少なくなくて、すべて文章の中に貼らないで、要約します:
  • アプリケーションベースのクラスパスを自動的に構成し、Beans
  • を定義します.
  • @SpringBootApplication注記を使用すると、自動構成
  • が自動的に有効になります.
  • は、注釈のexcludeName属性を設定することによって、またはspringによって行うことができる.autoconfigure.Exclude構成項目自動構成を必要としない項目を指定する
  • 自動構成の発生タイミングは、ユーザ定義Beansが登録する後の
  • である.
  • @SpringBootApplicationとともに使用されていない場合は、root packageのクラスに@EnableAutoConfiguration注記を置くと、すべてのサブpackagesのクラスが
  • で検索できるようになります.
  • 自動構成クラスは通常のSpring@Configurationクラスであり、SpringFactoriesLoaderメカニズムによりロードが完了し、実装上は通常@Conditional(@ConditionalOnClassまたは@ConditionalOnMissingBean)
  • を使用する
    @AutoConfigurationPackage
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    
    }

    この注釈の役割は、別の構成クラスを導入することです.Registrar.
    /**
     * ImportBeanDefinitionRegistrar      Config   base package
     */
    @Order(Ordered.HIGHEST_PRECEDENCE)
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
            register(registry, new PackageImport(metadata).getPackageName());
        }
    
        @Override
        public Set determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImport(metadata));
        }
    
    }

    この注釈実装の機能はすでに最下位になっており、上記のregisterメソッドが呼び出されるかどうかをデバッグしてみましょう.
    呼び出しパラメータのpackageName配列には、com.example.demo、つまりプロジェクトのroot package名です.
    呼び出しスタックから見るとregisterメソッドを呼び出す時間はコンテナリフレッシュ中:
    refresh->i n v o keBeanFactoryPostProcessors->i n v o keBeanDefinitionRegistryPostProcessors->postProcessBeanDefinitionRegistry->p r o s s C o f i g BeanDefinitions(構成Beanの定義の処理開始)->loadBeanDefinitions->loadBeanDefinitionsForConfigurationClass(構成ClassでのBean定義の読み出し)->loadBeanDefinitionsFromRegistrars(ここでは上のregisterメソッドに入る準備を始めます)->registerBeanDefinitions(上記メソッド)
    この過程はすでに複雑になっているので,現在はしばらく深く研究していない.その機能は簡単に言えば、アプリケーションのroot packageをSpringコンテナに登録し、後で使用することです.
    対照的に、以下で説明するいくつかのタイプこそ、自動構成を実現する鍵です.
    @Import(EnableAutoConfigurationImportSelector.class)
    @E n a b l e AutoConfiguration注記のもう1つの役割は、E n a b l e AutoConfigurationImportSelectorを導入することです.
    クラス図は次のとおりです.
    いくつかのAwareクラスインタフェースを実装する以外に、最も重要なのはDeferredImportSelector(ImportSelectorから継承)インタフェースを実装することであることがわかります.
    まず、ImportSelectorおよびDeferredImportSelectorインタフェースの定義を見てみましょう.
    public interface ImportSelector {
    
        /**
         *       Configuration  AnnotationMetadata                
         */
        String[] selectImports(AnnotationMetadata importingClassMetadata);
    
    }

    このインタフェースのJavadocは長いので、ポイントを拾って説明します.
  • 主な機能はselectImports法によって実現され、導入するクラス名
  • をフィルタリングするために使用される.
  • インプリメンテーションImportSelectorのクラスは、selectImportsメソッドの前に呼び出される一連のAwareインタフェースを実装することもできます(この点は、上記のクラス図によっても証明されていますが、E n a b l e AutoConfigurationImportSelectorは確かに4つのAwareタイプのインタフェースを実装しています)
  • .
  • ImportSelectorの実装は通常の@Importと処理方式で一致するが、全ての@Configurationクラスが処理された後に導入フィルタリング(詳細は後述するDeferredImportSelector)
  • を行うことができる.
    public interface DeferredImportSelector extends ImportSelector {
    
    }

    このインタフェースはタグインタフェースであり、それ自体はメソッドを定義していません.では、このインタフェースの意味は何でしょうか.
  • ImportSelectorインタフェースのバリエーションであり、すべての@Configurationが処理された後に実行されます.フィルタリングが必要な導入タイプが@Conditional注釈を備える場合に非常に有用である
  • .
  • インプリメンテーションクラスは、Orderedインタフェースを実装して、複数のDeferredImportSelectorの優先度を定義することもできる(同様に、E n a b l e A t o C o f i gurationImportSelectorもOrderedインタフェースを実装する)
  • .
    この2つのインタフェースの意味を明確にし、次にどのように実現されているかを見てみましょう.
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        try {
          // Step1:       
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            // Step2:             
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            // Step3:         
            List configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            // Step4:   
            configurations = removeDuplicates(configurations);
            // Step5:   
            configurations = sort(configurations, autoConfigurationMetadata);
            // Step6:       exclude        
            Set exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            // Step7:     
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return configurations.toArray(new String[configurations.size()]);
        }
        catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }

    明らかに、コアは上記の手順3にあります.
    protected List getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        List configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                "No auto configuration classes found in META-INF/spring.factories. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

    SpringFactoriesLoaderに委任されたloadFactoryNameメソッドを実装します.
    //    factoryClass:org.springframework.boot.autoconfigure.EnableAutoConfiguration
    public static List loadFactoryNames(Class> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List result = new ArrayList();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                    "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
    
    //     
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    このコードの意図は明確で,Spring Bootの起動過程を最初の文章で議論したときにすでに接触していた.クラスパスからMETA-INF/springという名前のものがすべて取得されます.factoriesのプロファイルをfactoryClassの名前で指定します.では、META-INF/springを探してみましょう.factoriesプロファイルを見てみましょう.
    META-INF/spring.factories
    たとえばspring-boot-autoconfigureパッケージ:
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
    org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
    org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
    org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
    #      

    非常に多くの自動構成候補を列挙し、AOP関連のAopAutoConfigurationを選んでみましょう.
    //      spring.aop.auto=false,  AOP     
    //      @EnableAspectJAutoProxy        
    //     JdkDynamicAutoProxyConfiguration,     spring.aop.proxy-target-class=true,    CglibAutoProxyConfiguration
    @Configuration
    @ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
    @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
    public class AopAutoConfiguration {
    
        @Configuration
        @EnableAspectJAutoProxy(proxyTargetClass = false)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = true)
        public static class JdkDynamicAutoProxyConfiguration {
    
        }
    
        @Configuration
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = false)
        public static class CglibAutoProxyConfiguration {
    
        }
    
    }

    この自動構成クラスの役割は、構成項目が存在するかどうかを判断することです.
    spring.aop.proxy-target-class=true

    値がtrueの場合、CGLIBバイトコード操作に基づく動的エージェントスキームが使用され、そうでなければJDKが持つ動的エージェントメカニズムが使用される.
    この構成クラスでは、2つの新しい注釈が使用されます.
  • @ConditionalOnClass
  • @ConditionalOnProperty

  • この2つの注釈の名前から、それらの機能を推測することができます.
    @ConditionalOnClass
    クラスパスに指定したクラスがある場合、条件を満たします.
    @ConditionalOnProperty
    設定に指定した属性がある場合、条件を満たします.
    実はこの2つの注釈のほかに、orgに似ているものがいくつかあります.springframework.boot.autoconfigure.conditionというパッケージの下で、実装について具体的に説明する前に、@Conditionalに対するSpring Bootの拡張を見てみましょう.=
    Spring Boot@Conditionalの拡張
    Spring Bootは、Conditionインタフェースを実装する抽象クラスSpringBootConditionを提供します.
    このクラスの主な役割は、診断用のログを印刷し、ユーザーにどのタイプが自動的に構成されているかを伝えることです.
    Conditionインタフェースを実装する方法:
    @Override
    public final boolean matches(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        String classOrMethodName = getClassOrMethodName(metadata);
        try {
            ConditionOutcome outcome = getMatchOutcome(context, metadata);
            logOutcome(classOrMethodName, outcome);
            recordEvaluation(context, classOrMethodName, outcome);
            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);
        }
    }
    
    /**
     * Determine the outcome of the match along with suitable log output.
     * @param context the condition context
     * @param metadata the annotation metadata
     * @return the condition outcome
     */
    public abstract ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata);

    SpringBootConditionは、内部のマッチングの詳細を抽象的なメソッドgetMatchOutcomeとして定義し、そのサブクラスに渡して完了する基本的な実装を提供しています.
    また、布団類が使用される可能性のある2つの方法も提供されています.
    /**
     *      conditions        ,     true
     * @param context the context
     * @param metadata the annotation meta-data
     * @param conditions conditions to test
     * @return {@code true} if any condition matches.
     */
    protected final boolean anyMatches(ConditionContext context,
            AnnotatedTypeMetadata metadata, Condition... conditions) {
        for (Condition condition : conditions) {
            if (matches(context, metadata, condition)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     *      condition    
     * @param context the context
     * @param metadata the annotation meta-data
     * @param condition condition to test
     * @return {@code true} if the condition matches.
     */
    protected final boolean matches(ConditionContext context,
            AnnotatedTypeMetadata metadata, Condition condition) {
        if (condition instanceof SpringBootCondition) {
            return ((SpringBootCondition) condition).getMatchOutcome(context, metadata)
                    .isMatch();
        }
        return condition.matches(context, metadata);
    }

    org.springframework.boot.autoconfigure.conditionパッケージ
    すでに出会った@ConditionalOnClassと@ConditionalOnPropertyのほか、このパッケージには多くの条件実装クラスが定義されています.以下にいくつか簡単に列挙します.
    @ConditionalOnExpression-SpELによる条件判断
    /**
     * Configuration annotation for a conditional element that depends on the value of a SpEL
     * expression.
     *
     * @author Dave Syer
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Documented
    @Conditional(OnExpressionCondition.class)
    public @interface ConditionalOnExpression {
    
        /**
         * The SpEL expression to evaluate. Expression should return {@code true} if the
         * condition passes or {@code false} if it fails.
         * @return the SpEL expression
         */
        String value() default "true";

    次に、対応するインプリメンテーションクラスはOnExpressionConditionであり、SpringBootConditionから継承されます.
    @ConditionalOnMissingClass-クラスが存在しないこととclasspathの条件に基づいて判断
    この条件の実装は@ConditionalOnClass条件とは正反対である.
    Spring Bootによって提供されるすべての条件注記を次に示します.
  • @ConditionalOnBean
  • @ConditionalOnClass
  • @ConditionalOnCloudPlatform
  • @ConditionalOnExpression
  • @ConditionalOnJava
  • @ConditionalOnJndi
  • @ConditionalOnMissingBean
  • @ConditionalOnMissingClass
  • @ConditionalOnNotWebApplication
  • @ConditionalOnProperty
  • @ConditionalOnResource
  • @ConditionalOnSingleCandidate
  • @ConditionalOnWebApplication

  • 一般的なモードは、SpringBootConditionから継承された特定の実装クラスに対応する条件注記です.