下面看下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