Talk about the property binding of spring boottomcat jdbpool.

  jdbc

Order

This article mainly studies the property binding of spring boot tomcat jdbc pool.

Incorrect configuration

spring:
  datasource:
    type: org.apache.tomcat.jdbc.pool.DataSource
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://192.168.99.100:5432/postgres?connectTimeout=6000&socketTimeout=6000
    username: postgres
    password: postgres
    jmx-enabled: true
    initial-size: 1
    max-active: 5
    ## when pool sweeper is enabled, extra idle connection will be closed
    max-idle: 5
    ## when idle connection > min-idle, poolSweeper will start to close
    min-idle: 1

Using the above configuration, it is finally found that the initial-size, max-active, max-idle, min-idle and other configurations are invalid, and the generated tomcat jdbc datasource is still the default configuration used.

Proper configuration

spring:
  datasource:
    type: org.apache.tomcat.jdbc.pool.DataSource
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://192.168.99.100:5432/postgres?connectTimeout=6000&socketTimeout=6000
    username: postgres
    password: postgres
    jmx-enabled: true
    tomcat:  ## 单个数据库连接池,而且得写上tomcat的属性配置才可以生效
      initial-size: 1
      max-active: 5
      ## when pool sweeper is enabled, extra idle connection will be closed
      max-idle: 5
      ## when idle connection > min-idle, poolSweeper will start to close
      min-idle: 1

Note that the configuration attribute of the specific tomcat database connection pool is put under the spring.datasource.tomcat attribute, so that it can take effect.

Source code analysis

spring-boot-autoconfigure-1.5.9.RELEASE-sources.jar! /org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java

    @Configuration
    @Conditional(PooledDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import({ DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Hikari.class,
            DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class,
            DataSourceConfiguration.Generic.class })
    @SuppressWarnings("deprecation")
    protected static class PooledDataSourceConfiguration {

    }

DataSourceConfiguration.Tomcat

spring-boot-autoconfigure-1.5.9.RELEASE-sources.jar! /org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java

    /**
     * Tomcat Pool DataSource configuration.
     */
    @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
    @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource", matchIfMissing = true)
    static class Tomcat extends DataSourceConfiguration {

        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.tomcat")
        public org.apache.tomcat.jdbc.pool.DataSource dataSource(
                DataSourceProperties properties) {
            org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(
                    properties, org.apache.tomcat.jdbc.pool.DataSource.class);
            DatabaseDriver databaseDriver = DatabaseDriver
                    .fromJdbcUrl(properties.determineUrl());
            String validationQuery = databaseDriver.getValidationQuery();
            if (validationQuery != null) {
                dataSource.setTestOnBorrow(true);
                dataSource.setValidationQuery(validationQuery);
            }
            return dataSource;
        }

    }

You can see that the DataSourceProperties here are only the configuration of the spring.datasource direct properties, such as url, username, password, driverClassName. Tomcat has no specific attributes.

createDataSource

protected <T> T createDataSource(DataSourceProperties properties,
            Class<? extends DataSource> type) {
        return (T) properties.initializeDataSourceBuilder().type(type).build();
    }

The PoolProperties of org.apache.tomcat.jdbc.pool.datasource, which comes directly from createDataSource, are also the default configuration.

ConfigurationProperties

The magic lies in @ configurationproperties (prefix = “spring.datasource.tomcat”), which sets the attribute specified by spring.datasource.tomcat to org.Apache.tomcat.JDBC.pool.datasource before the spring container constructs a proxy bean to return.

spring-boot-1.5.9.RELEASE-sources.jar! /org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java

private void postProcessBeforeInitialization(Object bean, String beanName,
            ConfigurationProperties annotation) {
        Object target = bean;
        PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(
                target);
        factory.setPropertySources(this.propertySources);
        factory.setValidator(determineValidator(bean));
        // If no explicit conversion service is provided we add one so that (at least)
        // comma-separated arrays of convertibles can be bound automatically
        factory.setConversionService(this.conversionService == null
                ? getDefaultConversionService() : this.conversionService);
        if (annotation != null) {
            factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
            factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
            factory.setExceptionIfInvalid(annotation.exceptionIfInvalid());
            factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties());
            if (StringUtils.hasLength(annotation.prefix())) {
                factory.setTargetName(annotation.prefix());
            }
        }
        try {
            factory.bindPropertiesToTarget();
        }
        catch (Exception ex) {
            String targetClass = ClassUtils.getShortName(target.getClass());
            throw new BeanCreationException(beanName, "Could not bind properties to "
                    + targetClass + " (" + getAnnotationDetails(annotation) + ")", ex);
        }
    }

