今回は、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定義が行われるのか見ていきましょう。
ざっくり図で示すと、以下のような感じです。
コンフィギュレーションクラスの指定
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
が付与されていれば、コンフィギュレーションクラスとして扱われる仕組みになっています
AutoConfigureの有効化
では、AutoConfigureを利用するにはどうすればよいのでしょうか?
AutoConfigureを利用する場合は、run
メソッドの引数に渡したコンフィギュレーションクラスに@EnableAutoConfiguration
を付与します。なお、@EnableAutoConfiguration
は@SpringBootApplication
にも付与されているため、@SpringBootApplication
をコンフィギュレーションクラスに付与するだけでAutoConfigureも有効化されます
インポートされるコンフィグレーションクラスは?
AutoConfigureを有効化するとSpring Bootが用意しているAutoConfigure用のコンフィギュレーションクラスがインポートされるわけですが、どのような仕組みでインポートされるコンフィギュレーションクラスが決まるのでしょうか?それを紐解くには、まず@EnableAutoConfiguration
のソースコードを見る必要があります。
// ...
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// ...
}
@EnableAutoConfiguration
には、別のコンフィギュレーションクラスをインポートすることを示すアノテーション(@Import
)が指定されており、インポートされるコンフィギュレーションクラスはorg.springframework.boot.autoconfigure.EnableAutoConfigurationImportSelector
クラスの実装によって決まります。
EnableAutoConfigurationImportSelector
の実装では、クラスパス上の/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の適用対象から除外したい場合は、@SpringBootApplication
のexclude
又はexcludeName
属性を使用します。
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SpringBootDemoApplication {
// ...
}
or
プロパティを使用して除外対象を指定することもできます。
spring.autoconfigure.exclude=...
プロパティを使用したAutoConfigureの無効化
あまり使用することはなさそうな気がしますが・・・プロパティを使用してAutoConfigureを無効化することもできます。
spring.boot.enableautoconfiguration=false
条件付きBean定義の仕組み
Spring Bootはデフォルトで数多くのAutoConfigure用のコンフィギュレーションクラスを提供していますが、実際にこれらのクラスは常に適用されるのでしょうか?冒頭ですこし触れましたが、ほとんどのコンフィギュレーションクラスでは、特定の条件をみたす時だけ有効になるようになっています。
@Conditional
を使用した条件付きのBean定義
まず、条件付きBean定義がどのように実現されているか簡単に説明しておきましょう。
Spring Frameworkは、特定の条件を充した時だけBean定義を有効にするための仕組みを提供しており、Bean定義を有効にする条件を指定するためのアノテーションが@org.springframework.context.annotation.Conditional
です。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
Class<? extends Condition>[] value();
}
@Conditional
は、クラスレベルとメソッドレベルに付与することができ、具体的な条件は、org.springframework.context.annotation.Condition
インタフェースのmatches
メソッドに実装します。
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
この仕組みは、Spring Framework本体のコア機能の中で利用されているのですが、どの機能で利用されているかわかりますか?おそらくSpringのヘビーユーザーの方はピンときた方もいるかと思いますが・・・・答えは「プロファイル機能」です。Spring Frameworkは、プロファイル毎にBean定義を行う仕組みをサポートしており、その際に利用するアノテーションが @Profile
です。
@Profile
を使用した条件付きBean定義
Spring BootのAutoConfigureの話からはすこし離れてしまいますが、条件付きBean定義の基本とも言える@Profile
を簡単に紹介しておきましょう。まず、@Profile
のソースコードを見てみます。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class) // ここがポイント
public @interface Profile {
String[] value();
}
@Profile
のソースコードをみると、メタアノテーションとして@Conditional
が指定されており、@Profile
を付与したクラス又はメソッドは条件付きBean定義の管理下に置かれます。せっかくなので、@Profile
が適用される具体的な条件も見ておきましょう。
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;
}
}
ざっくり説明すると、@Profile
のvalue
属性に指定したプロファイルが、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定義になっています。
@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用のコンフィギュレーションクラスを一読しておくことをお勧めします。特にアーキテクトの方は必読といっても過言ではないと思います