構(gòu)建功能最強(qiáng)仍律、性能最好的Java BeanCopy類庫

本文所提到的類庫已經(jīng)開源到GITHUB暇咆,讀者可下載源碼并提寶貴意見。

轉(zhuǎn)載請注明出處: http://www.reibang.com/p/9a136ecd3838

問題

在現(xiàn)有的項目中副硅,由于采用了PO/DTO/VO模型姥宝,導(dǎo)致需要大量的代碼進(jìn)行Java對象間的拷貝操作。通常的做法是取一個對象的get方法恐疲,設(shè)置到另一個對象的set方法上去:

    public MaterialInfoVO getMaterialInfo(String materialId) {
        
        MaterialInfo materialInfo = this.materialDao.findById(materialId);
        
        MaterialInfoVO materialInfoVO = new MaterialInfoVO();
        materialInfoVO.setMaterialId(materialInfo.getId());
        materialInfoVO.setType(materialInfo.getType());
        materialInfoVO.setArticles(new ArrayList<ArticleInfoVO>());
        
        List<ArticleInfo> articles = materialInfo.getArticles();
        for (ArticleInfo article : articles) {
            ArticleInfoVO articleInfoVO = new ArticleInfoVO();
            materialInfoVO.getArticles().add(articleInfoVO);
            
            articleInfoVO.setArticleId(article.getId());
            articleInfoVO.setTitle(article.getTitle());
            articleInfoVO.setContent(article.getContent());
            articleInfoVO.setContentSourceUrl(article.getContentSourceUrl());
            articleInfoVO.setDigest(article.getDigest());
            articleInfoVO.setShowCoverPic(article.getShowCoverPic());
            articleInfoVO.setShowOrder(article.getShowOrder());
            articleInfoVO.setThumbMediaId(article.getThumbMediaId());
            articleInfoVO.setUrl(article.getUrl());
            articleInfoVO.setContentSourceUrl(article.getContentSourceUrl());
            
        }
        
        return materialInfoVO;
    }

然而腊满,大量類似重復(fù)的代碼到處可見,代碼重復(fù)度太高培己,而且很容易視覺疲勞碳蛋,導(dǎo)致字段遺漏。由此萌生了改善的想法省咨,希望能達(dá)到以下的目的:

    public MaterialInfoVO getMaterialInfo(Integer materialId) {
        MaterialInfo materialInfo = this.materialDao.findById(materialId);
        return BeanCopyUtils.copyBean(materialInfo,MaterialInfoVO.class);
    }

需求分析

為了改善代碼肃弟,能夠自動的將一個類的屬性Copy到另一個類中,并且還需要滿足以下的特定需求:

  • 屬性名稱映射:From類的屬性叫id零蓉,To類的卻是materialId笤受。要能夠映射到不同名稱的屬性上去;
  • 基本類型自動轉(zhuǎn)換:要能夠自動針對Java中的Primitive Type同對應(yīng)的Java Type進(jìn)行轉(zhuǎn)換敌蜂。例如:int同Integer之間轉(zhuǎn)換箩兽;
  • 忽略拷貝: 要能夠支持屬性過濾,對不想進(jìn)行拷貝的屬性能夠不拷貝章喉。例如:user的password屬性就不想拷貝出去汗贫;
  • 遞歸拷貝:From類中的某個屬性也是JavaBean,也需要拷貝到To類中某個JavaBean中去秸脱;
  • 數(shù)組拷貝:如果From類中的某個屬性是數(shù)組類型落包,也希望能拷貝到To類中對應(yīng)的屬性中去;
  • 集合拷貝:同數(shù)組拷貝一樣撞反,如果From類中的某個屬性是集合(List妥色,Set等)類型搪花,也希望能拷貝到To類中對應(yīng)的屬性中去遏片;
  • 自定義數(shù)據(jù)轉(zhuǎn)換:如果在拷貝的過程中,希望能夠按照自定義的規(guī)則將某些屬性轉(zhuǎn)換成其他數(shù)據(jù)類型撮竿。例如:將Date轉(zhuǎn)換成String吮便;
  • 接口簡單:我們都是程序員,不希望寫復(fù)雜的代碼幢踏。

