昨天我们学习了 Spring Boot 中的条件化配置注解,今天我们来学习如何自定义一个自动化配置。
01-基本步骤
基本步骤如下:
定义一个配置类,例如我们可以定义一个 MySQL 数据源的自动化配置:
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class MysqlAutoConfiguration {
// ...
}
复制代码
在META-INF/spring.factories
中增加如下配置,以便 Spring Boot 的自动化配置特性可以识别到我们自定义的配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
self.samson.example.autoconfigure.MysqlAutoConfiguration
复制代码
通过@AutoConfigureOrder
注解,我们可以控制自定义的自动化配置的优先级,最低(Ordered.LOWEST_PRECEDENCE)到最高(Ordered.HIGHEST_PRECEDENCE)之间的一个值。
通过@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 尽在某些条件下才能生效:
根据某个 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
}
复制代码
根据某个类型或某个名称的 Bean 来控制条件化 Bean 生效。例如,我们创建 DataSource 的前提就是,当前容器中还未包含任何 DataSource 实例。我们就可以通过@ConditionalOnMissingBean(DataSource.class)
来控制生效条件。
根据某个资源来控制条件化 Bean 生效。
@ConditionalOnResource(
resources = "classpath:mysql.properties")
Properties additionalProperties() {
// additional properties
}
复制代码
定义复杂的条件逻辑,通过继承 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
复制代码
评论