了解 Spring Boot AutoConfiguration

来源:互联网 发布:淘宝 最贵的包子 编辑:程序博客网 时间:2024/06/07 06:20

探索 @Conditional 的力量

在开发基于 Spring 的应用程序时,我们可能需要有条件地注册 Bean。

例如,您希望在本地运行应用程序时注册一个指向 DEV 数据库的 DataSource bean,在生产中运行时指向 PRODUCTION 数据库。

您可以将数据库连接参数外部化,放置在属性(properties)文件中,并在适合的环境中使用这个文件。但是,当您需要指向不同的环境并构建应用程序时,您得更改配置。

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

@Configurationpublic class AppConfig{    @Bean    @Profile("DEV")    public DataSource devDataSource() {        ...    }     @Bean    @Profile("PROD")    public DataSource prodDataSource() {        ...    }}

然后,可以使用系统属性(System Property)来指定要激活配置文件 - Dspring.profiles.active = DEV

这种方法适用于启用了基于 profiles 来启用或禁用 bean 注册的简单情况。如果要根据一些条件逻辑来注册 bean,Profiles 方法是远远不够的。

为了让有条件地注册 Spring bean 变得更加灵活,Spring 4 引入了 @Conditional 概念。通过使用 @Conditional 方式,您可以根据任意条件有条件地注册一个 bean。

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

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

这里只是列举了几个例子,您可以根据自己的需要定义任何条件。

让我们来看看 Spring 的 @Conditional 是如何工作的。

假设我们有一个 UserDAO 接口,其包含从数据存储中获取数据的方法。我们有两个 UserDAO 接口的实现,即 JdbcUserDAO(与 MySQL 数据库通信)和 MongoUserDAO(与 MongoDB 通信)。

我们可能想通过基于系统属性 dbType 来只启用 JdbcUserDAO 或 MongoUserDAO 中的一个。

如果应用程序使用了 java -jar myapp.jar -DdbType = MySQL 启动,则启用 JdbcUserDAO,如果启动时使用了 java -jar myapp.jar -DdbType = MONGO,则启用 MongoUserDAO。

假设我们有 UserDAO 接口,以下是 JdbcUserDAO 和 MongoUserDAO 实现类:

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");    }}

我们可以实现一个 Condition MySQLDatabaseTypeCondition 来检查系统属性是否是 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"));    }}

现在我们可以使用 @Conditional 有条件地配置 JdbcUserDAO 和 MongoUserDAO beans,如下所示:

@Configurationpublic 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。

假设我们想要当 MongoDB java 驱动程序类 com.mongodb.Server 在 classpath 上可用时注册 MongoUserDAO bean,否则,注册 JdbcUserDAO bean。

为了做到这点,我们可以创建 Condition 来检查 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;        }    }}

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

如果我们只想在没有其他 UserDAO 类型的 Spring bean 注册时才注册 MongoUserDAO bean,该怎么办?

我们同样可以创建一个 Condition 来检查是否存在现有的某个类型的 bean,如下所示:

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

如果我们只想在属性占位符(properties placeholder)配置文件中设置属性 app.dbType = MONGO,我们该怎么注册 MongoUserDAO bean?

我们可以实现如下 Condition(条件):

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

我们刚刚已经看到了如何实现各种条件。

然而,使用注解实现 Condition 的方式更为优雅。而不是为 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));    }}

现在我们可以将 @DatabaseType 注解放置在我们的 bean 定义上:

@Configuration@ComponentScanpublic class AppConfig{    @DatabaseType("MYSQL")    public UserDAO jdbcUserDAO(){        return new JdbcUserDAO();    }     @Bean    @DatabaseType("MONGO")    public UserDAO mongoUserDAO(){        return new MongoUserDAO();    }}

此处,我们从 DatabaseType 注解获取元数据,并检查系统属性 dbType 的值以确定是否启用或禁用 bean 注册。

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

SpringBoot 中大量地使用了 @Conditional 功能,根据各种标准有条件地注册 bean。

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

现在我们来了解一下 SpringBoot 如何使用 @Conditional 功能有条件地检查是否注册一个 bean。

究竟是什么触发了自动配置(auto-configuration)机制?

这就是我们下一节的内容。

SpringBoot 的 AutoConfiguration

SpringBoot 自动配置魔法的关键在于 @EnableAutoConfiguration 注解。通常,我们使用 @SpringBootApplication 来注解我们的应用程序入口类,如果我们要自定义默认值,我们可以使用以下注解:

Configuration@EnableAutoConfiguration@ComponentScanpublic class Application{ }

@EnableAutoConfiguration 注解允许通过扫描 classpath 中的组件并注册匹配各种条件的 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.class 和 EmbeddedDatabaseType.class 类在 classpath 上可用时才会考虑 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/testspring.datasource.username=rootspring.datasource.password=secretspring.datasource.driver-class-name=com.mysql.jdbc.Driver

您还可以看到一些使用了 SpringBoot 的 Condition 注解(如 @ConditionalOnMissingBean、@ConditionalOnClass 和 @ConditionalOnProperty 等)的内部类和 bean 定义方法。

只有这些条件匹配时,这些 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
原创粉丝点击