SpringBoot AutoConfiguration魔术如何工作?

在我以前的文章中, 为什么选择SpringBoot? 我们已经研究了如何创建SpringBoot应用程序。 但是您可能会也可能不会了解幕后发生的事情。 您可能想了解SpringBoot自动配置背后的魔力。

但是在此之前,您应该了解Spring的@Conditional功能,所有SpringBoot的AutoConfiguration魔术赖以其基础。

探索@Conditional的力量

在开发基于Spring的应用程序时,我们可能会遇到有条件地注册bean的需求。

例如,您可能想在本地运行应用程序时注册一个指向DEV数据库的DataSource bean,而在生产环境中运行时则指向一个不同的PRODUCTION数据库。

您可以将数据库连接参数外部化为属性文件,并使用适合于环境的文件。 但是,每当需要指向其他环境并构建应用程序时,都需要更改配置。

为了解决这个问题,Spring 3.1引入了Profiles的概念。 您可以注册多个相同类型的bean,并将它们与一个或多个概要文件关联。 当您运行该应用程序时,您可以激活所需的概要文件,并且仅与激活的概要文件关联的bean将被注册。

@Configuration
public class AppConfig
{
    @Bean
    @Profile("DEV")
    public DataSource devDataSource() {
        ...
    }

    @Bean
    @Profile("PROD")
    public DataSource prodDataSource() {
        ...
    }
}

然后,您可以使用系统属性-Dspring.profiles.active = DEV指定活动配置文件

这种方法适用于简单的情况,例如基于激活的配置文件启用或禁用bean注册。 但是,如果您要基于某些条件逻辑来注册bean,那么Profiles方法本身是不够的。

为了为有条件地注册Spring Bean提供更大的灵活性,Spring 4引入了@Conditional的概念。 通过使用@Conditional方法,您可以根据任意条件有条件地注册bean。

例如,在以下情况下,您可能要注册一个bean:

  • 一个特定的类存在于类路径中
  • 某种类型的Spring bean尚未在ApplicationContext中注册
  • 特定文件存在于某个位置
  • 在配置文件中配置特定的属性值
  • 存在/不存在特定的系统属性

这些仅是几个示例,您可以具有所需的任何条件。

让我们看一下Spring的@Conditional的工作原理。

假设我们有一个UserDAO接口,其中包含从数据存储中获取数据的方法。 我们UserDAO的接口即JdbcUserDAO两种工具进行对话的MySQL数据库和MongoUserDAO进行对话的MongoDB的

我们可能只想基于系统属性dbType启用JdbcUserDAOMongoUserDAO之一。

如果使用java -jar myapp.jar -DdbType = MySQL启动应用程序,则我们要启用JdbcUserDAO ;否则,如果使用java -jar myapp.jar -DdbType = MONGO启动应用程序, 则要启用MongoUserDAO

假设我们有UserDAO接口和JdbcUserDAOMongoUserDAO实现如下:

public interface UserDAO
{
    List<String> getAllUserNames();
}

public class JdbcUserDAO implements UserDAO
{
    @Override
    public List<String> getAllUserNames()
    {
        System.out.println("**** Getting usernames from RDBMS *****");
        return Arrays.asList("Siva","Prasad","Reddy");
    }
}

public class MongoUserDAO implements UserDAO
{
    @Override
    public List<String> getAllUserNames()
    {
        System.out.println("**** Getting usernames from MongoDB *****");
        return Arrays.asList("Bond","James","Bond");
    }
}

我们可以实现条件MySQLDatabaseTypeCondition来检查系统属性dbType是否为“ MYSQL” ,如下所示:

public class MySQLDatabaseTypeCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
    {
        String enabledDBType = System.getProperty("dbType");
        return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MYSQL"));
    }
}

我们可以实现条件MongoDBDatabaseTypeCondition来检查系统属性dbType是否为“ MONGODB ”,如下所示:

public class MongoDBDatabaseTypeCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
    {
        String enabledDBType = System.getProperty("dbType");
        return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MONGODB"));
    }
}

现在,我们可以使用@Conditional有条件地配置JdbcUserDAOMongoUserDAO Bean,如下所示:

