問題現(xiàn)象
最近公司內(nèi)部一個(gè)運(yùn)行已久的產(chǎn)品,在最近一次上線后操作出現(xiàn)了“No value specified for 'Date'”的錯(cuò)誤,異常堆棧如下:
Caused by: org.apache.commons.beanutils.ConversionException: No value specified for 'Date'
at org.apache.commons.beanutils.converters.AbstractConverter.handleMissing(AbstractConverter.java:310)
at org.apache.commons.beanutils.converters.AbstractConverter.convert(AbstractConverter.java:136)
at org.apache.commons.beanutils.converters.ConverterFacade.convert(ConverterFacade.java:60)
at org.apache.commons.beanutils.BeanUtilsBean.convert(BeanUtilsBean.java:1078)
at org.apache.commons.beanutils.BeanUtilsBean.copyProperty(BeanUtilsBean.java:437)
at org.apache.commons.beanutils.BeanUtilsBean.copyProperties(BeanUtilsBean.java:286)
at org.apache.commons.beanutils.BeanUtils.copyProperties(BeanUtils.java:137)
通過修改記錄并沒有發(fā)現(xiàn)關(guān)于BeanUtils以及源目標(biāo)模型的修改。
問題分析
通過異常堆棧和BeanUtils的copyProperties源碼分析來看,問題的根源是要copy的源模型中的存在“Date”類型的屬性,但其值為null。查看源模型類源碼的修改記錄和數(shù)據(jù)庫(kù)中的字段和值,發(fā)現(xiàn)近期均未變化,也就是說在本次上線前,這種情況的源模型是可以使用BeanUtils來copy屬性的。
百度一下,大多數(shù)給出的解決方案都是在使用BeanUtils的copyProperties之前注冊(cè)一個(gè)DateConverter:
// 增加注冊(cè)DateConverter代碼,設(shè)置默認(rèn)值為null。
ConvertUtils.register(new DateConverter(null), java.util.Date.class);
BeanUtils.copyProperties(...);
增加這句代碼可以解決這個(gè)問題,但是我們上線之前沒有這句代碼也是可用的,換google檢索一下,最終在StackOverflow上查到了問題的根本原因,這個(gè)問題是beanutils 1.8.x版本上才會(huì)出現(xiàn),由于beanutils 1.8.x版本上修改了這個(gè)copyProperties方法,如果要轉(zhuǎn)換的類型是Date、Calendar或其他一些類型時(shí),當(dāng)值為null且沒有配置默認(rèn)值時(shí)會(huì)拋出異常。1.9.0及之后的版本修復(fù)了這個(gè)問題。
查看我們上線之前服務(wù)器上的beanutils的版本為1.9.3,而本次上線包中的beanutils的版本是1.8.3,顯然是打包時(shí)某些依賴間接依賴了低版本的beanutils進(jìn)而影響了打包結(jié)果。
問題原因
apache的BeanUtils 1.8.x版本的copyProperties修改了Date類型的null值的處理邏輯,改為了無默認(rèn)值值拋出異常。1.9.0之后的版本修復(fù)了該問題。
beanutils關(guān)于該bug的記錄:https://issues.apache.org/jira/browse/BEANUTILS-454
解決辦法
以下幾種方案選其一即可。
pom.xml中固化使用的beanutils的版本
在pom.xml中使用dependency:tree檢查依賴的beanutils的版本,可以通過強(qiáng)制指定beanutils依賴的方式來固定使用的beanutils的版本。
增加DateConverter轉(zhuǎn)換器(百度上的通用解決方案)
在程序的初始化代碼中增加DateConverter轉(zhuǎn)換器類:
// 增加注冊(cè)DateConverter代碼,設(shè)置默認(rèn)值為null。
ConvertUtils.register(new DateConverter(null), java.util.Date.class);
使用java.sql.Date取代java.util.Date(未驗(yàn)證)
模型中日期屬性的類型使用java.sql.Date而不是java.util.Date。
使用spring的BeanUtils工具類(未驗(yàn)證)
spring中提供了一個(gè)org.springframework.beans.BeanUtils工具類,跟apache的BeanUtils功能差不多一樣。
注意:如果之前使用的是apache的BeanUitls,不建議采用該方法。一是改動(dòng)量太大,二是spring的BeanUitls一樣存在各種各樣的坑。