Bean復(fù)制工具Orika

一. 背景

在開發(fā)過程中茎杂,經(jīng)常涉及到對(duì)象之間的拷貝(尤其是微服務(wù)架構(gòu)下,api 層纫雁,biz層煌往,Dao層,視圖層之間的對(duì)象拷貝)轧邪。 當(dāng)前項(xiàng)目中刽脖,用的比較多的是Spring 提供的 BeanUtils羞海。

BeanUtils.copyProperties(Object source, Object target)

實(shí)現(xiàn)原理比較簡(jiǎn)單,就是通過Java的Introspector獲取到兩個(gè)類的PropertyDescriptor曾棕,對(duì)比兩個(gè)屬性具有相同的名字和類型扣猫,如果是,則進(jìn)行賦值(通過ReadMethod獲取值翘地,通過WriteMethod賦值)申尤,否則忽略。

為了提高性能Spring對(duì)BeanInfo和PropertyDescriptor進(jìn)行了緩存(首次復(fù)制比較慢)衙耕。
高性能的代價(jià)是簡(jiǎn)潔昧穿,同時(shí)失去了靈活性和擴(kuò)展性,功能簡(jiǎn)單橙喘。

二. Orika 簡(jiǎn)潔

Orika是一個(gè)簡(jiǎn)單时鸵、快速的JavaBean拷貝框架,它能夠遞歸地將數(shù)據(jù)從一個(gè)JavaBean復(fù)制到另一個(gè)JavaBean厅瞎,這在多層應(yīng)用開發(fā)中非常有用饰潜。

<dependency>
   <groupId>ma.glasnost.orika</groupId>
   <artifactId>orika-core</artifactId>
   <version>1.5.4</version>
</dependency>

2.1 效率高

    底層使用Javassist生成字節(jié)碼,運(yùn)行效率很高

2.2 功能強(qiáng)大

  不同屬性名復(fù)制和簸,嵌套對(duì)象復(fù)制彭雾,屬性忽略,集合復(fù)制等等锁保,也支持自定convert薯酝,實(shí)現(xiàn)自身復(fù)雜轉(zhuǎn)換邏輯。

三. 使用案例

3.1 簡(jiǎn)單對(duì)象-相同屬性名拷貝

//構(gòu)建一個(gè)映射工廠
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
//注冊(cè)映射關(guān)系爽柒,避免首次復(fù)制效率低
mapperFactory.classMap(UserDTO.class,UserInfo.class)
        //byDefault 就是按相同屬性名稱復(fù)制
        .byDefault()

        .register();
// 從映射工廠獲取一個(gè)工具對(duì)象
MapperFacade mapperFacade = mapperFactory.getMapperFacade();    
//使用
UserDTO userDTO=new UserDTO();
userDTO.setName("jack");
UserInfo userInfo=new UserInfo();
//
mapperFacade.map(userDTO,userInfo);

//直接幫我們創(chuàng)建對(duì)象
UserInfo userInfo2 = mapperFacade.map(userDTO, UserInfo.class);

3.2 簡(jiǎn)單對(duì)象-不同屬性名拷貝

//假設(shè)我們需要將userDTO的屬性name吴菠,復(fù)制給UserInfo的屬性u(píng)serName
mapperFactory.classMap(UserDTO.class,UserInfo.class)
        .field("name","userName")
        .byDefault()
        .register();

3.3 內(nèi)嵌對(duì)象拷貝

@Data
public class UserDTO {

    private Long userId;

    private String name;

    private AddressDTO address;
}
@Data
public class UserInfo {

    private Long userId;

    private String name;

    private AddressInfo address;
}
// UserDTO屬性address復(fù)制,只需要注冊(cè)AddressDTO和AddressInfo的映射關(guān)系即可

mapperFactory.classMap(AddressDTO.class,AddressInfo.class)
        .byDefault()
        .register();

3.4 排除屬性復(fù)制

mapperFactory.classMap(AddressDTO.class,AddressInfo.class)
        .exclude("userId")
        .byDefault()
        .register();

3.5 集合對(duì)象拷貝

// 集合對(duì)象的拷貝不需要額外定義映射關(guān)系

List<UserInfo> userInfos=mapperFacade.mapAsList(userDTos, UserInfo.class);

3.6 自定義轉(zhuǎn)換關(guān)系

@Data
public class UserDTO {

    private Long userId;

    private String name;

    private Integer age;

    private UserProfessionEnum profession;

    private AddressDTO address;

    private Date createTime;
}
@Data
public class UserInfo {

    private Long userId;

    private String name;

    private Integer age;

    private Date createTime;

    private String profession;

    private AddressInfo address;
}

