聊聊spring boot tomcat jdbc pool的属性绑定

栏目: Java · 发布时间: 8年前

内容简介:聊聊spring boot tomcat jdbc pool的属性绑定
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还是使用的默认的配置

正确配置

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属性下面,这样才可以生效。

源码解析

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;
		}

	}

可以看到这里的DataSourceProperties仅仅只有spring.datasource直接属性的配置,比如url,username,password,driverClassName。tomcat的具体属性都没有。

createDataSource

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

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

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方法

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方法

getRelaxedTargetNames

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

这里new了一个RelaxedNames,可以识别多个变量的变种

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);

	}

即支持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中配置的写法

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

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

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

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来设置

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 ),然后设置进去

多数据源的配置

上面的配置对于单数据源来说是没有问题的,对于多数据源,则配置如下

@Configuration
public class MasterDatasourceConfig {

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

注意,这里要添加ConfigurationProperties注入tomcat jdbc pool的额外设置

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底下均无法生效。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Building Web Reputation Systems

Building Web Reputation Systems

Randy Farmer、Bryce Glass / Yahoo Press / 2010 / GBP 31.99

What do Amazon's product reviews, eBay's feedback score system, Slashdot's Karma System, and Xbox Live's Achievements have in common? They're all examples of successful reputation systems that enable ......一起来看看 《Building Web Reputation Systems》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具