Поговорите о привязке свойств весеннего пула tomcat jdbc.

Spring Boot задняя часть

последовательность

В этой статье в основном изучается привязка свойств пула jdbc tomcat spring boot.

неправильно сконфигурирован

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

Используя приведенную выше конфигурацию, наконец, было обнаружено, что начальный размер, максимальный активный, максимальный бездействующий, минимальный бездействующий и другие конфигурации были недействительными, а сгенерированный источник данных tomcat jdbc по-прежнему использовался по умолчанию.

правильно настроен

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, имя пользователя, пароль, driverClassName. Особых свойств у tomcat нет.

createDataSource

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

PoolProperties org.apache.tomcat.jdbc.pool.DataSource непосредственно из createDataSource также является конфигурацией по умолчанию.

ConfigurationProperties

Особая магия заключается в коде @ConfigurationProperties(prefix = "spring.datasource.tomcat"), который устанавливает для свойств, указанных в spring.datasource.tomcat, значение org.apache.tomcat до того, как контейнер Spring создаст прокси-компонент и вернет его. .pool. Источник данных

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

Обратите внимание, что аннотация здесь — @ConfigurationProperties (prefix = «spring.datasource.tomcat»), а ее префикс — spring.datasource.tomcat. TargetName PropertiesConfigurationFactory — 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);
	}

Вот новый 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 вызывает метод родительского класса 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: объект-оболочка [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 Здесь вложенным Pa является org.springframework.boot.bind.RelaxedDataBinder$RelaxedBeanWrapper: обертывающий объект [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);
		}
	}

Он устанавливает его с помощью класса 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.

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.

резюме

Автоматическая настройка spirngboot очень удобна, но в реальном сценарии применения необходимо понимать лежащий в основе механизм, иначе легко создать иллюзию настройки, думая, что конфигурация правильная, но на самом деле это не так. вступить в силу.

doc