Spring Boot「05」Annotations 02

124 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第N天,点击查看活动详情

昨天我们学习了 Spring Boot 中的条件化配置注解,今天我们来学习如何自定义一个自动化配置。

01-基本步骤

基本步骤如下:

  1. 定义一个配置类,例如我们可以定义一个 MySQL 数据源的自动化配置:
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class MysqlAutoConfiguration {
    // ...
}
  1. META-INF/spring.factories中增加如下配置,以便 Spring Boot 的自动化配置特性可以识别到我们自定义的配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
self.samson.example.autoconfigure.MysqlAutoConfiguration
  1. 通过@AutoConfigureOrder注解,我们可以控制自定义的自动化配置的优先级,最低(Ordered.LOWEST_PRECEDENCE)到最高(Ordered.HIGHEST_PRECEDENCE)之间的一个值。
  2. 通过@Conditional定义条件化的配置。注:自动化配置中定义的 Bean 只有在我们没有定义类似的 Bean 时生效,如果我们定义了 Bean,则会覆盖自动化配置中的定义。

02-自定义自动化配置(MySQL数据源)

我们要定义的 MySQL 数据源配置,只有在我们的应用中有数据库相关的依赖时,才需要去配置。所以我们的自定义配置类可以写成:

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(DataSource.class)
public class MysqlAutoConfiguration {
    // ...
}

@ConditionalOnClass : @Conditional that only matches when the specified classes are on the classpath.

这意味着,只有应用的 classpath 下包含DataSource.class时,该配置类定义的 Bean 才生效。

在配置中定义 Bean 时,Spring Boot @Conditional 特性同样提供了几种方式来配置 Bean 尽在某些条件下才能生效:

  1. 根据某个 property 的值来控制条件化 Bean 生效。例如,我们想通过 useMysql=local 表示使用本地的 MySQL 数据库,而使用 useMysql=custom 表示用户自定义的数据库,数据库配置信息仍然通过属性给出(@PropertySource("classpath:mysql.properties"))。 那么我们就可以把我们的配置文件写作:
@Bean(name = "dataSource")
@ConditionalOnProperty(
        name = "useMysql",
        havingValue = "local"
)
@ConditionalOnMissingBean(DataSource.class)
public DataSource localMysqlDataSource() {
    // use local mysql
}

@Bean(name = "dataSource")
@ConditionalOnProperty(
        name = "useMysql",
        havingValue = "custom"
)
@ConditionalOnMissingBean(DataSource.class)
public DataSource customMysqlDataSource() {
    // use custom mysql
}
  1. 根据某个类型或某个名称的 Bean 来控制条件化 Bean 生效。例如,我们创建 DataSource 的前提就是,当前容器中还未包含任何 DataSource 实例。我们就可以通过@ConditionalOnMissingBean(DataSource.class)来控制生效条件。
  2. 根据某个资源来控制条件化 Bean 生效。
@ConditionalOnResource(
        resources = "classpath:mysql.properties")
Properties additionalProperties() {
    // additional properties
}
  1. 定义复杂的条件逻辑,通过继承 SpringBootCondition.class 并实现 getMatchOutcome 方法,并搭配 @Conditional 使用。
@Conditional(HibernateCondition.class)
Properties additionalProperties() {
    // additional properties
}

public class HibernateCondition extends SpringBootCondition {
    private static String[] CLASS_NAMES = {
            "org.hibernate.ejb.HibernateEntityManager",
            "org.hibernate.jpa.HibernateEntityManager"
    };
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConditionMessage.Builder message = ConditionMessage.forCondition("Hibernate");

        return Arrays.stream(CLASS_NAMES)
                .filter(className -> ClassUtils.isPresent(className, context.getClassLoader()))
                .map(className -> ConditionOutcome.match(message.found("class").items(ConditionMessage.Style.NORMAL, className)))
                .findAny()
                .orElseGet(() -> ConditionOutcome.noMatch(message.didNotFind("class", "classes").items(ConditionMessage.Style.NORMAL, Arrays.asList(CLASS_NAMES))));
    }
}

需要注意的是:条件化配置中定义的条件,测试条件是否匹配时,根据的仅仅是测试条件这一时刻应用上下文中的内容。例如,考虑如下代码:

@Bean(name = "dataSource")
@ConditionalOnProperty(
        name = "useMysql",
        havingValue = "custom"
)
@ConditionalOnMissingBean
public DataSource customMysqlDataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    return dataSource;
}

@Bean
@ConditionalOnBean(name = "dataSource")
@ConditionalOnMissingBean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Autowired @Qualifier(value = "dataSource") DataSource dataSource) {
    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
    return em;
    }

当测试entityManagerFactory是否生效时,需要依赖名称为dataSource的 Bean。 如果将 customMysqlDataSource() 方法放置在 entityManagerFactory() 方法之后,则entityManagerFactory的定义不生效。

完整的程序代码以及测试用力可以到 gitee 下载。

有没有什么方法可以关闭某个自动化配置呢? 使用@EnableAutoConfiguration中的 exclude 属性,如下:

@EnableAutoConfiguration(
  exclude={MySQLAutoconfiguration.class})
public class AutoConfigureApplication { 
    
}

或者通过设置 property 来实现:spring.autoconfigure.exclude=self.samson.example.autoconfigure.MysqlAutoConfiguration

03-如何在 docker 安装 mysql

docker search mysql     
docker pull mysql 

docker run -d -p 3306:3306 --privileged=true -v "D:\docker\mysql\log":"/var/log/mysql" -v "D:\docker\mysql\data":"/var/lib/mysql" -v "D:\docker\mysql\conf":"/etc/mysql/conf.d" -e MYSQL_ROOT_PASSWORD="123456" mysql:latest