前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >DataSourceAutoConfiguration 解析(2)

DataSourceAutoConfiguration 解析(2)

作者头像
黑洞代码
发布2021-06-23 00:39:39
6910
发布2021-06-23 00:39:39
举报

DataSourceAutoConfiguration 解析(1)

初始化 DataSourceProperties 配置文件

2.1 初始化 DataSourceProperties 配置文件

如果设置的不是内置数据库的话:1)必须配置的有:url,username,password 2)数据库名不是必须的 3)driverClassName 不是必须的:可以从 url 中推导出 4)type 不是必须的:可以从上下文中推导出 如果设置的是内置数据库的话:1)必须配置的有:引入内置数据库依赖,如:H2 2)其它所有的都可以不配置(有默认的配置),如果随意配置,可能产生冲突

代码语言:javascript
复制
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
    private ClassLoader classLoader;
    //数据库名:如果使用内置数据库,默认为testdb
    private String name;
    //Whether to generate a random datasource name
    private boolean generateUniqueName;
    //如果generateUniqueName==true,则不使用name,而使用uniqueName来做数据库名
    private String uniqueName;
    //完整的数据库连接池名。默认从项目中检测出
    private Class<? extends DataSource> type;
    //JDBC driver的完整名,默认从URL中检测出相对应的driver
    private String driverClassName;
    //JDBC URL of the database
    private String url;

    //Login username of the database
    private String username;
    //Login password of the database
    private String password;
    //JNDI 数据源的位置:如果指定了,则数据库连接数据将会失效:driverClassName,url,username,password
    private String jndiName;
    //初始化database使用的sql文件的模式,默认是EMBEDDED,如果是NONE就不会执行sql文件
    //如果设置的模式和检测出来的模式不匹配,也不会执行sql文件
    private DataSourceInitializationMode initializationMode = DataSourceInitializationMode.EMBEDDED;

    //执行sql文件相关,schema-${platform}.sql,data-${platform}.sql
    //默认执行不带 platform 的 sql 文件 + 带 platform 的 sql 文件
    private String platform = "all";
    //具体的 schema 文件的位置,如果指定了这个就不会查找默认的sql文件了
    private List<String> schema;
    //执行schema使用数据库的用户名
    private String schemaUsername;
    //执行schema使用数据库的密码,如果schemaUsername和schemaPassword都不指定,就使用 **主数据源** 作为执行目的数据库!
    private String schemaPassword;
    //同schema
    private List<String> data;
    private String dataUsername;
    private String dataPassword;

    //如果初始化database时报错,是否继续
    private boolean continueOnError = false;
    //Statement separator in SQL initialization scripts.
    private String separator = ";";
    //SQL scripts encoding.
    private Charset sqlScriptEncoding;
    //默认的内置数据库连接信息:
    //1 NONE(null, null, null)
    //2 H2(EmbeddedDatabaseType.H2, "org.h2.Driver","jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE")
    //3 DERBY(...)
    //4 HSQL(...)
    private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE;

    private Xa xa = new Xa();
    //从项目中匹配相应的内置数据库,查找是否引入了相应的依赖,如果引入了H2依赖,这里embeddedDatabaseConnection就设置成 H2
    @Override
    public void afterPropertiesSet() throws Exception {
        this.embeddedDatabaseConnection = EmbeddedDatabaseConnection
                .get(this.classLoader);
    }

    // 通过 spring.datasource 属性 初始化一个DataSourceBuilder,用来方便的创建datasource,就是一个封装的方法
    public DataSourceBuilder<?> initializeDataSourceBuilder() {
        return DataSourceBuilder.create(getClassLoader()).type(getType())
                .driverClassName(determineDriverClassName()).url(determineUrl())
                .username(determineUsername()).password(determinePassword());
    }
    //智能的获取DriverClassName
    public String  determineDriverClassName() {
        if (StringUtils.hasText(this.driverClassName)) {
            Assert.state(driverClassIsLoadable(),
                    () -> "Cannot load driver class: " + this.driverClassName);
            return this.driverClassName;
        }
        String driverClassName = null;
        if (StringUtils.hasText(this.url)) {
            driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
        }
        //如果走到这,还没识别出 driverClassName,且它为null,就去内置数据库中找匹配的
        //如果项目中没有引入 内置数据库依赖,那就会报错啦
        if (!StringUtils.hasText(driverClassName)) {
            driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
        }
        if (!StringUtils.hasText(driverClassName)) {
            throw new DataSourceBeanCreationException(
                    "Failed to determine a suitable driver class", this,
                    this.embeddedDatabaseConnection);
        }
        return driverClassName;
    }
    private boolean driverClassIsLoadable() {
        try {
            ClassUtils.forName(this.driverClassName, null);
            return true;
        }
        catch (UnsupportedClassVersionError ex) {
            // Driver library has been compiled with a later JDK, propagate error
            throw ex;
        }
        catch (Throwable ex) {
            return false;
        }
    }
    //Determine the url to use based on this configuration and the environment.
    public String determineUrl() {
        if (StringUtils.hasText(this.url)) {
            return this.url;
        }
        String databaseName = determineDatabaseName();
        String url = (databaseName != null)
                ? this.embeddedDatabaseConnection.getUrl(databaseName) : null;
        if (!StringUtils.hasText(url)) {
            throw new DataSourceBeanCreationException(
                    "Failed to determine suitable jdbc url", this,
                    this.embeddedDatabaseConnection);
        }
        return url;
    }
    //Determine the name to used based on this configuration.
    public String determineDatabaseName() {
        if (this.generateUniqueName) {
            if (this.uniqueName == null) {
                this.uniqueName = UUID.randomUUID().toString();
            }
            return this.uniqueName;
        }
        if (StringUtils.hasLength(this.name)) {
            return this.name;
        }
        if (this.embeddedDatabaseConnection != EmbeddedDatabaseConnection.NONE) {
            return "testdb";
        }
        return null;
    }
    //
    public String determineUsername() {
        if (StringUtils.hasText(this.username)) {
            return this.username;
        }
        if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) {
            return "sa";
        }
        return null;
    }

    public String determinePassword() {
        if (StringUtils.hasText(this.password)) {
            return this.password;
        }
        if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) {
            return "";
        }
        return null;
    }
    //XA Specific datasource settings.
    public static class Xa {
        //XA datasource fully qualified name.
        private String dataSourceClassName;
        //Properties to pass to the XA data source.
        private Map<String, String> properties = new LinkedHashMap<>();
    }
}