Note that the annotation here is @ configurationproperties (prefix = “spring.datasource.tomcat”), whose prefix is spring.datasource.tomcat.
The targetName of PropertiesConfigurationFactory is spring.datasource.tomcat

PropertiesConfigurationFactory.bindPropertiesToTarget

spring-boot-1.5.9.RELEASE-sources.jar! /org/springframework/boot/bind/PropertiesConfigurationFactory.java

    public void bindPropertiesToTarget() throws BindException {
        Assert.state(this.propertySources != null, "PropertySources should not be null");
        try {
            if (logger.isTraceEnabled()) {
                logger.trace("Property Sources: " + this.propertySources);

            }
            this.hasBeenBound = true;
            doBindPropertiesToTarget();
        }
        catch (BindException ex) {
            if (this.exceptionIfInvalid) {
                throw ex;
            }
            PropertiesConfigurationFactory.logger
                    .error("Failed to load Properties validation bean. "
                            + "Your Properties may be invalid.", ex);
        }
    }

Delegate to doBindPropertiesToTarget Method

PropertiesConfigurationFactory.doBindPropertiesToTarget

private void doBindPropertiesToTarget() throws BindException {
        RelaxedDataBinder dataBinder = (this.targetName != null
                ? new RelaxedDataBinder(this.target, this.targetName)
                : new RelaxedDataBinder(this.target));
        if (this.validator != null
                && this.validator.supports(dataBinder.getTarget().getClass())) {
            dataBinder.setValidator(this.validator);
        }
        if (this.conversionService != null) {
            dataBinder.setConversionService(this.conversionService);
        }
        dataBinder.setAutoGrowCollectionLimit(Integer.MAX_VALUE);
        dataBinder.setIgnoreNestedProperties(this.ignoreNestedProperties);
        dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
        dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
        customizeBinder(dataBinder);
        Iterable<String> relaxedTargetNames = getRelaxedTargetNames();
        Set<String> names = getNames(relaxedTargetNames);
        PropertyValues propertyValues = getPropertySourcesPropertyValues(names,
                relaxedTargetNames);
        dataBinder.bind(propertyValues);
        if (this.validator != null) {
            dataBinder.validate();
        }
        checkForBindingErrors(dataBinder);
    }

Bind method

getRelaxedTargetNames

    private Iterable<String> getRelaxedTargetNames() {
        return (this.target != null && StringUtils.hasLength(this.targetName)
                ? new RelaxedNames(this.targetName) : null);
    }

New here is a RelaxedNames, which can identify variants of multiple variables.

RelaxedNames

spring-boot-1.5.9.RELEASE-sources.jar! /org/springframework/boot/bind/RelaxedNames.java

    private void initialize(String name, Set<String> values) {
        if (values.contains(name)) {
            return;
        }
        for (Variation variation : Variation.values()) {
            for (Manipulation manipulation : Manipulation.values()) {
                String result = name;
                result = manipulation.apply(result);
                result = variation.apply(result);
                values.add(result);
                initialize(result, values);
            }
        }
    }

    /**
     * Name variations.
     */
    enum Variation {

        NONE {

            @Override
            public String apply(String value) {
                return value;
            }

        },

        LOWERCASE {

            @Override
            public String apply(String value) {
                return value.isEmpty() ? value : value.toLowerCase();
            }

        },

        UPPERCASE {

            @Override
            public String apply(String value) {
                return value.isEmpty() ? value : value.toUpperCase();
            }

        };

        public abstract String apply(String value);

    }

It supports org.springframework.boot.bind.relatednames @ 6ef81f31 [name = spring.datasource.tomcat, values = [spring.datasource.tomcat, spring _ datasource _ tomcat, SpringDataSourceToCAT, SpringDataSourceToCAT, Spring.datasource.tomcat, Spring _ datasource _ tomcat, SpringDataSourceToCAT]] are written in this 7 configuration.

getPropertySourcesPropertyValues

    private PropertyValues getPropertySourcesPropertyValues(Set<String> names,
            Iterable<String> relaxedTargetNames) {
        PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names,
                relaxedTargetNames);
        return new PropertySourcesPropertyValues(this.propertySources, names, includes,
                this.resolvePlaceholders);
    }

This method will pull the attribute configuration under spring.datasource.tomact into the PropertyValues object.

RelaxedDataBinder.bind

