Java
spring
spring-boot

Spring BootのAutoConfigureの仕組みを理解する

More than 1 year has passed since last update.

今回は、Spring Bootのメイン機能の一つであるAutoConfigureの仕組みを紹介したいと思います。

Spring Bootを利用すると、簡単なアプリケーションであれば開発者がBean定義を行わなくてもSpringアプリケーションが作成できてしまいます。これはSpring Bootの最大の特徴ですが、Springアプリケーションを構築する上でBean定義そのものが不要になったというわけではありません。

Spring Bootが登場するまで開発者がコツコツBean定義していた部分を、Spring Bootが提供しているAutoConfigureという仕組みが単に肩代わりしてくれているだけなのです!!


前提バージョン


  • Spring Boot 1.4.1.BUILD-SNAPSHOT (投稿時点のスナップショット)

  • Spring Framework 4.3.3.BUILD-SNAPSHOT (投稿時点のスナップショット)


AutoConfigureとは

AutoConfigureは、Springが提供している各種プロジェクト(例えばSpring Framework(Spring MVC), Spring Security, Spring Data, Spring Cloud, Spring Integration, Spring Batchなど)や3rdパーティ製のOSSライブラリなどをSpring上で利用する際に必要となるBean定義を、Spring Bootが自動で行ってくれる仕組みです。「自動で」という表現をしましたが、もうすこし正確に表現すると・・・Spring Bootが予め用意しているAutoConfigure用のBean定義ファイル(コンフィギュレーションクラス)がインポートされ、インポートされたコンフィギュレーションクラスの定義に従ってBean定義が行われます。

また、Spring Bootが用意しているAutoConfigure用のコンフィギュレーションクラスでは、特定の条件をみたす時(例えば、クラスパス上に指定したクラスが存在する時、指定したクラスのBeanが定義されてない時、など・・)のみ、Bean定義が適用されるようになっているのも特徴のひとつと言えるでしょう。

なお、「コンフィギュレーションクラスをインポートする仕組み」や「特定の条件をみたす時だけBean定義を適用する仕組み」自体は、Spring Bootのオリジナル機能ではなく、Spring Frameworkから提供されている機能になります。


AutoConfigureの仕組み

SPRING INITIALIZRで作成したプロジェクトのソースコードを例に、どのようにコンフィギュレーションクラスがインポートされてBean定義が行われるのか見ていきましょう。

ざっくり図で示すと、以下のような感じです。

autoconfigure.png


コンフィギュレーションクラスの指定

SPRING INITIALIZRを使ってプロジェクトを作成すると、以下のようなmainメソッドをもったクラスが作成されます。まずポイントになるのは、SpringApplicationクラスのrunメソッドの第1引数です。第1引数には、DIコンテナを生成する際に使用するコンフィギュレーションクラス(@Configurationが付与されたクラス)を渡します。

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootDemoApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}

}

あれれ・・・第1引数に渡しているSpringBootDemoApplicationクラスには@Configurationが付与されていませんね・・・。その代わりに@SpringBootApplicationが付与されています。では、@SpringBootApplicationのソースコードを見てみましょう。

// ...

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
public @interface SpringBootApplication {
// ...
}

う〜ん、ここにも@Configurationが付与されていませんが、名前的に@SpringBootConfigurationが怪しそうです。

// ...

@Configuration
public @interface SpringBootConfiguration {

}

ふ〜ようやく@Configurationが見つかりました。ここで紹介したように、直接@Configurationが付与しなくても、指定したアノテーション内のどこかに@Configurationが付与されていれば、コンフィギュレーションクラスとして扱われる仕組みになっています :v:


AutoConfigureの有効化

では、AutoConfigureを利用するにはどうすればよいのでしょうか?

AutoConfigureを利用する場合は、runメソッドの引数に渡したコンフィギュレーションクラスに@EnableAutoConfigurationを付与します。なお、@EnableAutoConfiguration@SpringBootApplicationにも付与されているため、@SpringBootApplicationをコンフィギュレーションクラスに付与するだけでAutoConfigureも有効化されます :v:


インポートされるコンフィグレーションクラスは?

AutoConfigureを有効化するとSpring Bootが用意しているAutoConfigure用のコンフィギュレーションクラスがインポートされるわけですが、どのような仕組みでインポートされるコンフィギュレーションクラスが決まるのでしょうか?それを紐解くには、まず@EnableAutoConfigurationのソースコードを見る必要があります。

// ...

@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// ...
}

@EnableAutoConfigurationには、別のコンフィギュレーションクラスをインポートすることを示すアノテーション(@Import)が指定されており、インポートされるコンフィギュレーションクラスはorg.springframework.boot.autoconfigure.EnableAutoConfigurationImportSelectorクラスの実装によって決まります。

