通过使用Mongo和MySQL DB实现的示例,深入了解Spring Boot的@Conditional注释世界。

在我以前的文章“为什么选择Spring Boot?”中,我们讨论了如何创建Spring Boot应用程序,但是你可能了解也可能不了解幕后情况。 你可能想了解Spring Boot自动配置背后的魔力。

在此之前,你应该了解Spring的@Conditional功能,所有Spring Boot的AutoConfiguration魔术都依赖于此。

探索@Conditional的力量

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

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

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

为了解决这个问题,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,那么概要文件方法本身是不够的。

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

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

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

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

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

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

我们可能只想基于系统属性(即dbType)启用JdbcUserDAO和MongoUserDAO的一个接口。

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

假设我们有一个UserDAO bean和一个JdbcUserDAO bean。 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");
}
}

我们可以实现条件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有条件地配置JdbcUserDAO和MongoUserDAO 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。

假设仅当classpath上有MongoDB Java驱动程序类“ com.mongodb.Server”可用时,我们才想注册MongoUserDAO Bean,否则,我们希望注册JdbcUserDAO Bean。

为此,我们可以创建条件来检查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。

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

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

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

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

Spring Boot自动配置

Spring Boot自动配置魔术的关键是@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.class和EmbeddedDatabaseType.class类时,才会考虑DataSourceAutoConfiguration中的bean的AutoConfiguration。

该类还带有@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.JacksonAutoConfigurationetc etc.

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

感谢阅读!

Spring Boot自动配置如何工作的更多相关文章

  1. Spring Boot自动配置原理(转)

    第3章 Spring Boot自动配置原理 3.1 SpringBoot的核心组件模块 首先,我们来简单统计一下SpringBoot核心工程的源码java文件数量: 我们cd到spring-boot- ...

  2. Spring Boot 自动配置的原理、核心注解以及利用自动配置实现了自定义 Starter 组件

    本章内容 自定义属性快速入门 外化配置 自动配置 自定义创建 Starter 组件 摘录:读书是读完这些文字还要好好用心去想想,写书也一样,做任何事也一样 图 2 第二章目录结构图 第 2 章 Spr ...

  3. Spring Boot自动配置原理与实践(二)

    前言 在之前的博文(Spring Boot自动配置原理与实践(一))中,已经介绍了Spring boot的自动配置的相关原理与概念,本篇主要是对自动配置的实践,即自定义Starter,对原理与概念加深 ...

  4. Spring Boot自动配置SpringMVC(一)

    实际上在关于Spring Boot自动配置原理实战的文章Spring Boot自动配置实战 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中,可以看到我们使用到了@ReqeusMappi ...

  5. Springboot 系列(三)Spring Boot 自动配置原理

    注意:本 Spring Boot 系列文章基于 Spring Boot 版本 v2.1.1.RELEASE 进行学习分析,版本不同可能会有细微差别. 前言 关于配置文件可以配置的内容,在 Spring ...

  6. Spring Boot自动配置与Spring 条件化配置

    SpringBoot自动配置 SpringBoot的自动配置是一个运行时(应用程序启动时)的过程,简化开发时间,无需浪费时间讨论具体的Spring配置,只需考虑如何利用SpringBoot的自动配置即 ...

  7. Spring Boot自动配置原理、实战

    Spring Boot自动配置原理 Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射. org.s ...

  8. Spring Boot自动配置

    Spring Boot自动配置原理 Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射. org.s ...

  9. Spring boot 自动配置自定义配置文件

    示例如下: 1.   新建 Maven 项目 properties 2.   pom.xml <project xmlns="http://maven.apache.org/POM/4 ...

随机推荐

  1. 从0到1掌握某Json-TemplatesImpl链与ysoserial-jdk7u21的前因后果

    本文首发于先知社区: https://xz.aliyun.com/t/7096 前言 作为一名安全研究人员(java安全菜鸡),知道拿到exp怎么打还不够,还得进一步分析exp构造原理与漏洞原理才行. ...

  2. Java反射的常见用法

    反射的常见用法有三类,第一类是“查看”,比如输入某个类的属性方法等信息,第二类是“装载“,比如装载指定的类到内存里,第三类是“调用”,比如通过传入参数,调用指定的方法. 1 查看属性的修饰符.类型和名 ...

  3. Java程序员学习Go语言—之一

    转载:https://www.luozhiyun.com/archives/206 GOPATH 工作空间 GOPATH简单理解成Go语言的工作目录,它的值是一个目录的路径,也可以是多个目录路径,每个 ...

  4. linux C++类中成员变量和函数的使用

    1.undefined reference to XXX 问题原因 1)XXX所在的so库等未指定 2)XXX在类中实现的时候没有加上类::函数的格式 2. was not declared in t ...

  5. HanLP《自然语言处理入门》笔记--1.新手上路

    1. 新手上路 自然语言处理(Natural Language Processing,NLP)是一门融合了计算机科学.人工智能及语言学的交叉学科,它们的关系如下图所示.这门学科研究的是如何通过机器学习 ...

  6. Spring注解开发系列专栏

    这个系列主要是讲Spring注解的使用,可以为后面SpringBoot的学习带来一定的帮助.我觉得从Spring直接过度到SpringBoot还是有点快,还是得需要一个演变的过程.从Spring开发, ...

  7. SUSE Linux Enterprise 11 离线安装 DLIB 人脸识别 python机器学习模块

    python机器学习模块安装 我的博客:http://www.cnblogs.com/wglIT/p/7525046.html 环境:SUSE Linux Enterprise 11 sp4  离线安 ...

  8. 使用abp框架与vue一步一步写我是月老的小工具(1)

    一.前言 因为工作的原因,我真正写代码的时间很少,技术面广但深度一直不够,兴趣广泛但缺乏专业精神.以前一直想用asp.net写一款框架,用来在企业开发过程中做一些基础工作,不过后来我找到abp这个框架 ...

  9. Python小白入门题一——文件增删改

    题目描述:用python对文件进行增(创建一个文件).删(删除一个文件).改(重命名)操作. 说明:新建了一个文件夹files存放新增的两个文件,随后这两个文件被批量重命名成“数字.txt”,之后这两 ...

  10. 时序数据库 Apache-IoTDB 源码解析之文件格式简介(三)

    上一章聊到在车联网或物联网中对数据库的需求,以及 IoTDB 的整体架构,详情请见: 时序数据库 Apache-IoTDB 源码解析之系统架构(二) 打一波广告,欢迎大家访问IoTDB 仓库,求一波 ...