SpringBoot data source injection principle

1. SpringBoot data source injection principle

We know that after spring.datasource is configured in application.yaml, the data source can be injected, but why?

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    username: root
    password: root

(1) Default data source

In the spring boot autoconfigure package of SpringBoot, there is a file named spring.factories under the META-INF folder

Open this file and you can see that org.springframework.boot.autoconfigure.EnableAutoConfiguration is configured

This is an annotation for SpringBoot auto assembly

You should understand that spring.factories is the spi provided by SpringBoot
SpringBoot provides annotations that will automatically assemble all beans configured here.

A configuration can be found in spring.factories: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

This is the key to data source injection.

In DataSourceAutoConfiguration, you can see that two types of data sources are supported by default:
EmbeddedDatabaseConfiguration (embedded database) and PooledDataSourceConfiguration (pooled data source).

    @Configuration(proxyBeanMethods = false)
    @Conditional(EmbeddedDatabaseCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import(EmbeddedDataSourceConfiguration.class)
    protected static class EmbeddedDatabaseConfiguration {

    }

    @Configuration(proxyBeanMethods = false)
    @Conditional(PooledDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
            DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
            DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
    protected static class PooledDataSourceConfiguration {

    }

You can see that the @ Conditional annotation indicates the injection conditions. The detailed code will not be posted. You can read it yourself
The condition of pooled data source is that spring.datasource.type is configured or PooledDataSourceAvailableCondition is met. This condition is to load the following classes through the class loader of the following classes:

com.zaxxer.hikari.HikariDataSource
org.apache.tomcat.jdbc.pool.DataSource
org.apache.commons.dbcp2.BasicDataSource
 existence oracle.jdbc.OracleConnection of oracle.ucp.jdbc.PoolDataSourceImpl

The condition of embedded database is that spring.datasource.url is not configured first. In addition, the condition of pooled data source is not met, that is, whether the condition of pooled data source is matched will be judged. If not, the embedded database will be created
The embedded database supports H2, DERBY, HSQL and other types.

(2) Druid data source

We configured spring.datasource.type as DruidDataSource in the initial application.yaml, indicating that the Druid data source is used
You can see the following code in DruidDataSourceAutoConfigure:

@Configuration
@ConditionalOnClass(DruidDataSource.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class,
    DruidStatViewServletConfiguration.class,
    DruidWebStatFilterConfiguration.class,
    DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {

    private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);

    @Bean(initMethod = "init")
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        LOGGER.info("Init DruidDataSource");
        return new DruidDataSourceWrapper();
    }
}

@ConditionalOnClass indicates that there is a DruidDataSource, and the configuration will take effect.

@AutoConfigureBefore indicates that autoconfiguration takes effect before DataSourceAutoConfiguration.
DataSourceAutoConfiguration is the key class of data source automatic assembly mentioned above, which indicates that the Druid data source will be assembled first before the default data source assembly of SpringBoot
The @ ConditionalOnMissingBean annotation in DataSourceAutoConfiguration indicates that if the data source has been assembled, the default data source will not be assembled

@EnableConfigurationProperties enables the configurations in DruidStatProperties and DataSourceProperties to take effect. Click in to find their configuration prefixes:

@ConfigurationProperties("spring.datasource.druid")
public class DruidStatProperties {
    // Omit code
}

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
    // Omit code
}

@ConditionalOnMissingBean indicates that Druid data source will be injected only when there is no Bean of dataSource in the container. In other words, if we manually inject a dataSource, we will not create the data source of Druid.

@Import injects four beans to realize the monitoring, statistics and other functions of Druid data source.

@The ConditionalOnMissingBean annotation indicates that if the data source has been assembled, the Druid data source will not be assembled

Finally, a DruidDataSourceWrapper is return ed to inject the Durid data source into the container through the @ Bean annotation.

2. A related small problem

If datasource is configured in application.yaml, and then datasource is manually injected, which configuration will prevail?

Let's draw a conclusion first. If the Bean dataSource is injected manually, it will only take effect for the unconfigured attributes in application.yaml, and other attributes will be subject to the configured value.

Why?

We know that creating a Bean is divided into two stages: instantiation and initialization
When instantiating the dataSource Bean (that is, the createBeanInstance method in the doCreateBean method), the data source property will be set once, that is, the property set during manual injection.

When initializing the dataSource (that is, the initializeBean method in the doCreateBean method), the applyBeanPostProcessorsBeforeInitialization method will be called first to traverse the postProcessBeforeInitialization method to realize pre-processing.

The ConfigurationPropertiesBindingPostProcessor method will bind the property and assign the property set in application.yaml to the dataSource.

Therefore, the attribute values in the finally generated dataSource are the attributes configured in application.yaml and the attributes not overwritten in @ Bean during manual injection.

Write a simple code to verify:
First configure the properties in application.yaml, then manually inject the dataSource and modify the properties

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
@Configuration
public class DruidStarterConfig {
    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource druidDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        // The name attribute is not configured in application.yaml
        druidDataSource.setName("aaa");
        druidDataSource.setUsername("bbb");
        druidDataSource.setPassword("ccc");
        druidDataSource.setUrl("ddd");
        druidDataSource.setDriverClassName("eee");
        return druidDataSource;
    }
}

Print out the properties of the data source

@SpringBootTest
@RunWith(SpringRunner.class)
public class DruidStarterConfigTest {

    @Resource
    private DataSource dataSource;

    @Test
    public void test() throws SQLException {
        DruidDataSource druidDataSource = (DruidDataSource) dataSource;
        System.out.println(druidDataSource.getDriverClassName());
        System.out.println(druidDataSource.getUrl());
        System.out.println(druidDataSource.getUsername());
        System.out.println(druidDataSource.getPassword());
        System.out.println(druidDataSource.getName());
    }
}

Operation results:

You can see that except for the last name attribute, other attributes are still the values configured in application.yaml.

Reference acknowledgment

Built in database and pooled data source: https://developer.51cto.com/a...

Tags: Druid Spring Boot

Posted on Tue, 30 Nov 2021 16:23:08 -0500 by todd-imc