現(xiàn)有庫調(diào)研

在目前比較通用的BeanCopy類庫中髓需,有如下的幾個類庫:

  • org.apache.commons.beanutils.BeanUtil.copyProperties
  • org.apache.commons.beanutils.PropertyUtils.copyProperties
  • org.springframework.beans.BeanUtils.copyProperties
  • org.springframework.cglib.beans.BeanCopier.create
  • net.sf.ezmorph.bean.BeanMorpher

上面是目前能在市面上找到的幾個比較通用的類庫。然而房蝉,這些類庫卻很難滿足上述的需求:

Library 屬性名稱映射 基本類型轉(zhuǎn)換 忽略拷貝 數(shù)組拷貝 集合拷貝 自定義轉(zhuǎn)換 性能
apache BeanUtil. copyProperties X X X X X X 慘不忍睹
apache PropertyUtils. copyProperties X X X X X X 慘不忍睹
spring BeanUtils. copyProperties X X X X 還能接受
cglib BeanCopier X X X X X 難用 優(yōu)秀
ezmorph BeanMorpher X X X X X X 慘不忍睹

也許有些庫的用法還不得知僚匆,但很難全部功能都滿足微渠。看來只能自己造輪子了咧擂。

庫接口設(shè)計

函數(shù)接口

為了自己的輪子能否方便的使用逞盆,在接口函數(shù)設(shè)計上可盡量簡單。
函數(shù)調(diào)用方式如下:

  // 使用class方式
  ToBean toBean = BeanCopyUtils.copyBean(fromBean, ToBean.class);
  // 使用對象方式
  ToBean toBean = new ToBean();
  BeanCopyUtils.copyBean(fromBean, toBean);

為了能達(dá)到上述的需求松申,需要引入一些注解來進(jìn)行配置云芦。


                                           @BeanCopySource(source=FromBean.class)          
    public class FromBean {                public class ToBean {                           
                                                                                           
        private boolean beanBool;              private Boolean beanBool;                   
        private byte beanByte;                 private Byte beanByte;                      
        private char beanChar;                 private Character beanChar;                 
        private short beanShort;               private Short beanShort;                    
        private int beanInt;                   private Integer beanInt;                    
        private Long beanLong;                 private long beanLong;                      
        private Float beanFloat;               private float beanFloat;                    
        private Double beanDouble;             private double beanDouble;                  
        private String beanString;             private String beanString;                  
                                                                                           
                                               @CopyProperty(convertor=DateConvertor.class)
        private Date beanDate;                 private String beanDate;                    
                                                                                           
        private int[] beanIntArray;            private int[] beanIntArray;                 
                                                                                           
                                               @CopyProperty                               
        private FromBean2 bean2;               private ToBean2 bean2;                      
                                                                                           
                                               @CopyCollection(targetClass=ToBean3.class)  
        private List<FromBean3> bean3List;     private List<ToBean3> bean3List;            
                                                                                           
                                               @CopyProperty                               
        private FromBean4[] bean4Array;        private ToBean4[] bean4Array;               
                                                                                           
        // getters and setters...              @CopyProperty(property="beanInt")           
    }                                          private int beanId;                         
                                                                                           
                                               @CopyProperty(property="bean2.beanString")  
                                               private String bean2String;                 
                                               
                                               // getters and setters...
                                           }

現(xiàn)在問題又來了,如果ToBean是第三方類庫的贸桶,無法在ToBean上加注解舅逸,怎么辦?這能難倒我嗎皇筛?這兒僅需要提供一個Option類琉历,將所有注解的內(nèi)容加到Option類中,讓Option類代替ToBean來做配置设联。

@BeanCopySource(source=FromBean.class)
public class ToBeanOption {
    
    private Boolean beanBool;
    private Byte beanByte;
    private Character beanChar;
    private Short beanShort;
    private Integer beanInt;
    private long beanLong;
    private float beanFloat;
    private double beanDouble;
    private String beanString;
    
    @CopyProperty(convertor=DateConvertor.class)
    private String beanDate;
    
    private int[] beanIntArray;

    @CopyProperty
    private ToBean2 bean2;
    
