楊春發(fā),潘 鶴,李 鑫
(1.中國(guó)電建集團(tuán)華東勘測(cè)設(shè)計(jì)研究院有限公司,浙江 杭州 311122;2.中電建華東勘測(cè)設(shè)計(jì)研究院(鄭州)有限公司)
“灃西新城”為區(qū)域內(nèi)建設(shè)項(xiàng)目,對(duì)于其數(shù)據(jù)平臺(tái)的建設(shè),需要融入CIM(City Information Modeling)理念,即建立CIM 基礎(chǔ)數(shù)據(jù)平臺(tái)。該平臺(tái)服務(wù)于城市規(guī)劃、建設(shè)和治理等多個(gè)場(chǎng)景,為城市挖掘數(shù)據(jù)價(jià)值,有效避免重復(fù)投資與建設(shè)助力。
平臺(tái)開(kāi)發(fā)中,需要將不同二維數(shù)據(jù)對(duì)象的相同或相似屬性進(jìn)行賦值。傳統(tǒng)的方法是通過(guò)手動(dòng)編寫Getter/Setter 為屬性或字段賦值,當(dāng)拷貝對(duì)象字段較多時(shí),會(huì)導(dǎo)致大量代碼堆積,可讀性降低。為解決這一痛點(diǎn),平臺(tái)引入拷貝工具類,分別為Apache和Spring的BeanUtils,以優(yōu)化兩個(gè)任意多字段對(duì)象的屬性拷貝,提高開(kāi)發(fā)效率,為相似二維數(shù)據(jù)對(duì)象復(fù)制繁瑣問(wèn)題,提供解決方案。
JavaBeans 的靜態(tài)便捷方法,對(duì)Bean 進(jìn)行實(shí)例化、檢查屬性類型、復(fù)制屬性等操作。應(yīng)用比較廣泛的一個(gè)是Apache Commons 的BeanUtils,另一個(gè)是Spring Framework的BeanUtils。
JavaBean 作為工具類的作用對(duì)象,本質(zhì)上是一種命名規(guī)則,具體如下。
⑴對(duì)于一個(gè)名稱為xxx的屬性,通常要寫兩個(gè)方法:getXxx()和setXxx()。任何瀏覽這些方法的工具,都會(huì)把set 或get 后的第一個(gè)字母轉(zhuǎn)為小寫,以此產(chǎn)生屬性名。get方法返回的類型需要與set方法里的參數(shù)相同,屬性名稱與get和set所依據(jù)的類型毫無(wú)關(guān)系。
⑵對(duì)于布爾類型屬性,使用以上的get 和set 方式,可把get替換成is。
⑶ Bean 中的普通方法不必遵循以上的命名規(guī)則,不過(guò)其訪問(wèn)權(quán)限控制符必須是public的[1]。
工具類本質(zhì)上是對(duì)象的拷貝,而對(duì)象拷貝又分為深拷貝和淺拷貝,BeanUtils 的拷貝方法就是淺拷貝,拷貝原理見(jiàn)圖1,定義如下:
圖1 拷貝原理圖
⑴淺拷貝:對(duì)基本類型采用值傳遞(傳遞該變量的副本)。對(duì)引用類型進(jìn)行引用傳遞即復(fù)制指向源對(duì)象的地址,不復(fù)制對(duì)象本身,修改新對(duì)象會(huì)影響原對(duì)象。
⑵深拷貝:對(duì)基本類型采用值傳遞,對(duì)引用類型來(lái)說(shuō),深拷貝會(huì)復(fù)制一個(gè)完全與源對(duì)象一樣的對(duì)象,且不共享內(nèi)存。
工具類的核心拷貝原理就是通過(guò)反射和自省實(shí)現(xiàn)的。
Java反射機(jī)制是在運(yùn)行中,對(duì)任意一個(gè)類,能夠獲取得到這個(gè)類的所有屬性和方法,對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用類的任意一個(gè)方法,這種動(dòng)態(tài)獲取類信息以及動(dòng)態(tài)調(diào)用類對(duì)象方法的功能叫做Java語(yǔ)言的反射機(jī)制[2]。利用反射機(jī)制編寫與執(zhí)行程序代碼時(shí),使程序代碼能夠接入裝載到JVM中的類的內(nèi)部信息[3]。
內(nèi)省是操作Java 對(duì)象屬性的API,內(nèi)省依賴于反射,利用BeanInfo來(lái)獲取屬性描述器,就可獲取某個(gè)屬性對(duì)應(yīng)的Getter和Setter方法,最后通過(guò)反射機(jī)制來(lái)調(diào)用Getter和Setter方法[4]。
BeanUtils 本質(zhì)是通過(guò)內(nèi)省對(duì)JavaBean 的屬性進(jìn)行淺拷貝,通過(guò)Class 引用獲取BeanInfo 信息,調(diào)用BeanInfo 的getPropertyDescriptors 方法,返回類型為PropertyDescriptor 的屬性描述器數(shù)組。針對(duì)每一個(gè)PropertyDescriptor,調(diào)用getPropertyType 方法得到類型,getName 方法得到屬性名,getReadMethod 方法得到讀方法,getWriteMethod 方法得到寫方法,后兩個(gè)方法返回Method 對(duì)象,在對(duì)象上調(diào)用相應(yīng)方法(invoke),進(jìn)行屬性的賦值。
實(shí)驗(yàn)以平臺(tái)的三維可視化系統(tǒng)返回單條場(chǎng)景及相關(guān)信息為例進(jìn)行測(cè)試,使用Spring-Beans5.1.10 和Commons-BeanUtils1.9.3 作為實(shí)驗(yàn)版本,持久層方法對(duì)源對(duì)象屬性賦值后,依次用兩個(gè)工具類對(duì)源、目標(biāo)對(duì)象的同名屬性值進(jìn)行拷貝,打印場(chǎng)景聯(lián)合體對(duì)象的編號(hào)、名字、相關(guān)第一條圖層編號(hào)、相關(guān)第一條標(biāo)繪編號(hào),核心調(diào)用方法如下:
3.2.1 鏈?zhǔn)骄幊痰闹С中?/p>
⑴實(shí)驗(yàn)結(jié)果及分析
圖2 是工具類對(duì)啟用鏈?zhǔn)骄幊痰膶?duì)象拷貝后的打印結(jié)果,使用Spring 的BeanUtils 能正常打印新聞信息,而使用Apache 的BeanUtils 拷貝為空,具體原因如下。
圖2 啟用鏈?zhǔn)骄幊檀蛴〗Y(jié)果
①Apache 中,使用默認(rèn)的getWriteMethod()方法,通過(guò)查找set前綴的屬性名方法并判斷返回類型是否為void類型,如下:
②而SpringBean 中在獲取BeanInfo 對(duì)象的過(guò)程中,提供候選方法,候選可寫方法將返回類型不是void的類型納入可寫方法中,而鏈?zhǔn)骄幊痰姆祷仡愋褪潜绢?,這就導(dǎo)致Apache的BeanUtils不支持鏈?zhǔn)骄幊獭?/p>
③關(guān)閉鏈?zhǔn)骄幊毯笤俅螠y(cè)試,兩個(gè)工具類會(huì)打印同樣的結(jié)果,如圖3所示。
圖3 關(guān)閉鏈?zhǔn)骄幊檀蛴〗Y(jié)果
⑵結(jié)論
Spring 的BeanUtils 支持鏈?zhǔn)骄幊?,而Apache 的BeanUtils不支持。
3.2.2 安全性
⑴實(shí)驗(yàn)結(jié)果及分析
圖4、圖5 是Spring BeanUtils、Apache BeanUtils對(duì)于null的Long 寫入long 類型的打印結(jié)果,前者提示非法參數(shù)異常,后者拷貝正常,原因如下:
圖4 SpringBeanUtils打印結(jié)果
圖5 ApacheBeanUtils打印結(jié)果
①Spring的BeanUtils的部分代碼:
②Apache的BeanUtils的部分代碼:
Apache 的工具類采用多種數(shù)據(jù)驗(yàn)證方式來(lái)保障安全性,包括類型的轉(zhuǎn)換,甚至還會(huì)檢驗(yàn)對(duì)象所屬類的可訪問(wèn)性。
⑵結(jié)論Spring的BeanUtils校驗(yàn)更少,而Apache的BeanUtils會(huì)進(jìn)行多種數(shù)據(jù)驗(yàn)證和類型轉(zhuǎn)換,是更安全的。
3.2.3 性能
⑴實(shí)驗(yàn)結(jié)果及分析
針對(duì)兩種拷貝工具類一次進(jìn)行100,10000,100000,1000000 的對(duì)象數(shù)量多次拷貝,具體平均消耗時(shí)間如圖6所示。
圖6 BeanUtils性能對(duì)比圖
在大數(shù)量對(duì)象拷貝時(shí),SpringBeanUtils 的拷貝性能是ApacheBeanUtils 的近十倍,其主要原因在于Apache BeanUtils 對(duì)屬性的各類條件判斷,包括但不限于類型、可讀性等。Apache BeanUtils 還對(duì)類加載器進(jìn)行單例限制,獲取實(shí)例時(shí)會(huì)加鎖,造成資源持有,影響性能。
⑵結(jié)論
Spring 的BeanUtils 內(nèi)部邏輯簡(jiǎn)單,效率比Apache的BeanUtils更高。
針對(duì)平臺(tái)二維數(shù)據(jù)的需求場(chǎng)景選擇不同的拷貝工具類,實(shí)現(xiàn)數(shù)據(jù)的轉(zhuǎn)換。將灃西新城CIM 基礎(chǔ)平臺(tái)構(gòu)建為一個(gè)不僅是數(shù)據(jù)展示,更是數(shù)據(jù)管理的載體。對(duì)于平臺(tái)性能要求較高的場(chǎng)景,可采用Spring 的BeanUtils,如有更多的格式驗(yàn)證、訪問(wèn)控制的需求,可采用Apache的BeanUtils。使用過(guò)程中應(yīng)注意以下幾點(diǎn):
⑴涉及數(shù)值類型的封裝類像Integer、Short、Long、Double的數(shù)據(jù)轉(zhuǎn)換時(shí),Spring BeanUtils需要手動(dòng)初始化,否則提示數(shù)據(jù)轉(zhuǎn)換異常。而Apache BeanUtils 內(nèi)置轉(zhuǎn)換器,無(wú)需擔(dān)心初始化問(wèn)題。
⑵兩個(gè)拷貝工具類方法的傳參順序是相反的,SpringBeanUtils 的參數(shù)順序是(source,dest),而ApacheBeanUtils順序?yàn)椋╠est,source)。
⑶ Spring 的BeanUtils 可指定忽略字段,實(shí)現(xiàn)Bean的部分屬性拷貝。
⑷涉及Date 對(duì)象的數(shù)據(jù)轉(zhuǎn)換時(shí),需注意Apache的BeanUtils 不支持until 包下的Date 類,應(yīng)使用sql 下的Date類,避免數(shù)據(jù)異常。
此外,BeanUtils 并不是任何場(chǎng)景都適用的。BeanUtils 的不足之處在于其本質(zhì)是淺拷貝,涉及對(duì)象只能是單一屬性和或者幾乎不改動(dòng)的子對(duì)象。一但涉及深拷貝的場(chǎng)景,BeanUtils 是無(wú)法滿足的。另外,雖然使用BeanUtils 進(jìn)行屬性拷貝十分方便,極大的減少了代碼冗長(zhǎng),但由于各種驗(yàn)證、獲取及調(diào)用方法、序列化等對(duì)象操作,BeanUtils 的消耗時(shí)間實(shí)際上比手動(dòng)調(diào)用Getter/Setter的時(shí)間長(zhǎng),因此要合理使用。