引入 EmbeddedDatabaseConfiguration 配置类

2.2 引入 EmbeddedDatabaseConfiguration 配置类

代码语言:javascript
复制
//所有的 condition 类都会最终继承 SpringBootCondition,SpringBootCondition 是一个模板类,继承它后,我们只需要实现核心的 getMatchOutCome() 方法来自定义一个 Condition 类了。当这个类被@Conditional 注解引入的时候,最终时候执行这个核心方法来判断是否匹配的
static class EmbeddedDatabaseCondition extends SpringBootCondition {
    private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        ConditionMessage.Builder message = ConditionMessage
                .forCondition("EmbeddedDataSource");
        // anyMatches() 就是一个 SpringbootCondition 类中的模板方法,意思是:匹配任意一个 pooledCondition 中的条件
        // 这里 pooledCondition 中的条件其实是匹配非内置数据库的条件,这就很奇怪了,为什么不把 匹配非内置数据库的配置放在前面呢?
        if (anyMatches(context, metadata, this.pooledCondition)) {
            return ConditionOutcome
                    .noMatch(message.foundExactly("supported pooled data source"));
        }
        //这里查找了项目中有没有引入 H2,DERBY,HSQL 这3个class,如果没有引入,就返回 null,引入了返回 第一个 type
        EmbeddedDatabaseType type = EmbeddedDatabaseConnection
                .get(context.getClassLoader()).getType();
        if (type == null) {
            return ConditionOutcome
                    .noMatch(message.didNotFind("embedded database").atAll());
        }
        return ConditionOutcome.match(message.found("embedded database").items(type));
    }
}

引入 PooledDataSourceConfiguration 配置类

2.3 引入 PooledDataSourceConfiguration 配置类

代码语言:javascript
复制
@Configuration
//满足其中的任意一个:1)有spring.datasource.type属性 2)满足PooledDataSourceAvailableCondition:项目中引入了数据源依赖
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
//如果满足上面条件,就解析一下几个配置类(注意顺序,hikari优先)
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
      DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
      DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {}

 //继承了 AnyNestedCondition 的类,会对这个类中的所有内部类(不一定非得是静态内部类)上的注解做匹配,只要其中有一个匹配了,就匹配了
