在我們?nèi)粘i_發(fā)的程序中,為了各層之間解耦,一般會(huì)定義不同的對(duì)象用來在不同層之間傳遞數(shù)據(jù),比如xxxDTO、xxxVO、xxxQO,當(dāng)在不同層之間傳輸數(shù)據(jù)時(shí),不可避免地經(jīng)常需要將這些對(duì)象進(jìn)行相互轉(zhuǎn)換。
今天給大家介紹一個(gè)對(duì)象轉(zhuǎn)換工具M(jìn)apStruct,代碼簡潔安全、性能高,強(qiáng)烈推薦。
MapStruct簡介
MapStruct是一個(gè)代碼生成器,它基于約定優(yōu)于配置,極大地簡化了Java Bean類型之間映射的實(shí)現(xiàn)。特點(diǎn)如下:
- 基于注解
- 在編譯期自動(dòng)生成映射轉(zhuǎn)換代碼
- 類型安全、高性能、無依賴性、易于理解閱讀
MapStruct入門
1. 引入依賴
這里使用Gradle構(gòu)建
dependencies {
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}
2. 需要轉(zhuǎn)換的對(duì)象
創(chuàng)建兩個(gè)示例對(duì)象(e.g. 將Demo對(duì)象轉(zhuǎn)換為DemoDto對(duì)象)
/**
* 源對(duì)象
*/
@Data
public class Demo {
private Integer id;
private String name;
}
/**
* 目標(biāo)對(duì)象
*/
@Data
public class DemoDto {
private Integer id;
private String name;
}
3. 創(chuàng)建轉(zhuǎn)換器
只需要?jiǎng)?chuàng)建一個(gè)轉(zhuǎn)換器接口類,并在類上添加 @Mapper 注解即可(官方示例推薦以 xxxMapper 格式命名轉(zhuǎn)換器名稱)
@Mapper
public interface DemoMapper {
//使用Mappers工廠獲取DemoMapper實(shí)現(xiàn)類
DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);
//定義接口方法,參數(shù)為來源對(duì)象,返回值為目標(biāo)對(duì)象
DemoDto toDemoDto(Demo demo);
}
4. 驗(yàn)證
public static void main(String[] args) {
Demo demo = new Demo();
demo.setId(111);
demo.setName("hello");
DemoDto demoDto = DemoMapper.INSTANCE.toDemoDto(demo);
System.out.println("目標(biāo)對(duì)象demoDto為:" + demoDto);
//輸出結(jié)果:目標(biāo)對(duì)象demoDto為:DemoDto(id=111, name=hello)
}
測(cè)試結(jié)果如下:
目標(biāo)對(duì)象demoDto為:DemoDto(id=111, name=hello)
達(dá)到了我們的預(yù)期結(jié)果。
5. 自動(dòng)生成的實(shí)現(xiàn)類
為什么聲明一個(gè)接口就可以轉(zhuǎn)換對(duì)象呢?我們看一下MapStruct在編譯期間自動(dòng)生成的實(shí)現(xiàn)類:
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-09-01T17:54:38+0800",
comments = "version: 1.4.2.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-7.3.jar, environment: Java 1.8.0_231 (Oracle Corporation)"
)
public class DemoMapperImpl implements DemoMapper {
@Override
public DemoDto toDemoDto(Demo demo) {
if ( demo == null ) {
return null;
}
DemoDto demoDto = new DemoDto();
demoDto.setId( demo.getId() );
demoDto.setName( demo.getName() );
return demoDto;
}
}
可以看到,MapStruct幫我們將繁雜的代碼自動(dòng)生成了,而且實(shí)現(xiàn)類中用的都是最基本的get、set方法,易于閱讀理解,轉(zhuǎn)換速度非???。
MapStruct進(jìn)階
上面的例子只是小試牛刀,下面開始展示MapStruct的強(qiáng)大之處。
(限于篇幅,這里不展示自動(dòng)生成的實(shí)現(xiàn)類和驗(yàn)證結(jié)果,大家可自行測(cè)試)
場(chǎng)景1:屬性名稱不同、(基本)類型不同
- 屬性名稱不同: 在方法上加上 @Mapping 注解,用來映射屬性
- 屬性基本類型不同: 基本類型和String等類型會(huì)自動(dòng)轉(zhuǎn)換
關(guān)鍵字:@Mapping注解
/**
* 來源對(duì)象
*/
@Data
public class Demo {
private Integer id;
private String name;
}
/**
* 目標(biāo)對(duì)象
*/
@Data
public class DemoDto {
private String id;
private String fullname;
}
/**
* 轉(zhuǎn)換器
*/
@Mapper
public interface DemoMapper {
DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);
@Mapping(target = "fullname", source = "name")
DemoDto toDemoDto(Demo demo);
}
場(chǎng)景2:統(tǒng)一映射不同類型
下面例子中,time1、time2、time3都會(huì)被轉(zhuǎn)換,具體說明看下面的注釋:
/**
* 來源對(duì)象
*/
@Data
public class Demo {
private Integer id;
private String name;
/**
* time1、time2名稱相同,time3轉(zhuǎn)為time33
* 這里的time1、time2、time33都是Date類型
*/
private Date time1;
private Date time2;
private Date time3;
}
/**
* 目標(biāo)對(duì)象
*/
@Data
public class DemoDto {
private String id;
private String name;
/**
* 這里的time1、time2、time33都是String類型
*/
private String time1;
private String time2;
private String time33;
}
/**
* 轉(zhuǎn)換器
*/
@Mapper
public interface DemoMapper {
DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);
@Mapping(target = "time33", source = "time3")
DemoDto toDemoDto(Demo demo);
//MapStruct會(huì)將所有匹配到的:
//源類型為Date、目標(biāo)類型為String的屬性,
//按以下方法進(jìn)行轉(zhuǎn)換
static String date2String(Date date) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strDate = simpleDateFormat.format(date);
return strDate;
}
}
場(chǎng)景3:固定值、忽略某個(gè)屬性、時(shí)間轉(zhuǎn)字符串格式
一個(gè)例子演示三種用法,具體說明看注釋,很容易理解:
關(guān)鍵字:ignore、constant、dateFormat
/**
* 來源對(duì)象
*/
@Data
public class Demo {
private Integer id;
private String name;
private Date time;
}
/**
* 目標(biāo)對(duì)象
*/
@Data
public class DemoDto {
private String id;
private String name;
private String time;
}
/**
* 轉(zhuǎn)換器
*/
@Mapper
public interface DemoMapper {
DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);
//id屬性不賦值
@Mapping(target = "id", ignore = true)
//name屬性固定賦值為“hello”
@Mapping(target = "name", constant = "hello")
//time屬性轉(zhuǎn)為yyyy-MM-dd HH:mm:ss格式的字符串
@Mapping(target = "time", dateFormat = "yyyy-MM-dd HH:mm:ss")
DemoDto toDemoDto(Demo demo);
}
場(chǎng)景4:為某個(gè)屬性指定轉(zhuǎn)換方法
場(chǎng)景2中,我們是按照某個(gè)轉(zhuǎn)換方法,統(tǒng)一將一種類型轉(zhuǎn)換為另外一種類型;而下面這個(gè)例子,是為某個(gè)屬性指定方法:
關(guān)鍵字:@Named注解、qualifiedByName
/**
* 來源對(duì)象
*/
@Data
public class Demo {
private Integer id;
private String name;
}
/**
* 目標(biāo)對(duì)象
*/
@Data
public class DemoDto {
private String id;
private String name;
}
/**
* 轉(zhuǎn)換器
*/
@Mapper
public interface DemoMapper {
DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);
//為name屬性指定@Named為convertName的方法進(jìn)行轉(zhuǎn)換
@Mapping(target = "name", qualifiedByName = "convertName")
DemoDto toDemoDto(Demo demo);
@Named("convertName")
static String aaa(String name) {
return "姓名為:" + name;
}
}
場(chǎng)景5:多個(gè)參數(shù)合并為一個(gè)對(duì)象
如果參數(shù)為多個(gè)的話,@Mapping注解中的source就要指定是哪個(gè)參數(shù)了,用點(diǎn)分隔:
關(guān)鍵字:點(diǎn)(.)
/**
* 來源對(duì)象
*/
@Data
public class Demo {
private Integer id;
private String name;
}
/**
* 目標(biāo)對(duì)象
*/
@Data
public class DemoDto {
private String fullname;
private String timestamp;
}
/**
* 轉(zhuǎn)換器
*/
@Mapper
public interface DemoMapper {
DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);
//fullname屬性賦值demo對(duì)象的name屬性(注意這里.的用法)
//timestamp屬性賦值為傳入的time參數(shù)
@Mapping(target = "fullname", source = "demo.name")
@Mapping(target = "timestamp", source = "time")
DemoDto toDemoDto(Demo demo, String time);
}
場(chǎng)景6:已有目標(biāo)對(duì)象,將源對(duì)象屬性覆蓋到目標(biāo)對(duì)象
覆蓋目標(biāo)對(duì)象屬性時(shí),一般null值不覆蓋,所以需要在類上的@Mapper注解中添加屬性:nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE 代表null值不進(jìn)行賦值。
關(guān)鍵字:@MappingTarget注解、nullValuePropertyMappingStrategy
/**
* 來源對(duì)象
*/
@Data
public class Demo {
private Integer id;
private String name;
}
/**
* 目標(biāo)對(duì)象
*/
@Data
public class DemoDto {
private String id;
private String name;
}
/**
* 轉(zhuǎn)換器
*/
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE,
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface DemoMapper {
DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);
//將已有的目標(biāo)對(duì)象當(dāng)作一個(gè)參數(shù)傳進(jìn)來
DemoDto toDemoDto(Demo demo, @MappingTarget DemoDto dto);
}
場(chǎng)景7:源對(duì)象兩個(gè)屬性合并為一個(gè)屬性
這種情況可以使用@AfterMapping注解。
關(guān)鍵字:@AfterMapping注解、@MappingTarget注解
/**
* 來源對(duì)象
*/
@Data
public class Demo {
private Integer id;
private String firstName;
private String lastName;
}
/**
* 目標(biāo)對(duì)象
*/
@Data
public class DemoDto {
private String id;
private String name;
}
/**
* 轉(zhuǎn)換器
*/
@Mapper
public interface DemoMapper {
DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);
DemoDto toDemoDto(Demo demo);
//在轉(zhuǎn)換完成后執(zhí)行的方法,一般用到源對(duì)象兩個(gè)屬性合并為一個(gè)屬性的場(chǎng)景
//需要將源對(duì)象、目標(biāo)對(duì)象(@MappingTarget)都作為參數(shù)傳進(jìn)來,
@AfterMapping
static void afterToDemoDto(Demo demo, @MappingTarget DemoDto demoDto) {
String name = demo.getFirstName() + demo.getLastName();
demoDto.setName(name);
}
}
小結(jié)
本文介紹了對(duì)象轉(zhuǎn)換工具 MapStruct 庫,以安全、簡潔、優(yōu)雅的方式來優(yōu)化我們的轉(zhuǎn)換代碼。
從文中的示例場(chǎng)景中可以看出,MapStruct 提供了大量的功能和配置,使我們可以快捷的創(chuàng)建出各種或簡單或復(fù)雜的映射器。而這些,也只是 MapStruct 庫的冰山一角,還有很多強(qiáng)大的功能文中沒有提到,感興趣的朋友可以自行查看官方文檔。
-
對(duì)象
+關(guān)注
關(guān)注
1文章
38瀏覽量
17354 -
傳輸數(shù)據(jù)
+關(guān)注
關(guān)注
1文章
108瀏覽量
16072 -
代碼生成器
+關(guān)注
關(guān)注
0文章
25瀏覽量
9066
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論