理解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定义的。

大致上的概念图如下所示:

autoconfigure.png

指定配置类

使用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バージョン上で動作している場合に適用される。@ConditionalOnWebApplicationWebアプリケーション環境で動作している場合に適用される。@ConditionalOnNotWebApplicationWebアプリケーション環境で動作していない場合に適用される。

如果我们看一下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上使用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による自動設定」
广告
将在 10 秒后关闭
bannerAds