[Spring Boot] 4. Spring Boot自動構成を実現する原理
エントリ注記クラス@EnableAutoConfiguration
@SpringBootApplication注記には、自動構成のエントリ注記が含まれています.
この注釈のJavadocの内容はまだ少なくなくて、すべて文章の中に貼らないで、要約します:アプリケーションベースのクラスパスを自動的に構成し、Beans を定義します.@SpringBootApplication注記を使用すると、自動構成 が自動的に有効になります.は、注釈のexcludeName属性を設定することによって、またはspringによって行うことができる.autoconfigure.Exclude構成項目自動構成を必要としない項目を指定する 自動構成の発生タイミングは、ユーザ定義Beansが登録する後の である.@SpringBootApplicationとともに使用されていない場合は、root packageのクラスに@EnableAutoConfiguration注記を置くと、すべてのサブpackagesのクラスが で検索できるようになります.自動構成クラスは通常のSpring@Configurationクラスであり、SpringFactoriesLoaderメカニズムによりロードが完了し、実装上は通常@Conditional(@ConditionalOnClassまたは@ConditionalOnMissingBean) を使用する
@AutoConfigurationPackage
この注釈の役割は、別の構成クラスを導入することです.Registrar.
この注釈実装の機能はすでに最下位になっており、上記の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インタフェースの定義を見てみましょう.
このインタフェースのJavadocは長いので、ポイントを拾って説明します.主な機能はselectImports法によって実現され、導入するクラス名 をフィルタリングするために使用される.インプリメンテーションImportSelectorのクラスは、selectImportsメソッドの前に呼び出される一連のAwareインタフェースを実装することもできます(この点は、上記のクラス図によっても証明されていますが、E n a b l e AutoConfigurationImportSelectorは確かに4つのAwareタイプのインタフェースを実装しています) . ImportSelectorの実装は通常の@Importと処理方式で一致するが、全ての@Configurationクラスが処理された後に導入フィルタリング(詳細は後述するDeferredImportSelector) を行うことができる.
このインタフェースはタグインタフェースであり、それ自体はメソッドを定義していません.では、このインタフェースの意味は何でしょうか. ImportSelectorインタフェースのバリエーションであり、すべての@Configurationが処理された後に実行されます.フィルタリングが必要な導入タイプが@Conditional注釈を備える場合に非常に有用である .インプリメンテーションクラスは、Orderedインタフェースを実装して、複数のDeferredImportSelectorの優先度を定義することもできる(同様に、E n a b l e A t o C o f i gurationImportSelectorもOrderedインタフェースを実装する) .
この2つのインタフェースの意味を明確にし、次にどのように実現されているかを見てみましょう.
明らかに、コアは上記の手順3にあります.
SpringFactoriesLoaderに委任されたloadFactoryNameメソッドを実装します.
このコードの意図は明確で,Spring Bootの起動過程を最初の文章で議論したときにすでに接触していた.クラスパスからMETA-INF/springという名前のものがすべて取得されます.factoriesのプロファイルをfactoryClassの名前で指定します.では、META-INF/springを探してみましょう.factoriesプロファイルを見てみましょう.
META-INF/spring.factories
たとえばspring-boot-autoconfigureパッケージ:
非常に多くの自動構成候補を列挙し、AOP関連のAopAutoConfigurationを選んでみましょう.
この自動構成クラスの役割は、構成項目が存在するかどうかを判断することです.
値が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インタフェースを実装する方法:
SpringBootConditionは、内部のマッチングの詳細を抽象的なメソッドgetMatchOutcomeとして定義し、そのサブクラスに渡して完了する基本的な実装を提供しています.
また、布団類が使用される可能性のある2つの方法も提供されています.
org.springframework.boot.autoconfigure.conditionパッケージ
すでに出会った@ConditionalOnClassと@ConditionalOnPropertyのほか、このパッケージには多くの条件実装クラスが定義されています.以下にいくつか簡単に列挙します.
@ConditionalOnExpression-SpELによる条件判断
次に、対応するインプリメンテーションクラスはOnExpressionConditionであり、SpringBootConditionから継承されます.
@ConditionalOnMissingClass-クラスが存在しないこととclasspathの条件に基づいて判断
この条件の実装は@ConditionalOnClass条件とは正反対である.
Spring Bootによって提供されるすべての条件注記を次に示します. @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication
一般的なモードは、SpringBootConditionから継承された特定の実装クラスに対応する条件注記です.
@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の内容はまだ少なくなくて、すべて文章の中に貼らないで、要約します:
@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
この注釈実装の機能はすでに最下位になっており、上記の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は長いので、ポイントを拾って説明します.
public interface DeferredImportSelector extends ImportSelector {
}
このインタフェースはタグインタフェースであり、それ自体はメソッドを定義していません.では、このインタフェースの意味は何でしょうか.
この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つの新しい注釈が使用されます.
この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によって提供されるすべての条件注記を次に示します.
一般的なモードは、SpringBootConditionから継承された特定の実装クラスに対応する条件注記です.