
作者:小傅哥
博客:https://
原文:https://mp.weixin.qq.com/s/Xq7oQg7dYESMYxHVnxX8Dw
沉淀、分享、成長(zhǎng),讓自己和他人都能有所收獲!😄
一、前言
為哈么,你的代碼也就僅僅是能用而已?
沒(méi)有技術(shù)深度、短缺知識(shí)儲(chǔ)備、匱乏經(jīng)驗(yàn)積累的前提下,怎么寫(xiě)代碼?百度呀,遇到問(wèn)題這搜一點(diǎn),那查一塊,不管它是什么原理還是適合哪種場(chǎng)景,先粘貼到自己的工程里,看,能跑了,能跑就行。那這樣的代碼也就僅僅是能用程度的交付,根本沒(méi)有一定的質(zhì)量保證,也更別提數(shù)據(jù)結(jié)構(gòu)、算法邏輯和設(shè)計(jì)模式了,那看的編程資料刷的LeetCode,全歇菜了。
當(dāng)你感覺(jué)看了很多資料又不會(huì)用的時(shí)候,會(huì)說(shuō)什么,真卷,都學(xué)到這樣了
。但其實(shí)我并不覺(jué)對(duì)技術(shù)的深度挖掘、梳理全套的知識(shí)體系,一點(diǎn)點(diǎn)耕耘一點(diǎn)點(diǎn)收獲是在卷。反而把看技術(shù)視頻當(dāng)成看電影一樣輕松,不寫(xiě)案例就以為書(shū)看會(huì)了的爽,沒(méi)有意義的缺少腦力思考機(jī)械式體力重復(fù),才是卷,甚至很卷。
就像讓你用一個(gè)屬性拷貝工具,把vo轉(zhuǎn)成dto
,你用了哪呢,是 Apache 的還是 Spring 的,還是其他的什么,哪個(gè)效率最高?接下來(lái)我們來(lái)用數(shù)據(jù)驗(yàn)證下,并提供出各種案例的使用對(duì)比
二、性能測(cè)試對(duì)比
在 Java 系統(tǒng)工程開(kāi)發(fā)過(guò)程中,都會(huì)有各個(gè)層之間的對(duì)象轉(zhuǎn)換,比如 VO、DTO、PO、VO 等,而如果都是手動(dòng)get、set
又太浪費(fèi)時(shí)間,還可能操作錯(cuò)誤,所以選擇一個(gè)自動(dòng)化工具會(huì)更加方便。
目前我整理出,用于對(duì)象屬性轉(zhuǎn)換有12種,包括:普通的getset、json2Json、Apache屬性拷貝、Spring屬性拷貝、bean-mapping、bean-mapping-asm、BeanCopier、Orika、Dozer、ModelMapper、JMapper、MapStruct 接下來(lái)我們分別測(cè)試這11種屬性轉(zhuǎn)換操作分別在一百次
、一千次
、一萬(wàn)次
、十萬(wàn)次
、一百萬(wàn)次
時(shí)候的性能時(shí)間對(duì)比。

BeanUtils.copyProperties
是大家代碼里最常出現(xiàn)的工具類(lèi),但只要你不把它用錯(cuò)成 Apache
包下的,而是使用 Spring 提供的,就基本還不會(huì)對(duì)性能造成多大影響。- 但如果說(shuō)性能更好,可替代手動(dòng)
get、set
的,還是 MapStruct
更好用,因?yàn)樗旧砭褪窃诰幾g期生成get、set
代碼,和我們寫(xiě)get、set
一樣。 - 其他一些組件包主要基于
AOP
、ASM
、CGlib
,的技術(shù)手段實(shí)現(xiàn)的,所以也會(huì)有相應(yīng)的性能損耗。
三、12種轉(zhuǎn)換案例