spring-boot-1.5.9.RELEASE-sources.jar! /org/springframework/boot/bind/relateddatabinder.java’s bind method calls the parent class’s method
spring-context-4.3.13.RELEASE-sources.jar! /org/springframework/validation/DataBinder.java

    /**
     * Bind the given property values to this binder's target.
     * <p>This call can create field errors, representing basic binding
     * errors like a required field (code "required"), or type mismatch
     * between value and bean property (code "typeMismatch").
     * <p>Note that the given PropertyValues should be a throwaway instance:
     * For efficiency, it will be modified to just contain allowed fields if it
     * implements the MutablePropertyValues interface; else, an internal mutable
     * copy will be created for this purpose. Pass in a copy of the PropertyValues
     * if you want your original instance to stay unmodified in any case.
     * @param pvs property values to bind
     * @see #doBind(org.springframework.beans.MutablePropertyValues)
     */
    public void bind(PropertyValues pvs) {
        MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues) ?
                (MutablePropertyValues) pvs : new MutablePropertyValues(pvs);
        doBind(mpvs);
    }

    /**
     * Actual implementation of the binding process, working with the
     * passed-in MutablePropertyValues instance.
     * @param mpvs the property values to bind,
     * as MutablePropertyValues instance
     * @see #checkAllowedFields
     * @see #checkRequiredFields
     * @see #applyPropertyValues
     */
    protected void doBind(MutablePropertyValues mpvs) {
        checkAllowedFields(mpvs);
        checkRequiredFields(mpvs);
        applyPropertyValues(mpvs);
    }

    /**
     * Apply given property values to the target object.
     * <p>Default implementation applies all of the supplied property
     * values as bean property values. By default, unknown fields will
     * be ignored.
     * @param mpvs the property values to be bound (can be modified)
     * @see #getTarget
     * @see #getPropertyAccessor
     * @see #isIgnoreUnknownFields
     * @see #getBindingErrorProcessor
     * @see BindingErrorProcessor#processPropertyAccessException
     */
    protected void applyPropertyValues(MutablePropertyValues mpvs) {
        try {
            // Bind request parameters onto target object.
            getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
        }
        catch (PropertyBatchUpdateException ex) {
            // Use bind error processor to create FieldErrors.
            for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
                getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
            }
        }
    }

    /**
     * Return the underlying PropertyAccessor of this binder's BindingResult.
     */
    protected ConfigurablePropertyAccessor getPropertyAccessor() {
        return getInternalBindingResult().getPropertyAccessor();
    }

Finally, it is set by getPropertyAccessor (). This propertyAccessor is org.springframework.boot.bind.relateddatabinder $ relatedbedeanwrapper: wrappingobject [org.Apache.tomcat.JDBC.pool.datasource @ 6a84bc2a], which is also packaged in org.Apache.tomcat.JDBC.pool.datasource.

AbstractPropertyAccessor.setPropertyValues

spring-beans-4.3.13.RELEASE-sources.jar! /org/springframework/beans/AbstractPropertyAccessor.java

    @Override
    public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
            throws BeansException {

        List<PropertyAccessException> propertyAccessExceptions = null;
        List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
                ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
        for (PropertyValue pv : propertyValues) {
            try {
                // This method may throw any BeansException, which won't be caught
                // here, if there is a critical failure such as no matching field.
                // We can attempt to deal only with less serious exceptions.
                setPropertyValue(pv);
            }
            catch (NotWritablePropertyException ex) {
                if (!ignoreUnknown) {
                    throw ex;
                }
                // Otherwise, just ignore it and continue...
            }
            catch (NullValueInNestedPathException ex) {
                if (!ignoreInvalid) {
                    throw ex;
                }
                // Otherwise, just ignore it and continue...
            }
            catch (PropertyAccessException ex) {
                if (propertyAccessExceptions == null) {
                    propertyAccessExceptions = new LinkedList<PropertyAccessException>();
                }
                propertyAccessExceptions.add(ex);
            }
        }

        // If we encountered individual exceptions, throw the composite exception.
        if (propertyAccessExceptions != null) {
            PropertyAccessException[] paeArray =
                    propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]);
            throw new PropertyBatchUpdateException(paeArray);
        }
    }

    @Override
    public void setPropertyValue(PropertyValue pv) throws BeansException {
        PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
        if (tokens == null) {
            String propertyName = pv.getName();
            AbstractNestablePropertyAccessor nestedPa;
            try {
                nestedPa = getPropertyAccessorForPropertyPath(propertyName);
            }
            catch (NotReadablePropertyException ex) {
                throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
                        "Nested property in path '" + propertyName + "' does not exist", ex);
            }
            tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
            if (nestedPa == this) {
                pv.getOriginalPropertyValue().resolvedTokens = tokens;
            }
            nestedPa.setPropertyValue(tokens, pv);
        }
        else {
            setPropertyValue(tokens, pv);
        }
    }

Here’s nestedpa.setpropertyvalue (tokens, pv); Really set the attribute value of spring.datasource.tomcat into it.
NestedPa here is org.springframework.boot.bind.relateddatabinder $ relatedbedeanwrapper: wrappingobject [org.Apache.Tomcat.jdbc.pool.datasource @ 6a84bc2a]
Finally, call AbstractNestablePropertyAccessor. ProcessLocalProperty

