spring boot tomcat jdbc pool的属性绑定
下面看下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底下均无法生效。