MapStruct 進(jìn)階技巧:提升代碼效率
前言
MapStruct 是一個(gè) Java 編譯時(shí)注解處理框架,用來(lái)自動(dòng)化將一種 Java Bean 對(duì)象映射成另一種類型的對(duì)象。
該框架的主要目標(biāo)是使開發(fā)人員在盡可能少的代碼和最低的運(yùn)行時(shí)間成本下實(shí)現(xiàn)屬性映射。MapStruct 通過(guò)在編譯時(shí)生成代碼來(lái)實(shí)現(xiàn)這點(diǎn),這與大多數(shù)其他 Java Bean 映射框架在運(yùn)行時(shí)通過(guò)反射進(jìn)行映射形成了鮮明對(duì)比。
MapStruct 具有以下主要特性:
- 簡(jiǎn)潔: 簡(jiǎn)化了 Java Beans 之間轉(zhuǎn)換的代碼,自動(dòng)生成使用簡(jiǎn)單的賦值語(yǔ)句完成的映射實(shí)現(xiàn)。
- 性能優(yōu)秀: 由于 MapStruct 是在編譯時(shí)生成代碼,不涉及任何反射,因此執(zhí)行映射的性能優(yōu)越。
- 安全: 通過(guò)在編譯時(shí)生成映射代碼,MapStruct 提供了類型安全的映射,并能在編譯時(shí)就發(fā)現(xiàn)潛在的錯(cuò)誤。
- 靈活: 可通過(guò)自定義轉(zhuǎn)換方法、類型轉(zhuǎn)換和映射策略等來(lái)滿足復(fù)雜的映射需求。
- 良好的 IDE 支持: 由于 MapStruct 是編譯時(shí)工具,所以擁有良好的 IDE 集成,如代碼自動(dòng)完成、錯(cuò)誤高亮等。
總的來(lái)說(shuō), MapStruct 是一個(gè)強(qiáng)大且靈活的映射框架,很好的解決有關(guān)對(duì)象轉(zhuǎn)換的問(wèn)題,實(shí)現(xiàn)了代碼的簡(jiǎn)潔和性能的兼顧。MapStruct的常規(guī)用法,網(wǎng)上有很多教程了,本文將列舉一些進(jìn)階用法,方便日常開發(fā)使用。
expression
在轉(zhuǎn)化的時(shí)候,執(zhí)行 java 表達(dá)式,直接看例子:
@Mapper(componentModel = 'spring')
public interface MyMapper {
@Mapping(target = 'createTime', expression = 'java(System.currentTimeMillis())')
Target toTarget(Source source);
}
轉(zhuǎn)化成 target 對(duì)象時(shí),createTime字段的值,會(huì)設(shè)置為System.currentTimeMillis(),生成的代碼如下:
@Component
public class MyMapperImpl implements MyMapper {
@Override
public Target toTarget(Source source) {
Target target = new Target();
target.setCreateTime( System.currentTimeMillis() );
return target;
}
}
qualifiedByName
做映射時(shí),默認(rèn)情況下,從source 字段到target 字段是直接使用 get/set,如下:
@Data
public class Source {
private String name;
}
@Data
public class Target {
private String name;
}
@Mapper(componentModel = 'spring')
public interface MyMapper {
Target toTarget(Source source);
}
生成的轉(zhuǎn)化代碼類如下:
@Component
public class MyMapperImpl implements MyMapper {
@Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
// 無(wú)腦 set/get
target.setName( source.getName() );
return target;
}
}
如果這種直接的 set/get 無(wú)法滿足需求,比如需要把 name 轉(zhuǎn)化成大寫格式,那么可以使用qualifiedByName:
@Mapper(componentModel = 'spring')
public interface MyMapper {
@Mapping(target = 'name', source = 'name', qualifiedByName = 'toUpperCase')
Target toTarget(Source source);
@Named('toUpperCase')
default String toUpperCase(String value) {
// 這里寫轉(zhuǎn)換大寫的邏輯
return value == null ? null : value.toUpperCase();
}
}
生成的代碼如下:
@Component
public class MyMapperImpl implements MyMapper {
@Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.setName( toUpperCase( source.getName() ) );
return target;
}
}
nullValueMappingStrategy
如果 source 為 null 時(shí),對(duì)應(yīng)的 target 的處理策略,默認(rèn)是 NullValueMappingStrategy.RETURN_NULL,即 target 中對(duì)應(yīng)的字段也設(shè)置為 null。
但有時(shí)候設(shè)置為 null 可能不符合我們的需求,比如 target 中有個(gè) List ids,我們希望如果 source 中ids 為 null 時(shí),target 的 ids 設(shè)置為空 list。這時(shí)候可以使用nullValueMappingStrategy策略中的NullValueMappingStrategy.RETURN_DEFAULT。
nullValueMappingStrategy 可以使用在某個(gè)方法上(只對(duì)該方法生效),也可以使用在類上(對(duì)類中的所有方法都生效),如下:
@Component
public class MyMapperImpl implements MyMapper {
@Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.setName( source.getName() );
List<Integer> list = source.getIds();
if ( list != null ) {
target.setIds( new ArrayList<Integer>( list ) );
}
else {
target.setIds( null );
}
return target;
}
}
指定NullValueMappingStrategy.RETURN_DEFAULT策略后:
@Mapper(componentModel = 'spring',
nullValueMappingStrategy = org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT)
public interface MyMapper {
Target toTarget(Source source);
}
@Component
public class MyMapperImpl implements MyMapper {
@Override
public Target toTarget(Source source) {
Target target = new Target();
if ( source != null ) {
target.setName( toUpperCase( source.getName() ) );
List<Integer> list = source.getIds();
if ( list != null ) {
target.setIds( new ArrayList<Integer>( list ) );
}
else {
target.setIds( new ArrayList<Integer>() );
}
}
return target;
}
}
可以看到,當(dāng) source 或者 source.ids 為 null 時(shí),返回的 target 和 target.ids 都是默認(rèn)的空值(空對(duì)象+空 list)。
Decorator
你可以通過(guò)創(chuàng)建一個(gè) Decorator 類來(lái)對(duì)你的方法進(jìn)行修飾并實(shí)現(xiàn)全局處理。
以下是一個(gè)例子:
public abstract class YourMapperDecorator implements YourMapper {
private final YourMapper delegate;
public YourMapperDecorator(YourMapper delegate) {
this.delegate = delegate;
}
@Override
public Target toTarget(Source source) {
Target result = delegate.toTarget(source);
if (result != null) {
if (result.getField() == null) {
result.setField('');
}
// 你可以在這里對(duì)其他字段進(jìn)行同樣的處理...
}
return result;
}
}
然后你只需在你的 Mapper 接口上添加 @DecoratedWith 注解:
@Mapper
@DecoratedWith(YourMapperDecorator.class)
public interface YourMapper {
Target toTarget(Source source);
}
這樣,每次調(diào)用 toTarget 方法時(shí),YourMapperDecorator 中的實(shí)現(xiàn)會(huì)被調(diào)用。在這里,你可以實(shí)現(xiàn)任何你想要的邏輯,例如對(duì)空字段賦予特定的默認(rèn)值。