源碼:https://github.com/fuzhengwei/guide-vo2dto
描述:在案例工程下創(chuàng)建 interfaces.assembler 包,定義 IAssembler<SOURCE, TARGET>#sourceToTarget(SOURCE var)
接口,提供不同方式的對(duì)象轉(zhuǎn)換操作類(lèi)實(shí)現(xiàn),學(xué)習(xí)的過(guò)程中可以直接下載運(yùn)行調(diào)試。
1. get\set
@Component
public class GetSetAssembler implements IAssembler<UserVO, UserDTO> {
@Override
public UserDTO sourceToTarget(UserVO var) {
UserDTO userDTO = new UserDTO();
userDTO.setUserId(var.getUserId());
userDTO.setUserNickName(var.getUserNickName());
userDTO.setCreateTime(var.getCreateTime());
return userDTO;
}
}
- 推薦:★★★☆☆
- 性能:★★★★★
- 手段:手寫(xiě)
- 點(diǎn)評(píng):其實(shí)這種方式也是日常使用的最多的,性能肯定是杠杠的,就是操作起來(lái)有點(diǎn)麻煩。尤其是一大堆屬性的 VO 對(duì)象轉(zhuǎn)換為 DTO 對(duì)象時(shí)候。但其實(shí)也有一些快捷的操作方式,比如你可以通過(guò) Shift+Alt 選中所有屬性,Shift+Tab 歸并到一列,接下來(lái)在使用 Alt 選中這一列,批量操作粘貼
userDTO.set
以及快捷鍵大寫(xiě)屬性首字母,最后切換到結(jié)尾補(bǔ)充括號(hào)和分號(hào),最終格式化一下就搞定了。
2. json2Json
@Component
public class Json2JsonAssembler implements IAssembler<UserVO, UserDTO> {
@Override
public UserDTO sourceToTarget(UserVO var) {
String strJson = JSON.toJSONString(var);
return JSON.parseObject(strJson, UserDTO.class);
}
}
- 推薦:☆☆☆☆☆
- 性能:★☆☆☆☆
- 手段:把對(duì)象轉(zhuǎn)JSON串,再把JSON轉(zhuǎn)另外一個(gè)對(duì)象
- 點(diǎn)評(píng):這么寫(xiě)多半有點(diǎn)燒!
3. Apache copyProperties
@Component
public class ApacheCopyPropertiesAssembler implements IAssembler<UserVO, UserDTO> {
@Override
public UserDTO sourceToTarget(UserVO var) {
UserDTO userDTO = new UserDTO();
try {
BeanUtils.copyProperties(userDTO, var);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return userDTO;
}
}
- 推薦:☆☆☆☆☆
- 性能:★☆☆☆☆
- 手段:Introspector 機(jī)制獲取到類(lèi)的屬性來(lái)進(jìn)行賦值操作
- 點(diǎn)評(píng):有坑,兼容性交差,不建議使用
4. Spring copyProperties
@Component
public class SpringCopyPropertiesAssembler implements IAssembler<UserVO, UserDTO> {
@Override
public UserDTO sourceToTarget(UserVO var) {
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(var, userDTO);
return userDTO;
}
}
- 推薦:★★★☆☆
- 性能:★★★★☆
- 手段:Introspector機(jī)制獲取到類(lèi)的屬性來(lái)進(jìn)行賦值操作
- 點(diǎn)評(píng):同樣是反射的屬性拷貝,Spring 提供的 copyProperties 要比 Apache 好用的多,只要你不用錯(cuò),基本不會(huì)有啥問(wèn)題。
5. Bean Mapping
@Component
public class BeanMappingAssembler implements IAssembler<UserVO, UserDTO> {
@Override
public UserDTO sourceToTarget(UserVO var) {
UserDTO userDTO = new UserDTO();
BeanUtil.copyProperties(var, userDTO);
return userDTO;
}
}
- 推薦:★★☆☆☆
- 性能:★★★☆☆
- 手段:屬性拷貝
- 點(diǎn)評(píng):性能一般
6. Bean Mapping ASM
@Component
public class BeanMappingAssembler implements IAssembler<UserVO, UserDTO> {
@Override
public UserDTO sourceToTarget(UserVO var) {
UserDTO userDTO = new UserDTO();
BeanUtil.copyProperties(var, userDTO);
return userDTO;
}
}
- 推薦:★★★☆☆
- 性能:★★★★☆
- 手段:基于ASM字節(jié)碼框架實(shí)現(xiàn)
- 點(diǎn)評(píng):與普通的 Bean Mapping 相比,性能有所提升,可以使用。
7. BeanCopier
@Component
public class BeanCopierAssembler implements IAssembler<UserVO, UserDTO> {
@Override
public UserDTO sourceToTarget(UserVO var) {
UserDTO userDTO = new UserDTO();
BeanCopier beanCopier = BeanCopier.create(var.getClass(), userDTO.getClass(), false);
beanCopier.copy(var, userDTO, null);
return userDTO;
}
}
- 推薦:★★★☆☆
- 性能:★★★★☆
- 手段:基于CGlib字節(jié)碼操作生成get、set方法
- 點(diǎn)評(píng):整體性能很不錯(cuò),使用也不復(fù)雜,可以使用
8. Orika
@Component
public class OrikaAssembler implements IAssembler<UserVO, UserDTO> {
/**
* 構(gòu)造一個(gè)MapperFactory
*/
private static MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
static {
mapperFactory.classMap(UserDTO.class, UserVO.class)
.field("userId", "userId") // 字段不一致時(shí)可以指定
.byDefault()
.register();
}
@Override
public UserDTO sourceToTarget(UserVO var) {
return mapperFactory.getMapperFacade().map(var, UserDTO.class);
}
}
- 官網(wǎng):https://orika-mapper./orika-docs/
- 推薦:★★☆☆☆
- 性能:★★★☆☆
- 手段:基于字節(jié)碼生成映射對(duì)象
- 點(diǎn)評(píng):測(cè)試性能不是太突出,如果使用的話(huà)需要把 MapperFactory 的構(gòu)建優(yōu)化成 Bean 對(duì)象
9. Dozer
@Component
public class DozerAssembler implements IAssembler<UserVO, UserDTO> {
private static DozerBeanMapper mapper = new DozerBeanMapper();
@Override
public UserDTO sourceToTarget(UserVO var) {
return mapper.map(var, UserDTO.class);
}
}
- 官網(wǎng):http://dozer./documentation/gettingstarted.html
- 推薦:★☆☆☆☆
- 性能:★★☆☆☆
- 手段:屬性映射框架,遞歸的方式復(fù)制對(duì)象
- 點(diǎn)評(píng):性能有點(diǎn)差,不建議使用
10. ModelMapper
@Component
public class ModelMapperAssembler implements IAssembler<UserVO, UserDTO> {
private static ModelMapper modelMapper = new ModelMapper();
static {
modelMapper.addMappings(new PropertyMap<UserVO, UserDTO>() {
@Override
protected void configure() {
// 屬性值不一樣可以自己操作
map().setUserId(source.getUserId());
}
});
}
@Override
public UserDTO sourceToTarget(UserVO var) {
return modelMapper.map(var, UserDTO.class);
}
}
- 官網(wǎng):http://
- 推薦:★★★☆☆
- 性能:★★★☆☆
- 手段:基于ASM字節(jié)碼實(shí)現(xiàn)
- 點(diǎn)評(píng):轉(zhuǎn)換對(duì)象數(shù)量較少時(shí)性能不錯(cuò),如果同時(shí)大批量轉(zhuǎn)換對(duì)象,性能有所下降
11. JMapper
JMapper<UserDTO, UserVO> jMapper = new JMapper<>(UserDTO.class, UserVO.class, new JMapperAPI()
.add(JMapperAPI.mappedClass(UserDTO.class)
.add(JMapperAPI.attribute("userId")
.value("userId"))
.add(JMapperAPI.attribute("userNickName")
.value("userNickName"))
.add(JMapperAPI.attribute("createTime")
.value("createTime"))
));
- 官網(wǎng):https://github.com/jmapper-framework/jmapper-core/wiki
- 推薦:★★★★☆
- 性能:★★★★★
- 手段:Elegance, high performance and robustness all in one java bean mapper
- 點(diǎn)評(píng):速度真心可以,不過(guò)結(jié)合 SpringBoot 感覺(jué)有的一點(diǎn)點(diǎn)麻煩,可能姿勢(shì)不對(duì)
12. MapStruct
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.IGNORE)
public interface UserDTOMapping extends IMapping<UserVO, UserDTO> {
/** 用于測(cè)試的單例 */
IMapping<UserVO, UserDTO> INSTANCE = Mappers.getMapper(UserDTOMapping.class);
@Mapping(target = "userId", source = "userId")
@Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Override
UserDTO sourceToTarget(UserVO var1);
@Mapping(target = "userId", source = "userId")
@Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Override
UserVO targetToSource(UserDTO var1);
}
- 官網(wǎng):https://github.com/mapstruct/mapstruct
- 推薦:★★★★★
- 性能:★★★★★
- 手段:直接在編譯期生成對(duì)應(yīng)的get、set,像手寫(xiě)的代碼一樣
- 點(diǎn)評(píng):速度很快,不需要到運(yùn)行期處理,結(jié)合到框架中使用方便
四、總結(jié)
- 其實(shí)對(duì)象屬性轉(zhuǎn)換的操作無(wú)非是基于反射、AOP、CGlib、ASM、Javassist 在編譯時(shí)和運(yùn)行期進(jìn)行處理,再有好的思路就是在編譯前生成出對(duì)應(yīng)的get、set,就像手寫(xiě)出來(lái)的一樣。
- 所以我更推薦我喜歡的 MapStruct,這貨用起來(lái)還是比較舒服的,一種是來(lái)自于功能上的拓展性,易用性和兼容性。
- 無(wú)論哪種使用,都要做一下完整的測(cè)試和驗(yàn)證,不要上來(lái)就復(fù)制粘貼,否則你可能早早的就把挖好坑了,當(dāng)然不一定是哪個(gè)兄弟來(lái)填坑了。
五、系列推薦
- DDD + RPC 開(kāi)發(fā)分布式架構(gòu),抽獎(jiǎng)系統(tǒng)
- SpringBoot 中間件設(shè)計(jì)和開(kāi)發(fā)
- 半年招聘篩選了400+份簡(jiǎn)歷,告訴你怎么寫(xiě)容易被撩!
- 數(shù)學(xué),離一個(gè)程序員有多近?
- 13年畢業(yè),用兩年時(shí)間從外包走進(jìn)互聯(lián)網(wǎng)大廠(chǎng)