作者:小傅哥
部落格:https://bugstack.cn
原文:https://mp.weixin.qq.com/s/Xq7oQg7dYESMYxHVnxX8Dw
沉澱、分享、成長,讓自己和他人都能有所收穫!?
一、前言
為哈麼,你的程式碼也就僅僅是能用而已?
沒有技術深度、短缺知識儲備、匱乏經驗積累的前提下,怎麼寫程式碼?百度呀,遇到問題這搜一點,那查一塊,不管它是什麼原理還是適合哪種場景,先貼上到自己的工程裡,看,能跑了,能跑就行。那這樣的程式碼也就僅僅是能用程度的交付,根本沒有一定的質量保證,也更別提資料結構、演算法邏輯和設計模式了,那看的程式設計資料刷的LeetCode,全歇菜了。
當你感覺看了很多資料又不會用的時候,會說什麼,真卷,都學到這樣了
。但其實我並不覺對技術的深度挖掘、梳理全套的知識體系,一點點耕耘一點點收穫是在卷。反而把看技術視訊當成看電影一樣輕鬆,不寫案例就以為書看會了的爽,沒有意義的缺少腦力思考機械式體力重複,才是卷,甚至很卷。
就像讓你用一個屬性拷貝工具,把vo轉成dto
,你用了哪呢,是 Apache 的還是 Spring 的,還是其他的什麼,哪個效率最高?接下來我們來用資料驗證下,並提供出各種案例的使用對比
二、效能測試對比
在 Java 系統工程開發過程中,都會有各個層之間的物件轉換,比如 VO、DTO、PO、VO 等,而如果都是手動get、set
又太浪費時間,還可能操作錯誤,所以選擇一個自動化工具會更加方便。
目前我整理出,用於物件屬性轉換有12種,包括:普通的getset、json2Json、Apache屬性拷貝、Spring屬性拷貝、bean-mapping、bean-mapping-asm、BeanCopier、Orika、Dozer、ModelMapper、JMapper、MapStruct 接下來我們分別測試這11種屬性轉換操作分別在一百次
、一千次
、一萬次
、十萬次
、一百萬次
時候的效能時間對比。
BeanUtils.copyProperties
是大家程式碼裡最常出現的工具類,但只要你不把它用錯成Apache
包下的,而是使用 Spring 提供的,就基本還不會對效能造成多大影響。- 但如果說效能更好,可替代手動
get、set
的,還是MapStruct
更好用,因為它本身就是在編譯期生成get、set
程式碼,和我們寫get、set
一樣。 - 其他一些元件包主要基於
AOP
、ASM
、CGlib
,的技術手段實現的,所以也會有相應的效能損耗。
三、12種轉換案例
原始碼:https://github.com/fuzhengwei/guide-vo2dto
描述:在案例工程下建立 interfaces.assembler 包,定義 IAssembler<SOURCE, TARGET>#sourceToTarget(SOURCE var)
介面,提供不同方式的物件轉換操作類實現,學習的過程中可以直接下載執行除錯。
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;
}
}
- 推薦:★★★☆☆
- 效能:★★★★★
- 手段:手寫
- 點評:其實這種方式也是日常使用的最多的,效能肯定是槓槓的,就是操作起來有點麻煩。尤其是一大堆屬性的 VO 物件轉換為 DTO 物件時候。但其實也有一些快捷的操作方式,比如你可以通過 Shift+Alt 選中所有屬性,Shift+Tab 歸併到一列,接下來在使用 Alt 選中這一列,批量操作貼上
userDTO.set
以及快捷鍵大寫屬性首字母,最後切換到結尾補充括號和分號,最終格式化一下就搞定了。
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);
}
}
- 推薦:☆☆☆☆☆
- 效能:★☆☆☆☆
- 手段:把物件轉JSON串,再把JSON轉另外一個物件
- 點評:這麼寫多半有點燒!
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 機制獲取到類的屬性來進行賦值操作
- 點評:有坑,相容性交差,不建議使用
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機制獲取到類的屬性來進行賦值操作
- 點評:同樣是反射的屬性拷貝,Spring 提供的 copyProperties 要比 Apache 好用的多,只要你不用錯,基本不會有啥問題。
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;
}
}
- 推薦:★★☆☆☆
- 效能:★★★☆☆
- 手段:屬性拷貝
- 點評:效能一般
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位元組碼框架實現
- 點評:與普通的 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位元組碼操作生成get、set方法
- 點評:整體效能很不錯,使用也不復雜,可以使用
8. Orika
@Component
public class OrikaAssembler implements IAssembler<UserVO, UserDTO> {
/**
* 構造一個MapperFactory
*/
private static MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
static {
mapperFactory.classMap(UserDTO.class, UserVO.class)
.field("userId", "userId") // 欄位不一致時可以指定
.byDefault()
.register();
}
@Override
public UserDTO sourceToTarget(UserVO var) {
return mapperFactory.getMapperFacade().map(var, UserDTO.class);
}
}
- 官網:https://orika-mapper.github.io/orika-docs/
- 推薦:★★☆☆☆
- 效能:★★★☆☆
- 手段:基於位元組碼生成對映物件
- 點評:測試效能不是太突出,如果使用的話需要把 MapperFactory 的構建優化成 Bean 物件
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);
}
}
- 官網:http://dozer.sourceforge.net/documentation/gettingstarted.html
- 推薦:★☆☆☆☆
- 效能:★★☆☆☆
- 手段:屬性對映框架,遞迴的方式複製物件
- 點評:效能有點差,不建議使用
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);
}
}
- 官網:http://modelmapper.org
- 推薦:★★★☆☆
- 效能:★★★☆☆
- 手段:基於ASM位元組碼實現
- 點評:轉換物件數量較少時效能不錯,如果同時大批量轉換物件,效能有所下降
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"))
));
- 官網:https://github.com/jmapper-framework/jmapper-core/wiki
- 推薦:★★★★☆
- 效能:★★★★★
- 手段:Elegance, high performance and robustness all in one java bean mapper
- 點評:速度真心可以,不過結合 SpringBoot 感覺有的一點點麻煩,可能姿勢不對
12. MapStruct
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.IGNORE)
public interface UserDTOMapping extends IMapping<UserVO, UserDTO> {
/** 用於測試的單例 */
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);
}
- 官網:https://github.com/mapstruct/mapstruct
- 推薦:★★★★★
- 效能:★★★★★
- 手段:直接在編譯期生成對應的get、set,像手寫的程式碼一樣
- 點評:速度很快,不需要到執行期處理,結合到框架中使用方便
四、總結
- 其實物件屬性轉換的操作無非是基於反射、AOP、CGlib、ASM、Javassist 在編譯時和執行期進行處理,再有好的思路就是在編譯前生成出對應的get、set,就像手寫出來的一樣。
- 所以我更推薦我喜歡的 MapStruct,這貨用起來還是比較舒服的,一種是來自於功能上的擴充性,易用性和相容性。
- 無論哪種使用,都要做一下完整的測試和驗證,不要上來就複製貼上,否則你可能早早的就把挖好坑了,當然不一定是哪個兄弟來填坑了。