    @CopyCollection(targetClass=ToBean3.class)
    private List<ToBean3> bean3List;
    
    @CopyProperty
    private ToBean4[] bean4Array;
    
    @CopyProperty(property="beanInt")
    private int beanId;
    
    @CopyProperty(property="bean2.beanString")
    private String bean2String;
    // getters and setters can be empty.
}

調(diào)用方法也非常簡單:

  // 使用class方式
  ToBean toBean = BeanCopyUtils.copyBean(fromBean, ToBean.class, ToBeanOption.class);
  // 使用對象方式
  ToBean toBean = new ToBean();
  BeanCopyUtils.copyBean(fromBean, toBean, ToBeanOption.class);

是不是有點羨慕善已?

自定義數(shù)據(jù)轉(zhuǎn)換

為了支持自定義數(shù)據(jù)轉(zhuǎn)換,需要引入接口离例,以方便自定義轉(zhuǎn)換要求:

public interface BeanCopyConvertor<S, T> {
    abstract public T convertTo(S object);
}

具體實現(xiàn)舉例:

public class GendorConvertor implements BeanCopyConvertor<Integer, String> {

    @Override
    public String convertTo(Integer object) {
        if( object == 1 ) 
            return "Male";
        if( object == 2) 
            return "Female";
        return "Unknown";
    }
}

當(dāng)然换团,也需要在注解上標(biāo)明:

    @CopyProperty(convertor=GendorConvertor.class)
    private String gendor;

實現(xiàn)及優(yōu)化

具體的實現(xiàn)步驟不表,僅說優(yōu)化及其它考慮宫蛆。

優(yōu)化方案

  • 使用緩存
    由于采取的注解的方式艘包,所以在拷貝具體執(zhí)行前可以明確的知道哪些屬性要拷貝,需要做哪些映射耀盗,對應(yīng)哪些屬性要做遞歸拷貝等想虎。而且,對于特定的FromBean和ToBean以及特定的ToBeanOption叛拷,在整個Java進(jìn)程的生命期中拷貝的屬性是定死的舌厨。因此,我們可以先解析注解的拷貝信息忿薇,然后緩存起來裙椭,在后續(xù)相同的對象類型拷貝中直接使用。
  • 使用Javassist類庫
    如果直接使用Java的反射方式署浩,那么性能最高也就是和spring的BeanUtils.copyProperties性能相當(dāng)揉燃。對于CGlib的BeanCopier那樣高性能,只能采取相同的做法筋栋,也就是動態(tài)生成特定的Copy class炊汤,然后在執(zhí)行。可惜我對CGlib的純粹字節(jié)碼操作不感冒抢腐,因此采用了Javassist庫了姑曙。

減少依賴

類庫設(shè)計之初是為了通用使用,因此要盡量減少對其它類庫的引用迈倍。因此渣磷,除了必須要引用的javassist類庫之外,全部自我實現(xiàn)相關(guān)功能授瘦,不引用其它的類庫醋界。
更為甚者,如果要在javassist類都無法運(yùn)行的環(huán)境中使用此庫提完,也可以通過修改此類庫的全局配置形纺,切換到用反射的方式上運(yùn)行此庫。

缺省集合類

再做集合拷貝的時候徒欣,對于集合類的實現(xiàn)類可以通過全局配置的方式切換為自定義的實現(xiàn)類逐样。 例如List對應(yīng)的實現(xiàn)類,缺省為ArrayList打肝。但是如果想使用LinkedList脂新,可以通過全局配置來進(jìn)行切換。

性能

通過對不同類庫循環(huán)調(diào)用的方法來測試各個類庫的性能粗梭。

