理解Spring Boot的AutoConfigure机制
我想介绍一下这次的主题,即Spring Boot的一个主要功能AutoConfigure的机制。
使用Spring Boot,即使是简单的应用程序,开发者也可以创建Spring应用程序而无需进行Bean定义。这是Spring Boot的最大特点,但并不意味着在构建Spring应用程序时Bean定义本身是不必要的。
在Spring Boot出现之前,开发者需要辛苦地进行Bean定义,而现在Spring Boot提供的AutoConfigure机制只是代替了这部分工作而已!
前提的版本 de
-
- Spring Boot 1.4.1.BUILD-SNAPSHOT (投稿時点のスナップショット)
- Spring Framework 4.3.3.BUILD-SNAPSHOT (投稿時点のスナップショット)
AutoConfigure 是什么?
AutoConfigure是Spring Boot提供的一种机制,用于在使用Spring的各种项目(例如Spring Framework(Spring MVC)、Spring Security、Spring Data、Spring Cloud、Spring Integration、Spring Batch等)和第三方开源库时自动定义Bean。虽然我用了“自动”一词来表达,但更准确的表述是… Spring Boot会导入预先准备好的AutoConfigure用的Bean定义文件(配置类),并根据导入的配置类的定义进行Bean的定义。
此外,Spring Boot提供的AutoConfigure配置类还具有一个特点,只有在满足特定条件时(例如,指定的类存在于类路径上、指定类的Bean未定义等等)才会应用Bean定义。
另外,”导入配置类的机制”和”只在特定条件下应用Bean定义的机制”本身都不是Spring Boot的原创功能,而是由Spring Framework提供的功能。
自动配置的机制
让我们以通过使用SPRING INITIALIZR创建的项目源代码为例,看一下配置类是如何导入和进行Bean定义的。
大致上的概念图如下所示:

指定配置类
使用SPRING INITIALIZR创建项目时,会创建一个具有以下main方法的类。首先需要留意的是SpringApplication类的run方法的第一个参数。第一个参数是用于生成依赖注入容器的配置类(被注解@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);
}
}
哎呀…第一个参数传递给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时,将@EnableAutoConfiguration附加到传递给run方法的配置类的参数中。值得注意的是,@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 {
// ...
}
或者
您也可以使用属性来指定要排除的对象。
spring.autoconfigure.exclude=...
使用属性禁用自动配置。
虽然我觉得你可能不常用它,但是你也可以使用属性来禁用AutoConfigure。
spring.boot.enableautoconfiguration=false
条件Bean的定义机制
虽然Spring Boot默认提供了很多用于自动配置的配置类,但是这些类实际上是否总是被应用呢?我在开头简要提到过,大多数配置类只在满足特定条件时才会生效。
使用@Conditional进行条件化的Bean定义
首先,让我们简单解释一下条件Bean定义是如何实现的。
Spring Framework提供了一种机制,在满足特定条件时才能启用Bean定义,并使用注解@org.springframework.context.annotation.Conditional来指定启用Bean定义的条件。
@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框架的核心功能中使用,你知道它在哪个功能中使用吗?可能Spring的重度用户可能已经有所了解,答案是“Profile功能”。Spring框架支持根据不同的配置文件进行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虚拟机的系统属性(-Dspring.profiles.active)来指定。
java -Dspring.profiles.active=production ...
Spring Boot提供了一个名为@Conditional的复合注解。
Spring Boot提供了一系列合成注解来表达在支持AutoConfigure机制时所需的“条件”。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);
}
}
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上使用Spring Boot不支持的OSS库的配置以及为内部项目创建的通用库的配置,可以创建AutoConfigure用的配置类,并在多个项目之间共享使用。
另外,我在MyBatis和Doma2中也有贡献的经验,它们都提供了与Spring Boot集成的工具。如果您使用这些库,一定要尝试使用这些工具。
-
- https://github.com/mybatis/spring-boot-starter
- https://github.com/domaframework/doma-spring-boot
总结
这次介绍了Spring Boot的一个主要功能之一AutoConfigure(加上条件Bean定义)的机制。AutoConfigure是一个非常强大和方便的功能,但是如果不理解AutoConfigure本身的机制…那么Bean定义是在哪里?哪些状态的Bean被注册到DI容器中?这些部分不明确的地方可能会导致意想不到的问题。
当我发现不按照预期工作时…我首先会查看Spring Boot提供的AutoConfigure配置类的源代码。
- https://github.com/spring-projects/spring-boot/tree/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure
在使用Spring Boot时,建议阅读与所使用功能的AutoConfigure相关的配置类。特别是对于架构师来说,我认为阅读这些配置类是必不可少的。
参考网站/参考书籍
-
- http://docs.spring.io/spring-boot/docs/1.4.1.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-developing-auto-configuration
-
- http://docs.spring.io/spring/docs/4.3.3.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#beans-definition-profiles
- Spring徹底入門の「13.1.2 AutoConfigureによる自動設定」