@Configuration
public class AppConfig
{
    @Bean
    @Conditional(MySQLDatabaseTypeCondition.class)
    public UserDAO jdbcUserDAO(){
        return new JdbcUserDAO();
    }

    @Bean
    @Conditional(MongoDBDatabaseTypeCondition.class)
    public UserDAO mongoUserDAO(){
        return new MongoUserDAO();
    }
}

如果我们像java -jar myapp.jar -DdbType = MYSQL那样运行应用程序,则仅JdbcUserDAO bean将被注册。 但是,如果您-DdbType = MONGODB,则仅MongoUserDAO Bean将被注册。

现在,我们已经看到了如何基于系统属性有条件地注册bean。

假设我们希望,注册MongoUserDAO豆只有当MongoDB的 Java驱动程序类“com.mongodb.Server”可在类路径中,如果不是我们想注册JdbcUserDAO豆。

为此,我们可以创建条件来检查MongoDB驱动程序类“ com.mongodb.Server”的存在与否,如下所示:

public class MongoDriverPresentsCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext,AnnotatedTypeMetadata metadata)
    {
        try {
            Class.forName("com.mongodb.Server");
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}

public class MongoDriverNotPresentsCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
    {
        try {
            Class.forName("com.mongodb.Server");
            return false;
        } catch (ClassNotFoundException e) {
            return true;
        }
    }
}

我们刚刚看到了如何根据类路径中是否存在类来有条件地注册bean。

如果仅在尚未注册其他类型为UserDAO的 Spring Bean的情况下才想注册MongoUserDAO Bean,该怎么办?

我们可以创建一个条件来检查是否存在某种特定类型的现有bean,如下所示:

public class UserDAOBeanNotPresentsCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
    {
        UserDAO userDAO = conditionContext.getBeanFactory().getBean(UserDAO.class);
        return (userDAO == null);
    }
}

如果仅在属性占位符配置文件中设置属性app.dbType = MONGO时才想注册MongoUserDAO bean,该怎么办

我们可以如下实现该条件:

public class MongoDbTypePropertyCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext,
    AnnotatedTypeMetadata metadata)
    {
        String dbType = conditionContext.getEnvironment()
                            .getProperty("app.dbType");
        return "MONGO".equalsIgnoreCase(dbType);
    }
}

我们刚刚看到了如何实现各种类型的条件。 但是,还有使用注释来实现条件的更优雅的方法。 除了为MYSQL和MongoDB创建Condition实现之外,我们还可以创建一个DatabaseType注释,如下所示:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(DatabaseTypeCondition.class)
public @interface DatabaseType
{
    String value();
}

然后,我们可以实现DatabaseTypeCondition以使用DatabaseType值来确定是启用还是禁用bean注册,如下所示:

public class DatabaseTypeCondition implements Condition
{
    @Override
    public boolean matches(ConditionContext conditionContext,
    AnnotatedTypeMetadata metadata)
    {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(DatabaseType.class.getName());
        String type = (String) attributes.get("value");
        String enabledDBType = System.getProperty("dbType","MYSQL");
        return (enabledDBType != null && type != null && enabledDBType.equalsIgnoreCase(type));
    }
}

现在,我们可以在bean定义上使用@DatabaseType批注,如下所示:

@Configuration
@ComponentScan
public class AppConfig
{
    @DatabaseType("MYSQL")
    public UserDAO jdbcUserDAO(){
        return new JdbcUserDAO();
    }

    @Bean
    @DatabaseType("MONGO")
    public UserDAO mongoUserDAO(){
        return new MongoUserDAO();
    }
}

在这里,我们从DatabaseType批注中获取元数据,并对照System Property dbType值进行检查,以确定是启用还是禁用Bean注册。

我们已经看到了很多示例,以了解如何使用@Conditional批注有条件地注册bean。

SpringBoot广泛使用@Conditional功能根据各种条件有条件地注册bean。

您可以在spring-boot-autoconfigure- {version} .jar的 org.springframework.boot.autoconfigure包中找到SpringBoot使用的各种Condition实现。

现在,我们了解了SpringBoot如何使用@Conditional功能有条件地检查是否注册Bean。 但是究竟是什么触发了自动配置机制呢?