EnableAutoConfigurationImportSelectorの実装では、クラスパス上の/META-INF/spring.factoriesよりインポート対象のコンフィギュレーションクラスを取得するようになっており、以下のコンフィギュレーションクラスがインポート対象になっています。


spring-boot-autoconfigure.jar/META-INF/spring.factoriesの抜粋

# ...


# 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.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.hornetq.HornetQAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.velocity.VelocityAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

# ...



適用順序の制御

AutoConfigure用のコンフィギュレーションクラスを適用する順番の制御は、@AutoConfigureAfter@AutoConfigureBefore@AutoConfigureOrderを使用します。他のコンフィギュレーションクラスで行うBean定義との依存関係がある場合は、これらのアノテーションを使用してBean定義の順序性を担保します。


AutoConfigure対象からの除外

特定のコンフィギュレーションクラスをAutoConfigureの適用対象から除外したい場合は、@SpringBootApplicationexclude又はexcludeName属性を使用します。

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

public class SpringBootDemoApplication {
// ...
}

or

プロパティを使用して除外対象を指定することもできます。


src/main/resources/application.properties

spring.autoconfigure.exclude=...



プロパティを使用したAutoConfigureの無効化

あまり使用することはなさそうな気がしますが・・・プロパティを使用してAutoConfigureを無効化することもできます。


src/main/resources/application.properties

spring.boot.enableautoconfiguration=false



条件付きBean定義の仕組み

Spring Bootはデフォルトで数多くのAutoConfigure用のコンフィギュレーションクラスを提供していますが、実際にこれらのクラスは常に適用されるのでしょうか?冒頭ですこし触れましたが、ほとんどのコンフィギュレーションクラスでは、特定の条件をみたす時だけ有効になるようになっています。


@Conditionalを使用した条件付きのBean定義

まず、条件付きBean定義がどのように実現されているか簡単に説明しておきましょう。

Spring Frameworkは、特定の条件を充した時だけBean定義を有効にするための仕組みを提供しており、Bean定義を有効にする条件を指定するためのアノテーションが@org.springframework.context.annotation.Conditionalです。


Conditionalアノテーションのコード抜粋

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
Class<? extends Condition>[] value();
}

@Conditionalは、クラスレベルとメソッドレベルに付与することができ、具体的な条件は、org.springframework.context.annotation.Conditionインタフェースのmatchesメソッドに実装します。


Conditionインタフェースのコード抜粋

public interface Condition {

boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

この仕組みは、Spring Framework本体のコア機能の中で利用されているのですが、どの機能で利用されているかわかりますか?おそらくSpringのヘビーユーザーの方はピンときた方もいるかと思いますが・・・・答えは「プロファイル機能」です。Spring Frameworkは、プロファイル毎にBean定義を行う仕組みをサポートしており、その際に利用するアノテーションが @Profileです。


@Profileを使用した条件付きBean定義

Spring BootのAutoConfigureの話からはすこし離れてしまいますが、条件付きBean定義の基本とも言える@Profileを簡単に紹介しておきましょう。まず、@Profileのソースコードを見てみます。


@Profileのコード抜粋

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class) // ここがポイント
public @interface Profile {
String[] value();
}

@Profileのソースコードをみると、メタアノテーションとして@Conditionalが指定されており、@Profileを付与したクラス又はメソッドは条件付きBean定義の管理下に置かれます。せっかくなので、@Profileが適用される具体的な条件も見ておきましょう。


ProfileConditionクラスのコード抜粋

class ProfileCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}

}


ざっくり説明すると、@Profilevalue属性に指定したプロファイルが、DIコンテナが管理しているアクティブプロファイルに含まれている場合に限り、Bean定義が有効になります。

例えば、商用環境用で利用するBean、システム結合テスト環境で利用するBeanを切り替える必要がある場合は、以下のようなコンフィギュレーションクラスを作成することで対応することができます。

@Configuration

@Profile("production")
public class ProductionConfig {
// ...
}

@Configuration

@Profile("it")
public class IntegrationTestConfig {
// ...
}

なお、DIコンテナに対してアクティブなプロファイルを指定する方法はいろいろありますが、Java VMのシステムプロパティ(-Dspring.profiles.active)を使用して指定するのが一般的です。

java -Dspring.profiles.active=production ...


Spring Bootが提供する@Conditionalの合成アノテーション

Spring Bootは、AutoConfigureの仕組みをサポートする上で必要となる「条件」を表現するために、以下にあげる@Conditionalの合成アノテーションを提供しています。Spring Bootが提供しているAutoConfigure用のコンフィギュレーションクラスでも、これらのアノテーションを駆使してBean定義を行っています。

アノテーション
説明

