下面看下spring boot tomcat jdbc pool的属性绑定代码,具体代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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 |
使用如上配置,最后发现initial-size,max-active,max-idle,min-idle等配置均无效,生成的tomcat jdbc datasource还是使用的默认的配置
正确配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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 |
注意,这里把具体tomcat数据库连接池的配置属性放到了spring.datasource.tomcat属性下面,这样才可以生效。
源码解析
1
2
3
4
5
6
7
8
9
10
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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; } } |
可以看到这里的DataSourceProperties仅仅只有spring.datasource直接属性的配置,比如url,username,password,driverClassName。tomcat的具体属性都没有。
createDataSource
1
2
3
4
|
protected <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) { return (T) properties.initializeDataSourceBuilder().type(type).build(); } |
直接createDataSource出来的org.apache.tomcat.jdbc.pool.DataSource的PoolProperties也是默认的配置
ConfigurationProperties
具体的魔力就在于@ConfigurationProperties(prefix = "spring.datasource.tomcat")
这段代码,它在spring容器构造好代理bean返回之前会将spring.datasource.tomcat指定的属性设置到org.apache.tomcat.jdbc.pool.DataSource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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); } } |
注意,这里的annotation就是@ConfigurationProperties(prefix = "spring.datasource.tomcat")
,它的prefix是spring.datasource.tomcat PropertiesConfigurationFactory
的targetName就是spring.datasource.tomcat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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); } } |
委托给doBindPropertiesToTarget方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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); } |
这里借助RelaxedDataBinder.bind方法
1
2
3
4
5
|
getRelaxedTargetNames private Iterable<String> getRelaxedTargetNames() { return ( this .target != null && StringUtils.hasLength( this .targetName) ? new RelaxedNames( this .targetName) : null ); } |
这里new了一个RelaxedNames,可以识别多个变量的变种
RelaxedNames
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
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); } |
即支持org.springframework.boot.bind.RelaxedNames@6ef81f31[name=spring.datasource.tomcat,values=[spring.datasource.tomcat, spring_datasource_tomcat, springDatasourceTomcat, springdatasourcetomcat, SPRING.DATASOURCE.TOMCAT, SPRING_DATASOURCE_TOMCAT, SPRINGDATASOURCETOMCAT]]这7中配置的写法
1
2
3
4
5
6
7
8
|
getPropertySourcesPropertyValues private PropertyValues getPropertySourcesPropertyValues(Set<String> names, Iterable<String> relaxedTargetNames) { PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names, relaxedTargetNames); return new PropertySourcesPropertyValues( this .propertySources, names, includes, this .resolvePlaceholders); } |
这个方法会把spring.datasource.tomact底下的属性配置拉取到PropertyValues对象里头
RelaxedDataBinder.bind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
spring-boot- 1.5 . 9 .RELEASE-sources.jar!/org/springframework/boot/bind/RelaxedDataBinder.java的bind方法调用的是父类的方法 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(); } |
最后通过getPropertyAccessor()来设置,这个propertyAccessor就是org.springframework.boot.bind.RelaxedDataBinder$RelaxedBeanWrapper: wrapping object [org.apache.tomcat.jdbc.pool.DataSource@6a84bc2a],也就包装的org.apache.tomcat.jdbc.pool.DataSource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
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); } } |
这里的nestedPa.setPropertyValue(tokens, pv);真正把spring.datasource.tomcat的属性值设置进去 这里的nestedPa就是org.springframework.boot.bind.RelaxedDataBinder$RelaxedBeanWrapper: wrapping object [org.apache.tomcat.jdbc.pool.DataSource@6a84bc2a] 最后是调用AbstractNestablePropertyAccessor.processLocalProperty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
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); } } |
它使其是使用class org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler来设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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); } } } |
这里利用反射找出setXXX方法( 比如setMaxActive ),然后设置进去
多数据源的配置
上面的配置对于单数据源来说是没有问题的,对于多数据源,则配置如下
1
2
3
4
5
6
7
8
9
|
@Configuration public class MasterDatasourceConfig { @Bean ( "masterDataSource" ) @ConfigurationProperties (prefix = "spring.datasource.master" ) public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } } |
注意,这里要添加ConfigurationProperties注入tomcat jdbc pool的额外设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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 |
原先tomcat的配置都要放在数据源前缀的底下,放在spring.datasource.tomcat或者spring.datasource.master.tomcat底下均无法生效。
原文链接:https://juejin.im/post/5a6eb0f5518825733c144ff4