mapperFactory.classMap(UserDTO.class,UserInfo.class)
        .customize(new CustomMapper<UserDTO, UserInfo>() {
            @Override
            public void mapAtoB(UserDTO userDTO, UserInfo userInfo, MappingContext context) {
                super.mapAtoB(userDTO, userInfo, context);
                userInfo.setProfession(userDTO.getProfession().getName());
            }

            @Override
            public void mapBtoA(UserInfo userInfo, UserDTO userDTO, MappingContext context) {
                super.mapBtoA(userInfo, userDTO, context);
                userDTO.setProfession(Arrays.stream(UserProfessionEnum.values()).filter(p->Objects.equals(p.getName(),userInfo.getProfession())).findFirst().orElseGet(null));
            }
        })
        .byDefault()
        .register();

3.7 自定義轉(zhuǎn)換關(guān)系-全局

mapperFactory.getConverterFactory().registerConverter(new BidirectionalConverter<UserProfessionEnum,String>() {

    @Override
    public String convertTo(UserProfessionEnum userProfessionEnum, Type<String> type, MappingContext mappingContext) {

        return userProfessionEnum.getName();
    }

    @Override
    public UserProfessionEnum convertFrom(String s, Type<UserProfessionEnum> type, MappingContext mappingContext) {

       return Arrays.stream(UserProfessionEnum.values()).filter(p->Objects.equals(p.getName(),s)).findFirst().orElseGet(null);
    }
});

四. 使用姿勢(shì)推薦

4.1 定義全局抽象convert

public abstract class AbstractOrikaConvert {

    protected MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

    private MapperFacade mapperFacade = null;

    public AbstractOrikaConvert() {

        //注冊(cè)一些項(xiàng)目級(jí)別的轉(zhuǎn)換器浩村,比如和業(yè)務(wù)無(wú)關(guān)的日期的轉(zhuǎn)換
        mapperFacade=mapperFactory.getMapperFacade();
    }
    public <V, P> P convert(V base, Class<P> target) {

    if(base==null){
        return null;
    }
    return mapperFacade.map(base, target);
    }

    public <V, P> void convert(V base, P target) {
        mapperFacade.map(base, target);
    }

    public <V, P> List<P> convertList(List<V> baseList, Class<P> target) {
        return baseList == null ? new LinkedList<>() : mapperFacade.mapAsList(baseList, target);
    } 
}

4.2 層與層之間定義convert

// 比如Biz層和視圖層之間
@Component
public class BizConvert  extends AbstractOrikaConvert{

    public BizConvert() {
        // 定義一些只存在biz層和視圖層對(duì)象映射關(guān)系的公共轉(zhuǎn)換器
        
       // 定義一系列classMap
    }
}

4.3 轉(zhuǎn)換對(duì)象較多的可單獨(dú)定義convert

// 定義Customer業(yè)務(wù)域內(nèi)的轉(zhuǎn)換關(guān)系
@Component
public class CustomerBizConvert extends AbstractOrikaConvert{


    public CustomerBizConvert() {
        
        // mapperFactory.classMap()
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末做葵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子穴亏,更是在濱河造成了極大的恐慌蜂挪,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗓化,死亡現(xiàn)場(chǎng)離奇詭異棠涮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)刺覆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門严肪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事驳糯∑螅” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵酝枢,是天一觀的道長(zhǎng)恬偷。 經(jīng)常有香客問我,道長(zhǎng)帘睦,這世上最難降的妖魔是什么袍患? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮竣付,結(jié)果婚禮上诡延,老公的妹妹穿的比我還像新娘。我一直安慰自己古胆,他們只是感情好肆良,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逸绎,像睡著了一般惹恃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棺牧,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天座舍,我揣著相機(jī)與錄音,去河邊找鬼陨帆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛采蚀,可吹牛的內(nèi)容都是我干的疲牵。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼榆鼠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纲爸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起妆够,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤识啦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后神妹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颓哮,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年鸵荠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冕茅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖姨伤,靈堂內(nèi)的尸體忽然破棺而出哨坪,到底是詐尸還是另有隱情,我是刑警寧澤乍楚,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布当编,位于F島的核電站,受9級(jí)特大地震影響徒溪,放射性物質(zhì)發(fā)生泄漏忿偷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一词渤、第九天 我趴在偏房一處隱蔽的房頂上張望牵舱。 院中可真熱鬧,春花似錦缺虐、人聲如沸芜壁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)慧妄。三九已至,卻和暖如春剪芍,著一層夾襖步出監(jiān)牢的瞬間塞淹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工罪裹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留饱普,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓状共,卻偏偏與公主長(zhǎng)得像套耕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子峡继,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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