@ConditionalOnClass
指定したクラスがクラスパス上に存在する場合に適用される。

@ConditionalOnMissingClass
指定したクラスがクラスパス上に存在しない場合に適用される。

@ConditionalOnBean
指定した型や名前のBeanがDIコンテナ上に存在する場合に適用される。

@ConditionalOnMissingBean
指定した型や名前のBeanがDIコンテナ上に存在しない場合に適用される。

@ConditionalOnSingleCandidate
指定した型や名前のBeanがDIコンテナ上に1つ(or @Primaryが付与されたBeanが1つ)存在している場合に適用される。

@ConditionalOnExpression
指定したSpELの評価結果がtrueになる場合に適用される。

@ConditionalOnProperty
プロパティ値が指定した値と一致する場合に適用される。なお、プロパティが未定義の場合に適用対象とするか否かは、アノテーションの属性値で指定することができる。

@ConditionalOnResource
指定したリソース(ファイルなど)が存在する場合に適用される。

@ConditionalOnJndi
指定したJNDI名に対応するリソースが存在する場合に適用される。

@ConditionalOnJava
指定したJavaバージョン上で動作している場合に適用される。

@ConditionalOnWebApplication
Webアプリケーション環境で動作している場合に適用される。

@ConditionalOnNotWebApplication
Webアプリケーション環境で動作していない場合に適用される。

例えば、Spring Bootが提供しているJdbcTemplate用のコンフィギュレーションクラスを見てみると、以下のようなBean定義になっています。


JdbcTemplateAutoConfigurationのコード抜粋

@Configuration

@ConditionalOnClass({ DataSource.class, JdbcTemplate.class }) // (1)
@ConditionalOnSingleCandidate(DataSource.class) // (2)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class JdbcTemplateAutoConfiguration {

private final DataSource dataSource;

public JdbcTemplateAutoConfiguration(DataSource dataSource) {
this.dataSource = dataSource;
}

@Bean
@Primary
@ConditionalOnMissingBean(JdbcOperations.class) // (3)
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(this.dataSource);
}

@Bean
@Primary
@ConditionalOnMissingBean(NamedParameterJdbcOperations.class) // (4)
public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
return new NamedParameterJdbcTemplate(this.dataSource);
}

}


項番
説明

(1)

JdbcTemplateが格納されているspring-jdbcのjarをクラスパスに含めることで有効となる。

(2)
型によるインジェクションが可能なDataSourceを一つだけ定義することで有効となる。 複数のDataSourceを定義する場合は、JdbcTemplateにインジェクションしたいDataSourceのBean定義に@Primaryを付与すればよい。

(3)

JdbcOperationsの実装クラスDIコンテナにいなければBean定義が行われる。 これは、ユーザ定義のBean定義ファイルでJdbcTemplateのBean定義が行われている場合は、AutoConfigureによるBean定義が無効になることを意味している。

(4)

NamedParameterJdbcOperationsの実装クラスDIコンテナにいなければBean定義が行われる。 これは、ユーザ定義のBean定義ファイルでNamedParameterJdbcTemplateのBean定義が行われている場合は、AutoConfigureによるBean定義が無効になることを意味している。

本投稿では扱いませんが、自作の@Conditionalの合成アノテーションを作成することで、より柔軟な条件を設けることもできます。

特定のアプリケーション向けにAutoConfigure用のコンフィギュレーションクラスを作成することは基本的にはないと思いますが、Spring Bootが標準サポートしていないOSSライブラリをSpring上で利用するためのコンフィギュレーションや、社内プロジェクト用に作成した共通ライブラリをSpringで利用するためのコンフィギュレーションなどは、AutoConfigure用のコンフィギュレーションクラスを作成して複数のプロジェクトで共有するのがよいでしょう。

なお、私もコントリビュートの経験があるMyBatisやDoma2では、Spring Bootとの連携用アーティファクトが提供されているので、これらのライブラリを使用する場合は是非利用してみてください。


まとめ

今回は、Spring Bootのメイン機能のひとつであるAutoConfigure(+条件付きBean定義)の仕組みを紹介してみました。AutoConfigureは非常に強力で便利な機能ですが、AutoConfigure自体の仕組みを理解していないと・・・どこでBean定義されているのか?どういう状態のBeanがDIコンテナに登録されているのか?という部分がわからず、思わぬところでハマるケースがあるようです。

私は、思った通りに動かない時は・・・まずSpring Bootが提供しているAutoConfigure用のコンフィギュレーションクラスのソースコードをみるようにしています。

Spring Bootを利用する際は、利用する機能のAutoConfigure用のコンフィギュレーションクラスを一読しておくことをお勧めします。特にアーキテクトの方は必読といっても過言ではないと思います :sweat_smile:


参考サイト/参考書籍