//说明:如果没有spring.datasource.type属性,就默认查看项目中有没有引入:hikari,tomcat,dbcp2。这样说明如果项目中exclude了这3个,那么就必须使用 spring.datasource.type来指定数据库连接池了
//type 属性优先级比较低,是在找不到,就通过 DataSourceConfiguration.Generic.class 类,来根据 type 属性配置
static class PooledDataSourceCondition extends AnyNestedCondition {
   PooledDataSourceCondition() {
      //因为 AnyNestedCondition 实现了 ConfigurationCondition,所以要设置 这个属性
      //这个属性在 sholudSkip() 方法中会用到,如果这个属性是 REGISTER_BEAN 的话,在生成 configClass 阶段就不会进行匹配过滤,要等到 loadBeanDefintion 的时候,在进行过滤
      //因为类中的静态内部类,都被 @ConditionalOnProperty 注解,这些注解都是在 configClass 阶段做匹配的,所以要设置为 PARSE_CONFIGURATION
      //如果这里设置为 REGISTER_BEAN,但是内部有应该在 configClass 阶段做匹配的,就不符合整体思想了(这样本应该在 configClass 阶段就做匹配的,延迟到了 loadBeanDefintion 阶段),就可能能出现莫名其妙的问题。
      //进一步思考:继承了 AnyNestedCondition 的子类中,不应该同时存在 configClass 阶段做匹配和在 loadBeanDefintion 阶段匹配的
      super(ConfigurationPhase.PARSE_CONFIGURATION);
   }
   //条件一:是否配置了 spring.datasource.type 属性
   @ConditionalOnProperty(prefix = "spring.datasource", name = "type")
   static class ExplicitType {
   }
   //条件二:项目中是否引入了数据源依赖(如,hikari)
   @Conditional(PooledDataSourceAvailableCondition.class)
   static class PooledDataSourceAvailable {
   }
}
static class PooledDataSourceAvailableCondition extends SpringBootCondition {
  @Override
  public ConditionOutcome getMatchOutcome(ConditionContext context,
        AnnotatedTypeMetadata metadata) {
     //这个类只是用来传递消息的
     ConditionMessage.Builder message = ConditionMessage
           .forCondition("PooledDataSource");
      //getDataSourceClassLoader(context):内部做class.forName来找项目中的相关class,找到了就不为null啦,一般肯定能找到的,在org.springframework.boot:spring-boot-starter-jdbc中就已经引入了 hikariDatabase,而在 spring.boot:mybatis-spring-boot-starter中引入了 jdbc!
     if (getDataSourceClassLoader(context) != null) {
        return ConditionOutcome
              .match(message.foundExactly("supported DataSource"));
     }
     return ConditionOutcome
           .noMatch(message.didNotFind("supported DataSource").atAll());
  }
  private ClassLoader getDataSourceClassLoader(ConditionContext context) {
      //在DataSourceBuilder中有个关键的 findType方法来按:hikari,tomcat,dbcp2顺序查找,一查到就返回
     Class<?> dataSourceClass = DataSourceBuilder
           .findType(context.getClassLoader());
     return (dataSourceClass != null) ? dataSourceClass.getClassLoader() : null;
  }
}

导入 DataSourcePoolMetadataProvidersConfiguration 配置类

2.5 导入 DataSourcePoolMetadataProvidersConfiguration 配置类

代码语言:javascript
复制
//为数据源注册一个DataSourcePoolMetadataProvider实例,这个实例主要用于获取内置数据源的一些状态
@Configuration
public class DataSourcePoolMetadataProvidersConfiguration {
    @Configuration
    @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
    static class TomcatDataSourcePoolMetadataProviderConfiguration {...}
    @Configuration
    @ConditionalOnClass(HikariDataSource.class)
    static class HikariPoolDataSourceMetadataProviderConfiguration {
        @Bean
        public DataSourcePoolMetadataProvider hikariPoolDataSourceMetadataProvider() {
            return (dataSource) -> {
                HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(dataSource,
                        HikariDataSource.class);
                if (hikariDataSource != null) {
                    //这里就返回了一个HikariDataSourcePoolMetadata实例,算是代理数据源吧
                    return new HikariDataSourcePoolMetadata(hikariDataSource);
                }
                return null;
            };
        }
    }
    @Configuration
    @ConditionalOnClass(BasicDataSource.class)
    static class CommonsDbcp2PoolDataSourceMetadataProviderConfiguration {...}
}
//函数式接口,可以这样声明
//DataSourcePoolMetadataProvider dpmp = dataSource->{...,return 一个DataSourcePoolMetadata实例}
@FunctionalInterface
public interface DataSourcePoolMetadataProvider {
    //返回能够管理指定数据源的DataSourcePoolMetadata实例,如果无法处理给定的数据源,则返回空值。
    DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource);
}
//每个都可能返回null,如果数据源没有提供相应的信息
public interface DataSourcePoolMetadata {
    //返回池的使用情况,值介于0和1之间(如果池不受限制,则返回-1),1表示所有的都分配了
    Float getUsage();
    //返回从数据源分配的当前活动连接数
    Integer getActive();
    //返回可同时分配的最大活动连接数,无限制的话返回-1
    Integer getMax();
    //返回池中空闲连接的最小数目
    Integer getMin();
    //返回用于验证连接是否有效的查询
    String getValidationQuery();
    //此池创建的连接的默认自动提交状态
    Boolean getDefaultAutoCommit();
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 落叶飞翔的蜗牛 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • DataSourceAutoConfiguration 解析(1)
  • 初始化 DataSourceProperties 配置文件
  • 引入 EmbeddedDatabaseConfiguration 配置类
  • 引入 PooledDataSourceConfiguration 配置类
  • 导入 DataSourcePoolMetadataProvidersConfiguration 配置类
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档