背景/場景
我們在程式設計過程中,經常存在實體類的轉換。如資料庫層對象pojo類 與 底層對外傳輸的對象 xxResponse類。一般常用的方式是每個值去get/set。幸運的是,我們有很多架構可以解決這種問題。
常見的工具類
- Spring BeanUtils
- Apache BeanUtils
- Dozer
- Orika
- MapStruct
- ModelMapper
-
JMapper
那我們如何選擇這些工具呢
性能對比
不啰嗦,直接上圖
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPBJmd10WZ1g2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLyUDO0EzNxMTMzITNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
總之,就是 MapStruct性能在各種綜合情況下最優
MapStruct使用
maven依賴引入
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.3.1.Final</version>
</dependency>
<!-- lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
打包
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.1.Final</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
示例代碼(拷貝到IDE可直接運作)
public class Test {
public static void main(String[] args) {
UserDo userDo = new UserDo("zhangsan", 20, "酒仙橋");
UserResp userResp = SourceMapper.MAPPER.convert(userDo);
System.out.println( JSON.toJSONString(userResp));
}
}
/**
* 原始對象
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
class UserDo {
private String name;
private int age;
private String address;
}
/**
* 傳回給前端的對象
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
class UserResp {
private String name;
//這裡故意少掉 age屬性,看缺失字段是否會成功
//這裡故意寫成 addr屬性,測試名稱不同能否轉換
private String addr;
}
/**
* 轉換器
*/
@Mapper
interface SourceMapper{
SourceMapper MAPPER = Mappers.getMapper( SourceMapper.class );
/**
* 對象轉換時參數不對應使用 @Mapping注解 顯示指定值的對應關系
* 如果存在多個可使用 @Mappings注解,裡面是個集合
* 如 @Mappings(value = {
* @Mapping(source = "aa", target = "bb" ),
* @Mapping(source = "cc", target = "dd" )})
* @param userDo
* @return
*/
@Mapping( source = "address", target = "addr" )
UserResp convert( UserDo userDo );
}
代碼編譯後
在 target/generated-sources/annotations 目錄會自動生成 SourceMapper的實作類
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2020-05-23T13:08:56+0800",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_60 (Oracle Corporation)"
)
class SourceMapperImpl implements SourceMapper {
@Override
public UserResp convert(UserDo userDo) {
if ( userDo == null ) {
return null;
}
UserResp userResp = new UserResp();
userResp.setAddr( userDo.getAddress() );
userResp.setName( userDo.getName() );
return userResp;
}
}
安裝 MapStruct 插件
在 IDEA 中依次打開 File - > Settings - > Plugins
然後在 Markeyplace 搜尋框中輸入 mapstruct,點選 install,然後重新開機 IDE 即可,不再啰嗦啦
MapStruct技術總結
技術要領
- 需要寫個接口做轉換器
- 接口類上需要聲明 @Mapper 注解
- 接口中需聲明 SourceMapper MAPPER = Mappers.getMapper( SourceMapper.class );
- 接口中寫要轉換的接口:
- 參數為原始對象,
- 傳回為 要傳回的對象
- 接口上聲明要轉換的具體參數
架構優點
- 相對反射來說
- 架構使用注解生成代碼,運作時比較高效。
- 反射debug困難,這裡debug直接到生成的代碼中了
- 編寫層面來說
- 寫法快捷高效
- 手寫 set 方法屬性太多還容易漏,這裡不會
- 名字不對應恰好又不想傳回的屬性自動過濾,簡直不要太友善了
參考
- 芋道源碼mapStruct文章:http://www.iocoder.cn/Spring-Boot/MapStruct/
- 架構/工具性能對比:https://www.baeldung.com/java-performance-mapping-frameworks
- mapstruct官網:https://mapstruct.org/
- 官方文檔:https://mapstruct.org/documentation/stable/reference/html/#shared-configurations
- 官方git代碼示例:https://github.com/mapstruct/mapstruct-examples