这就是我们将在下一部分中讨论的内容。

SpringBoot自动配置

SpringBoot自动配置魔术的关键是@EnableAutoConfiguration批注。 通常,我们使用@SpringBootApplication注释应用程序入口点类,或者如果要自定义默认值,则可以使用以下注释:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application
{

}

@EnableAutoConfiguration批注通过扫描类路径组件并注册与各种条件匹配的bean来启用Spring ApplicationContext的自动配置。

SpringBoot在spring-boot-autoconfigure- {version} .jar中提供了各种AutoConfiguration类,这些类负责注册各种组件。

通常, AutoConfiguration类使用@Configuration注释,以将其标记为Spring配置类,并使用@EnableConfigurationProperties注释,以绑定定制属性和一个或多个条件Bean注册方法。

例如,考虑org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration类。

@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration 
{
    ...
    ...
    @Conditional(DataSourceAutoConfiguration.EmbeddedDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import(EmbeddedDataSourceConfiguration.class)
    protected static class EmbeddedConfiguration {

    }

    @Configuration
    @ConditionalOnMissingBean(DataSourceInitializer.class)
    protected static class DataSourceInitializerConfiguration {
        @Bean
        public DataSourceInitializer dataSourceInitializer() {
        return new DataSourceInitializer();
        }
    }

    @Conditional(DataSourceAutoConfiguration.NonEmbeddedDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    protected static class NonEmbeddedConfiguration {
        @Autowired
        private DataSourceProperties properties;

        @Bean
        @ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
        public DataSource dataSource() {
            DataSourceBuilder factory = DataSourceBuilder
                    .create(this.properties.getClassLoader())
                    .driverClassName(this.properties.getDriverClassName())
                    .url(this.properties.getUrl()).username(this.properties.getUsername())
                    .password(this.properties.getPassword());
            if (this.properties.getType() != null) {
                factory.type(this.properties.getType());
            }
            return factory.build();
        }
    }
    ...
    ...
    @Configuration
    @ConditionalOnProperty(prefix = "spring.datasource", name = "jmx-enabled")
    @ConditionalOnClass(name = "org.apache.tomcat.jdbc.pool.DataSourceProxy")
    @Conditional(DataSourceAutoConfiguration.DataSourceAvailableCondition.class)
    @ConditionalOnMissingBean(name = "dataSourceMBean")
    protected static class TomcatDataSourceJmxConfiguration {
        @Bean
        public Object dataSourceMBean(DataSource dataSource) {
        ....
        ....
        }
    }
    ...
    ...
}

此处, DataSourceAutoConfiguration带有@ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class})注释,这意味着仅当在类路径上有DataSource.classEmbeddedDatabaseType.class类可用时,才会考虑在DataSourceAutoConfiguration中对bean进行自动配置。

该类还带有@EnableConfigurationProperties(DataSourceProperties.class)批注,该启用了自动将application.properties中的属性绑定到DataSourceProperties类的属性的功能。

@ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
public class DataSourceProperties implements BeanClassLoaderAware, EnvironmentAware, InitializingBean {

    public static final String PREFIX = "spring.datasource";
    ...
    ...
    private String driverClassName;
    private String url;
    private String username;
    private String password;
    ...
    //setters and getters
}

使用此配置,所有以spring.datasource。*开头的属性都将自动绑定到DataSourceProperties对象。

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=secret
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

您还可以看到一些内部类和bean定义方法,这些内部类和bean定义方法用SpringBoot的条件注释(例如@ ConditionalOnMissingBean,@ ConditionalOnClass和@ConditionalOnProperty等)进行注释。

仅当这些条件匹配时,这些Bean定义才会在ApplicationContext中注册。

您还可以在spring-boot-autoconfigure- {version} .jar中探索许多其他AutoConfiguration类,例如

  • org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration
  • org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
  • org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
  • org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration等。

我希望您现在通过使用各种AutoConfiration类以及@Conditional功能来了解SpringBoot自动配置的工作方式。

翻译自: https://www.javacodegeeks.com/2016/03/springboot-autoconfiguration-magic-works.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值