AbstractNestablePropertyAccessor.processLocalProperty

spring-beans-4.3.13.RELEASE-sources.jar! /org/springframework/beans/AbstractNestablePropertyAccessor.java

private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
        PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
        if (ph == null || !ph.isWritable()) {
            if (pv.isOptional()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Ignoring optional value for property '" + tokens.actualName +
                            "' - property not found on bean class [" + getRootClass().getName() + "]");
                }
                return;
            }
            else {
                throw createNotWritablePropertyException(tokens.canonicalName);
            }
        }

        Object oldValue = null;
        try {
            Object originalValue = pv.getValue();
            Object valueToApply = originalValue;
            if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
                if (pv.isConverted()) {
                    valueToApply = pv.getConvertedValue();
                }
                else {
                    if (isExtractOldValueForEditor() && ph.isReadable()) {
                        try {
                            oldValue = ph.getValue();
                        }
                        catch (Exception ex) {
                            if (ex instanceof PrivilegedActionException) {
                                ex = ((PrivilegedActionException) ex).getException();
                            }
                            if (logger.isDebugEnabled()) {
                                logger.debug("Could not read previous value of property '" +
                                        this.nestedPath + tokens.canonicalName + "'", ex);
                            }
                        }
                    }
                    valueToApply = convertForProperty(
                            tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
                }
                pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
            }
            ph.setValue(this.wrappedObject, valueToApply);
        }
        catch (TypeMismatchException ex) {
            throw ex;
        }
        catch (InvocationTargetException ex) {
            PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
                    this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
            if (ex.getTargetException() instanceof ClassCastException) {
                throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
            }
            else {
                Throwable cause = ex.getTargetException();
                if (cause instanceof UndeclaredThrowableException) {
                    // May happen e.g. with Groovy-generated methods
                    cause = cause.getCause();
                }
                throw new MethodInvocationException(propertyChangeEvent, cause);
            }
        }
        catch (Exception ex) {
            PropertyChangeEvent pce = new PropertyChangeEvent(
                    this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
            throw new MethodInvocationException(pce, ex);
        }
    }

It makes it set using classorg.spring framework.beans.beanwrapperimpl $ beanpropertyhandler

BeanWrapperImpl$BeanPropertyHandler.setValue

spring-beans-4.3.13.RELEASE-sources.jar! /org/springframework/beans/BeanWrapperImpl.java

        @Override
        public void setValue(final Object object, Object valueToApply) throws Exception {
            final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
                    ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
                    this.pd.getWriteMethod());
            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) {
                if (System.getSecurityManager() != null) {
                    AccessController.doPrivileged(new PrivilegedAction<Object>() {
                        @Override
                        public Object run() {
                            writeMethod.setAccessible(true);
                            return null;
                        }
                    });
                }
                else {
                    writeMethod.setAccessible(true);
                }
            }
            final Object value = valueToApply;
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                        @Override
                        public Object run() throws Exception {
                            writeMethod.invoke(object, value);
                            return null;
                        }
                    }, acc);
                }
                catch (PrivilegedActionException ex) {
                    throw ex.getException();
                }
            }
            else {
                writeMethod.invoke(getWrappedInstance(), value);
            }
        }
    }

Here, reflection is used to find the setXXX method (For example setMaxActive), and then set it in

Configuration of Multiple Data Sources

The above configuration is no problem for single data source, and for multiple data sources, the configuration is as follows

@Configuration
public class MasterDatasourceConfig {

    @Bean("masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }
}    

Note that additional settings for ConfigurationProperties injection into tomcat jdbc pool are to be added here

spring:
  datasource:
    master:
      type: org.apache.tomcat.jdbc.pool.DataSource
      driver-class-name: org.postgresql.Driver
      url: jdbc:postgresql://192.168.99.100:5432/postgres?connectTimeout=6000&socketTimeout=6000
      username: postgres
      password: postgres
      jmx-enabled: true
#    tomcat: ## 多数据源的话,这里要去掉tomcat,通通放在数据源前缀下面
      initial-size: 1
      max-active: 5
      ## when pool sweeper is enabled, extra idle connection will be closed
      max-idle: 5
      ## when idle connection > min-idle, poolSweeper will start to close
      min-idle: 1

The original tomcat configuration should be placed under the prefix of the data source, and neither spring.datasource.tomcat nor Spring.datasource.master.Tomcat can take effect.

Summary

The automatic configuration of spirngboot is quite convenient, but in the actual application scenario, it is only possible to understand the underlying mechanism, otherwise it is easy to come up with the false impression of configuration, believing that the configuration is correct and does not actually take effect.

doc