一. 背景
在開發(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()
}
}