欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

spring boot tomcat jdbc pool的属性绑定

程序员文章站 2023-11-16 21:41:40
下面看下spring boot tomcat jdbc pool的属性绑定代码,具体代码如下所示: spring: datasource: type: o...

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