1 前言
上节我们看了看,SpringBoot 启动后都有哪些线程,看到有一部分是关于数据源连接池的,那么这节我们就看看数据源连接池都是如何工作的。
我们本节就从这两个问题看起:
(1)连接池是如何创建的,也就是什么时候创建的呢?
(2)连接是什么时候放进连接池的?是创建完就初始化了一批新的连接,还是等获取的时候才开始创建?或者说获取连接的过程是什么?
2 实践
2.1 连接池的创建时机
老生常谈,SpringBoot 直接自动装配找起:
进到 DataSourceAutoConfiguration 看看,有两个关键的要素:
(1)DataSourceInitializationConfiguration 数据源初始化配置类(这个就是创建连接池的入口)
(2)5种类型的数据源配置:hikari、tomcat、dbcp、generic、jmx
看到这里,我们思考一个问题,我们可以看到会引入 5种类型的数据源,那么为什么 SpringBoot 默认的数据源是 Hikari 的呢,怎么做到的呢?
这个就看到这。
我们接着看 DataSourceInitializationConfiguration :
@Configuration(proxyBeanMethods = false) // 引入了两个类 一个 processor 用于引入后边的 invoker 一个 invoker 就是用来初始化连接池的 @Import({ DataSourceInitializerInvoker.class, DataSourceInitializationConfiguration.Registrar.class }) class DataSourceInitializationConfiguration { /** * {@link ImportBeanDefinitionRegistrar} to register the * {@link DataSourceInitializerPostProcessor} without causing early bean instantiation * issues. */ static class Registrar implements ImportBeanDefinitionRegistrar { private static final String BEAN_NAME = "dataSourceInitializerPostProcessor"; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 注入 Processor if (!registry.containsBeanDefinition(BEAN_NAME)) { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); // We don't need this one to be post processed otherwise it can cause a // cascade of bean instantiation that we would rather avoid. beanDefinition.setSynthetic(true); registry.registerBeanDefinition(BEAN_NAME, beanDefinition); } } } }
那我们继续看看 DataSourceInitializerPostProcessor:
/** * {@link BeanPostProcessor} used to ensure that {@link DataSourceInitializer} is * initialized as soon as a {@link DataSource} is. * Bean 的后置处理器 也就是在 Bean的生命周期最后一步初始化里调用到 后置处理器 * @author Dave Syer */ class DataSourceInitializerPostProcessor implements BeanPostProcessor, Ordered { @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE + 1; } @Autowired private BeanFactory beanFactory; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 如果当前 Bean 是数据源类型的就进行获取,也就是进行创建 if (bean instanceof DataSource) { // force initialization of this bean as soon as we see a DataSource this.beanFactory.getBean(DataSourceInitializerInvoker.class); } return bean; } }
那看到这里,我们上边的默认数据源是 Hikari,那么就是在创建这个数据源的时候,进入到这里的 Processor的,是不是这样呢?我们 debug看看,确实如此哈。
那我们继续看看 DataSourceInitializerInvoker:
package org.springframework.boot.autoconfigure.jdbc; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.core.log.LogMessage; /** * Bean to handle {@link DataSource} initialization by running {@literal schema-*.sql} on * {@link InitializingBean#afterPropertiesSet()} and {@literal data-*.sql} SQL scripts on * a {@link DataSourceSchemaCreatedEvent}. * 监听事件以及实现了 InitializingBean初始化 Bean * @author Stephane Nicoll * @see DataSourceAutoConfiguration */ class DataSourceInitializerInvoker implements ApplicationListener<DataSourceSchemaCreatedEvent>, InitializingBean { private static final Log logger = LogFactory.getLog(DataSourceInitializerInvoker.class); private final ObjectProvider<DataSource> dataSource; private final DataSourceProperties properties; private final ApplicationContext applicationContext; private DataSourceInitializer dataSourceInitializer; private boolean initialized; DataSourceInitializerInvoker(ObjectProvider<DataSource> dataSource, DataSourceProperties properties, ApplicationContext applicationContext) { this.dataSource = dataSource; this.properties = properties; this.applicationContext = applicationContext; } @Override public void afterPropertiesSet() { DataSourceInitializer initializer = getDataSourceInitializer(); if (initializer != null) { boolean schemaCreated = this.dataSourceInitializer.createSchema(); if (schemaCreated) { initialize(initializer); } } } private void initialize(DataSourceInitializer initializer) { try { this.applicationContext.publishEvent(new DataSourceSchemaCreatedEvent(initializer.getDataSource())); // The listener might not be registered yet, so don't rely on it. if (!this.initialized) { this.dataSourceInitializer.initSchema(); this.initialized = true; } } catch (IllegalStateException ex) { logger.warn(LogMessage.format("Could not send event to complete DataSource initialization (%s)", ex.getMessage())); } } @Override public void onApplicationEvent(DataSourceSchemaCreatedEvent event) { // NOTE the event can happen more than once and // the event datasource is not used here DataSourceInitializer initializer = getDataSourceInitializer(); if (!this.initialized && initializer != null) { initializer.initSchema(); this.initialized = true; } } private DataSourceInitializer getDataSourceInitializer() { if (this.dataSourceInitializer == null) { DataSource ds = this.dataSource.getIfUnique(); if (ds != null) { this.dataSourceInitializer = new DataSourceInitializer(ds, this.properties, this.applicationContext); } } return this.dataSourceInitializer; } }
我们可以看到该类(1)监听 DataSourceSchemaCreatedEvent 事件 (2)实现了 InitializingBean,那我们刚才在 Processor 里看到的 getBean 也只是创建出来 DataSourceInitializerInvoker 这个对象,并没有进行实际的连接池的创建操作。
莫非是在监听到事件或者执行 InitializingBean 的时候创建的?其实不是,但也不能说绝对不是,因为我发现连接池的创建是在你第一次获取连接的时候,创建出来的。DataSourceInitializerInvoker 能帮我们执行一些数据库的脚本,当你有脚本执行的话,他就会获取连接了,也就会初始化连接池了哈。
我是如何看到连接池是在第一次获取连接的时候,创建的呢?这里我是通过看日志以及 debug 看的。
看启动日志,大家心细的会发现,数据源启动的时候,会有几行日志:
那么谁第一次获取连接的呢?我看线程的方法链路看又是 SpringBoot 的监控,又是 actuator 那么监控包,看 jdbc 连接的健康情况过来的,这玩意看起来还挺重要。
好,那我们第一个问题看下来,首先是自动装配,引入我们的数据源,并且注入了数据源的一个 Processor ,可以用于数据库脚本的启动执行。而数据库连接池的创建是落在第一次获取连接的时候,进行创建的,接下来,我们就看一下连接的获取过程,顺便就把连接池的创建也看了哈。
2.2 连接池的创建
不知道大家看过我之前的数据源的连接协同以及在动态数据源下的连接协同,其实连接的获取都是从数据源获取的,那我们这里就直接从 HikariDataSource 的 getConnection 看起:
/** 获取连接 */ @Override public Connection getConnection() throws SQLException{ // 已经关闭的话 直接报异常 if (isClosed()) { throw new SQLException("HikariDataSource " + this + " has been closed."); } // HikariDataSource 的数据源的带参的构造方法中会直接创建连接池并赋值给 fastPathPool 和 pool if (fastPathPool != null) { return fastPathPool.getConnection(); } // 双重检查 HikariPool result = pool; if (result == null) { synchronized (this) { result = pool; if (result == null) { // 配置以及参数校验 validate(); // 获取连接池名字 并打印日志 LOGGER.info("{} - Starting...", getPoolName()); try { // 创建连接池 pool = result = new HikariPool(this); // 标记 seal 表示已经创建过,防止多次创建 this.seal(); } catch (PoolInitializationException pie) { if (pie.getCause() instanceof SQLException) { throw (SQLException) pie.getCause(); } else { throw pie; } } // 打印日志 LOGGER.info("{} - Start completed.", getPoolName()); } } } // 从连接池中获取连接 return result.getConnection(); }
可以看到大概分三步:
(1)配置以及参数的校验
(2)创建连接池
(3)获取连接
3 小结
好啦,本节就看到这里,关于参数的校验以及连接池的创建,我发现细节还挺多,本节就暂时写到这里,内容太多容易造成疲惫,我们下节再来看这两点哈。本节我们先对连接池的创建时机以及创建过程,有个大概的了解,下节我们继续,有理解错误的地方还请欢迎指正哈。
标签:SpringBoot,数据源,private,class,创建,连接,连接池 From: https://www.cnblogs.com/kukuxjx/p/18149340