Library 1 time 100 times 10000 times 1000000 times 10000000 times
apache BeanUtil.copyProperties 1 12 128 9963 99879
apache PropertyUtils.copyProperties 0 2 56 5564 55651
spring BeanUtils.copyProperties 0 2 5 473 4700
ezmorph BeanMorpher 1 4 67 6769 68051
cglib BeanCopier.create 1 1 2 2 87 843
cglib BeanCopier.create 2 0 0 0 10 98
BeanCopyUtils.copyBean 1 0 0 0 21 196
BeanCopyUtils.copyBean 2 0 0 0 11 97
native Copy 0 0 0 10 88
  • 表格中數(shù)字單位為毫秒争便,也就是對應(yīng)類庫運(yùn)行次數(shù)所花費的時間
  • 表格中各個數(shù)據(jù)為類庫的運(yùn)行多次的平均值,運(yùn)行平臺為Win10, intel i7 6700HQ, 24G
  • 具體的測試代碼也在GITHUB中断医。讀者可自行下載運(yùn)行比較
  • cglib BeanCopier.create的兩種使用方法和BeanCopyUtils.copyBean的兩種使用方法滞乙,請參考GITHUB中代碼
  • native copy:就是全部手動寫get/set代碼

結(jié)尾和后續(xù)

目前第一版本已經(jīng)完成,后續(xù)會加上對注解的檢查函數(shù)鉴嗤,以避免注解配置錯誤斩启。同時由于是第一版本,測試的并不是很充分醉锅,還需要大量的測試來保證正確性兔簇。

關(guān)于作者

作者很懶,很少發(fā)表技術(shù)有關(guān)的話題文章硬耍。同時作者在軟件開發(fā)上混了快20年垄琐,輾轉(zhuǎn)于C、C++默垄,Java此虑,Object-C各個開發(fā)語言之間甚纲;做過Windows口锭、Linux、Android、iOS以及Java后臺開發(fā)鹃操,管理過20+以上的團(tuán)隊韭寸。但更多的,卻是想當(dāng)一個默默奉獻(xiàn)的碼農(nóng)荆隘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恩伺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子椰拒,更是在濱河造成了極大的恐慌晶渠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件燃观,死亡現(xiàn)場離奇詭異褒脯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缆毁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門番川,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脊框,你說我怎么就攤上這事颁督。” “怎么了浇雹?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵沉御,是天一觀的道長。 經(jīng)常有香客問我昭灵,道長嚷节,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任虎锚,我火速辦了婚禮硫痰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窜护。我一直安慰自己效斑,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布柱徙。 她就那樣靜靜地躺著缓屠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪护侮。 梳的紋絲不亂的頭發(fā)上敌完,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音羊初,去河邊找鬼滨溉。 笑死什湘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晦攒。 我是一名探鬼主播闽撤,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼脯颜!你這毒婦竟也來了哟旗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤栋操,失蹤者是張志新(化名)和其女友劉穎闸餐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矾芙,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡绎巨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蠕啄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片场勤。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖歼跟,靈堂內(nèi)的尸體忽然破棺而出和媳,到底是詐尸還是另有隱情,我是刑警寧澤哈街,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布留瞳,位于F島的核電站,受9級特大地震影響骚秦,放射性物質(zhì)發(fā)生泄漏她倘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一作箍、第九天 我趴在偏房一處隱蔽的房頂上張望硬梁。 院中可真熱鬧,春花似錦胞得、人聲如沸荧止。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跃巡。三九已至,卻和暖如春牧愁,著一層夾襖步出監(jiān)牢的瞬間素邪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工猪半, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留兔朦,地道東北人偷线。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像烘绽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子俐填,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1. 簡介 1.1 什么是 MyBatis 安接? MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,520評論 0 4
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法英融,類相關(guān)的語法盏檐,內(nèi)部類的語法,繼承相關(guān)的語法驶悟,異常的語法胡野,線程的語...
    子非魚_t_閱讀 31,630評論 18 399
  • operation=o+per+a+tion inquiring=in+qui(ring)
    SunnySun_fb42閱讀 204評論 0 0
  • 故障分為P1-4級 P1 系統(tǒng)無法訪問 有10%以上的查詢無結(jié)果,或者無法查詢 booking失效比率超過5% P...
    高效匠人閱讀 2,292評論 0 2
  • 今天想研究一下到歌舞伎街后要怎么玩痕鳍,隨便搜了下硫豆,發(fā)現(xiàn)了一篇文章×簦看了一下熊响,還寫得挺好的,相當(dāng)幽默诗赌。本以為就是一篇吐...
    lxt閱讀 416評論 11 1