通过使用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. python 继承机制

    继承机制经常用于创建和现有类功能类似的新类,又或是新类只需要在现有类基础上添加一些成员(属性和方法),但又不想将现有类代码复制给新类.也就是说,通过继承这种机制,可以实现类的重复使用. class S ...

  3. [bzoj2004] [洛谷P3204] [Hnoi2010] Bus 公交线路

    Description 小Z所在的城市有N个公交车站,排列在一条长(N-1)km的直线上,从左到右依次编号为1到N,相邻公交车站间的距 离均为1km. 作为公交车线路的规划者,小Z调查了市民的需求,决 ...

  4. Thematic002.字符串专题

    目录 Trie字典树 KMP AC自动机 Manacher 回文自动机 后缀数组 后缀自动机 Trie字典树 概念 我们先来看看什么是Trie字典树 可以发现,这棵树的每一条边都有一个字符 有一些点是 ...

  5. Educational Codeforces Round 80 (Rated for Div. 2) E. Messenger Simulator

    可以推出 min[i]要么是i要么是1,当a序列中存在这个数是1 max[i]的话就比较麻烦了 首先对于i来说,如果还没有被提到第一位的话,他的max可由他后面的这部分序列中 j>=i 的不同数 ...

  6. JS-09-数组

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. .net 项目更改默认命名空间

    这个项目是我去年毕业设计做的项目,项目创建的时候由于手抖把Landlordtenant写成了 LandloRdtenant(R大写了),当时不知道如何改回来又担心改完之后报错也就没管了. 今天尝试使用 ...

  8. webpack--运行npm run dev自动打开浏览器运行首页的两种方式以及热加载

    作为开发人员,我们在修改了代码之后,在vscode终端运行npm run dev指令后,希望它可以自动打开浏览器方便我们调试,有两种方式可以实现: 自动打开浏览器的两种方式: 方式一: 1.webpa ...

  9. k3s首季在线培训来袭!本周四晚,线上见!

    筹备已久的k3s在线培训终于要和大家见面啦! k3s是一款适用于边缘计算场景以及IoT场景的轻量级Kubernetes发行版,经过CNCF的一致性认证.由业界应用最广泛的Kubernetes管理平台R ...

  10. Windows下查看dll被哪个进程调用

    卸载程序,结果没卸载干净---程序的安装目录中还剩下一个dll文件.想删,结果系统提示说dll文件被某个进程占用了,不让删. 先前碰到这种做法,我都是直接使用unlocker先unlock一下,然后删 ...