在前幾天的文章《爲何阿里巴巴禁止使用Apache Beanutils進行屬性的copy?》中,我曾經對幾款屬性拷貝的工具類進行了對比。
前端
而後在評論區有些讀者反饋說MapStruct纔是真的香,因而我就抽時間瞭解了一下MapStruct。結果我發現,這真的是一個神仙框架,炒雞香。java
這一篇文章就來簡單介紹下MapStruct的用法,而且再和其餘幾個工具類進行一下對比。git
首先,咱們先說一下MapStruct這類框架適用於什麼樣的場景,爲何市面上會有這麼多的相似的框架。github
在軟件體系架構設計中,分層式結構是最多見,也是最重要的一種結構。不少人都對三層架構、四層架構等並不陌生。web
甚至有人說:"計算機科學領域的任何問題均可以經過增長一個間接的中間層來解決,若是不行,那就加兩層。"數據庫
可是,隨着軟件架構分層愈來愈多,那麼各個層次之間的數據模型就要面臨着相互轉換的問題,典型的就是咱們能夠在代碼中見到各類O,如DO、DTO、VO等。express
通常狀況下,一樣一個數據模型,咱們在不一樣的層次要使用不一樣的數據模型。如在數據存儲層,咱們使用DO來抽象一個業務實體;在業務邏輯層,咱們使用DTO來表示數據傳輸對象;到了展現層,咱們又把對象封裝成VO來與前端進行交互。apache
那麼,數據的從前端透傳到數據持久化層(從持久層透傳到前端),就須要進行對象之間的互相轉化,即在不一樣的對象模型之間進行映射。編程
一般咱們可使用get/set等方式逐一進行字段映射操做,如:安全
personDTO.setName(personDO.getName());
personDTO.setAge(personDO.getAge());
personDTO.setSex(personDO.getSex());
personDTO.setBirthday(personDO.getBirthday());
可是,編寫這樣的映射代碼是一項冗長且容易出錯的任務。MapStruct等相似的框架的目標是經過自動化的方式儘量多地簡化這項工做。
MapStruct(https://mapstruct.org/ )是一種代碼生成器,它極大地簡化了基於"約定優於配置"方法的Java bean類型之間映射的實現。生成的映射代碼使用純方法調用,所以快速、類型安全且易於理解。
約定優於配置,也稱做按約定編程,是一種軟 件設計範式,旨在減小軟件開發人員需作決定的數量,得到簡單的好處,而又不失靈活性。
假設咱們有兩個類須要進行互相轉換,分別是PersonDO和PersonDTO,類定義以下:
public class PersonDO {
private Integer id;
private String name;
private int age;
private Date birthday;
private String gender;
}
public class PersonDTO {
private String userName;
private Integer age;
private Date birthday;
private Gender gender;
}
咱們演示下如何使用MapStruct進行bean映射。
想要使用MapStruct,首先須要依賴他的相關的jar包,使用maven依賴方式以下:
...
<properties>
<org.mapstruct.version>1.3.1.Final</org.mapstruct.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source> <!-- depending on your project -->
<target>1.8</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
由於MapStruct須要在編譯器生成轉換代碼,因此須要在maven-compiler-plugin插件中配置上對mapstruct-processor的引用。這部分在後文會再次介紹。
以後,咱們須要定義一個作映射的接口,主要代碼以下:
@Mapper
interface PersonConverter {
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
@Mappings(@Mapping(source = "name", target = "userName"))
PersonDTO do2dto(PersonDO person);
}
使用註解@Mapper定義一個Converter接口,在其中定義一個do2dto方法,方法的入參類型是PersonDO,出參類型是PersonDTO,這個方法就用於將PersonDO轉成PersonDTO。
測試代碼以下:
public static void main(String[] args) {
PersonDO personDO = new PersonDO();
personDO.setName("Hollis");
personDO.setAge(26);
personDO.setBirthday(new Date());
personDO.setId(1);
personDO.setGender(Gender.MALE.name());
PersonDTO personDTO = PersonConverter.INSTANCE.do2dto(personDO);
System.out.println(personDTO);
}
輸出結果:
PersonDTO{userName='Hollis', age=26, birthday=Sat Aug 08 19:00:44 CST 2020, gender=MALE}
能夠看到,咱們使用MapStruct完美的將PersonDO轉成了PersonDTO。
上面的代碼能夠看出,MapStruct的用法比較簡單,主要依賴@Mapper註解。
可是咱們知道,大多數狀況下,咱們須要互相轉換的兩個類之間的屬性名稱、類型等並不徹底一致,還有些狀況咱們並不想直接作映射,那麼該如何處理呢?
其實MapStruct在這方面也是作的很好的。
首先,能夠明確的告訴你們,若是要轉換的兩個類中源對象屬性與目標對象屬性的類型和名字一致的時候,會自動映射對應屬性。
那麼,若是遇到特殊狀況如何處理呢?
名字不一致如何映射
如上面的例子中,在PersonDO中用name表示用戶名稱,而在PersonDTO中使用userName表示用戶名,那麼如何進行參數映射呢。
這時候就要使用@Mapping註解了,只須要在方法簽名上,使用該註解,並指明須要轉換的源對象的名字和目標對象的名字就能夠了,如將name的值映射給userName,可使用以下方式:
@Mapping(source = "name", target = "userName")
能夠自動映射的類型
除了名字不一致之外,還有一種特殊狀況,那就是類型不一致,如上面的例子中,在PersonDO中用String類型表示用戶性別,而在PersonDTO中使用一個Genter的枚舉表示用戶性別。
這時候類型不一致,就須要涉及到互相轉換的問題
其實,MapStruct會對部分類型自動作映射,不須要咱們作額外配置,如例子中咱們將String類型自動轉成了枚舉類型。
通常狀況下,對於如下狀況能夠作自動類型轉換:
-
基本類型及其餘們對應的包裝類型。 -
基本類型的包裝類型和String類型之間 -
String類型和枚舉類型之間
自定義常量
@Mapping(source = "name", constant = "hollis")
類型不一致的如何映射
public class PersonDO {
private String name;
private String address;
}
public class PersonDTO {
private String userName;
private HomeAddress address;
}
@Mapper
interface PersonConverter {
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
@Mapping(source = "userName", target = "name")
@Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")
PersonDO dto2do(PersonDTO dto2do);
default String homeAddressToString(HomeAddress address){
return JSON.toJSONString(address);
}
}
default方法: Java 8 引入的新的語言特性,用關鍵字default來標註,被default所標註的方法,須要提供實現,而子類能夠選擇實現或者不實現該方法
@Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")
@Mapping(target = "birthday",dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapper
interface PersonConverter {
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
@Mapping(source = "userName", target = "name")
@Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")
@Mapping(target = "birthday",dateFormat = "yyyy-MM-dd HH:mm:ss")
PersonDO dto2do(PersonDTO dto2do);
default String homeAddressToString(HomeAddress address){
return JSON.toJSONString(address);
}
}
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2020-08-09T12:58:41+0800",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)"
)
class PersonConverterImpl implements PersonConverter {
@Override
public PersonDO dto2do(PersonDTO dto2do) {
if ( dto2do == null ) {
return null;
}
PersonDO personDO = new PersonDO();
personDO.setName( dto2do.getUserName() );
if ( dto2do.getAge() != null ) {
personDO.setAge( dto2do.getAge() );
}
if ( dto2do.getGender() != null ) {
personDO.setGender( dto2do.getGender().name() );
}
personDO.setAddress( homeAddressToString(dto2do.getAddress()) );
return personDO;
}
}
強烈推薦,真的很香!!!
往期推薦
用了Dapper以後通篇仍是SqlConnection,真的看不下去了
實用!一鍵生成數據庫文檔,堪稱數據庫界的Swagger
爲何阿里巴巴要求日期格式化時必須有使用y表示年,而不能用Y?
直面Java第329期:哪一個命令能夠監控虛擬機各類運行狀態信息?
深刻併發第013期:拓展synchronized——鎖優化
若是你喜歡本文,
請長按二維碼,關注 Hollis.
轉發至朋友圈,是對我最大的支持。
本文分享自微信公衆號 - Hollis(hollischuang)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。