struct用法 java_MapStruct用法

1 MapStruct配置

MapStuct的使用非常简单,把对应的jar包引入即可。

1.3.1.Final

org.mapstruct

mapstruct

${mapstruct.version}

org.mapstruct

mapstruct-processor

${mapstruct.version}

2 原理&性能

2.1 实现原理

对象拷贝工具实现上一般分为2种:

(1) 在运行时,通过反射调用set/get方法或者直接对成员变量进行赋值。

(2)在编译期,动态生成调用get/set方法赋值的代码,直接生成对应的class文件。

MapStrut属于第二种,在编译期间消耗少许的时间,换取运行时的高性能。

接口声明:

@Mapper

public interfaceProductAssembler {

SkuDTO toDTO(Sku sku);

}

编辑生成的class反编译

public class ProductAssemblerImpl implementsProductAssembler {

@Override

publicSkuDTO toDTO(Sku sku) {

if ( sku == null) {

return null;

}

SkuDTO skuDTO = newSkuDTO();

skuDTO.setSkuId( sku.getSkuId() );

returnskuDTO;

}

}

2.2 性能对比

20200606010912137343.png

3 使用方法

使用Mapper注解,声明使用MapStruct实现的接口;

使用Mapping注解,实现灵活的字段映射

3.1 转换器的检索

在声明好转换接口之后,MapStruct提供几种方式获取生成的Mapper实现。

3.1.1 使用Mappers工厂获取

可以通过提供的Mappers工厂类,获取指定的类型。

@Mapper

public interfaceAssembler {

//使用工厂方法获取Mapper实例

Assembler INSTANCE = Mappers.getMapper(Assembler.class);

ProductDTO toDTO(Product product);

}

3.1.2 通过依赖注入的方式获取

MapStuct同时支持和其他框架结合,通过依赖注入的方式获取Mapper实例。目前支持spring和cdi。

@Mapper(componentModel = "spring")

public interfaceAssembler {

ProductDTO toDTO(Product product);

}

@Component

public class AssemblerImpl implementsAssembler {

@Override

publicProductDTO toDTO(Product product) {

if ( product == null) {

return null;

}

ProductDTO productDTO = newProductDTO();

productDTO.setProductId( product.getProductId() );

returnproductDTO;

}

}

3.2 简单映射

对于同名同属性的字段,无需特别声明指定,自动转换。

对于不同名相同属性的字段,可以使用Mapping注解指定。

@Data

@NoArgsConstructor

@AllArgsConstructor

public class Product {

private String productId;

private String name;

}

@Data

@NoArgsConstructor

@AllArgsConstructor

public class ProductDTO implementsSerializable {

private static final long serialVersionUID = -6780322740093464581L;

privateString productId;

privateString productName;

}

@Mapper(componentModel = "spring")

public interfaceAssembler {

@Mapping(source = "name", target = "productName")

ProductDTO toDTO(Product product);

}

@Component

public class AssemblerImpl implementsAssembler {

@Override

publicProductDTO toDTO(Product product) {

if ( product == null) {

return null;

}

ProductDTO productDTO = newProductDTO();

productDTO.setProductName( product.getName() ); //不同字段名映射

productDTO.setProductId( product.getProductId() );

returnproductDTO;

}

}

3.3 数据类型转换

3.3.1 对于基本的数据类型会进行自动隐式的转换

如int、long、String,Integer等。

@Data

@NoArgsConstructor

@AllArgsConstructor

public classProduct {

privateString productId;

privateLong price;

}

@Data

@NoArgsConstructor

@AllArgsConstructor

public class ProductDTO implementsSerializable {

private static final long serialVersionUID = -6780322740093464581L;

private intproductId;

privateString price;

}

@Mapper(componentModel = "spring")

public interfaceAssembler {

ProductDTO toDTO(Product product);

}

@Component

public class AssemblerImpl implementsAssembler {

@Override

publicProductDTO toDTO(Product product) {

if ( product == null) {

return null;

}

ProductDTO productDTO = newProductDTO();

if ( product.getProductId() != null) {

//String自动转int

productDTO.setProductId( Integer.parseInt( product.getProductId() ) );

}

if ( product.getPrice() != null) {

//Long转String

productDTO.setPrice( String.valueOf( product.getPrice() ) );

}

returnproductDTO;

}

}

3.3.2 指定转换格式

(1)对于基本数据类型与String之间的转换,可以使用 numberFormat 指定转换格式,使用的是java.text.DecimalFormat 实现。

@Data

@NoArgsConstructor

@AllArgsConstructor

public classProduct {

privateString productId;

privateBigDecimal price;

privateString stock;

}

@Data

@NoArgsConstructor

@AllArgsConstructor

public class ProductDTO implementsSerializable {

private static final long serialVersionUID = -6780322740093464581L;

privateString productId;

privateString price;

privateInteger stock;

}

@Mapper(componentModel = "spring")

public interfaceDemo3Assembler {

@Mapping(target = "price", numberFormat = "#.00元")

@Mapping(target = "stock", numberFormat = "#个")

ProductDTO toDTO(Product product);

}

@Component

public class Demo3AssemblerImpl implementsDemo3Assembler {

@Override

publicProductDTO toDTO(Product product) {

if ( product == null) {

return null;

}

ProductDTO productDTO = newProductDTO();

productDTO.setProductId( product.getProductId() );

if ( product.getPrice() != null) {

//BigDecimal格式化成字符串

productDTO.setPrice( createDecimalFormat( "#.00元").format( product.getPrice() ) );

}

try{

if ( product.getStock() != null) {

//字符串格式化为int

productDTO.setStock( new DecimalFormat( "#个").parse( product.getStock() ).intValue() );

}

}

catch( ParseException e ) {

throw newRuntimeException( e );

}

returnproductDTO;

}

privateDecimalFormat createDecimalFormat( String numberFormat ) {

DecimalFormat df = newDecimalFormat( numberFormat );

df.setParseBigDecimal( true);

returndf;

}

}

@Test

public voidtest2() {

com.gotten.study.mapstruct.demo3.Product product = newcom.gotten.study.mapstruct.demo3.Product ();

product.setProductId("P001");

product.setPrice(new BigDecimal("100"));

product.setStock("1个");

com.gotten.study.mapstruct.demo3.ProductDTO productDTO =demo3Assembler.toDTO(product);

System.out.println("productDTO:" +JSON.toJSONString(productDTO));

}

productDTO:{"price":"100.00元","productId":"P001","stock":1}

(2)Date和String之间的转换,可以通过dateFormat指定转换格式,使用的是SimpleDateFormat的实现。

@Data

@NoArgsConstructor

@AllArgsConstructor

public classProduct {

privateString productId;

privateDate saleTime;

privateString validTime;

}

@Data

@NoArgsConstructor

@AllArgsConstructor

public class ProductDTO implementsSerializable {

private static final long serialVersionUID = -6780322740093464581L;

privateString productId;

privateString saleTime;

privateDate validTime;

}

@Mapper(componentModel = "spring")

public interfaceDemo4Assembler {

@Mapping(target = "saleTime", dateFormat = "yyyy-MM-dd HH:mm:ss")

@Mapping(target = "validTime", dateFormat = "yyyy-MM-dd HH:mm")

ProductDTO toDTO(Product product);

}

@Component

public class Demo4AssemblerImpl implementsDemo4Assembler {

@Override

publicProductDTO toDTO(Product product) {

if ( product == null) {

return null;

}

ProductDTO productDTO = newProductDTO();

productDTO.setProductId( product.getProductId() );

if ( product.getSaleTime() != null) {

productDTO.setSaleTime( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( product.getSaleTime() ) ); //转换成String

}

try{

if ( product.getValidTime() != null) {

productDTO.setValidTime( new SimpleDateFormat( "yyyy-MM-dd HH:mm" ).parse( product.getValidTime() ) ); //转换成Date

}

}

catch( ParseException e ) {

throw newRuntimeException( e );

}

returnproductDTO;

}

}

3.3.3 对象引用的映射

(1)对应是相同类型的对象引用,直接简单的对引用进行拷贝。

(2)如果类型相同,但是是集合类的引用,会创建一个新的集合,集合里面的所有引用进行拷贝。

@Override

publicProductDTO toDTO(Product product) {

if ( product == null) {

return null;

}

ProductDTO productDTO = newProductDTO();

productDTO.setProductId( product.getProductId() );

List list =product.getSkuList();

if ( list != null) {

productDTO.setSkuList( new ArrayList( list ) ); //创建新的集合,并对所有元素进行拷贝

}

returnproductDTO;

}

(3)对象的类型不同,会检查是否存在对应的映射方法或者默认的类型转换器,否则会尝试自动创建子映射方法。

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Product {

private String productId;

private ProductDetail productDetail;

}

@Data

@AllArgsConstructor

@NoArgsConstructor

public class ProductDetail {

private String id;

}

@Data

@AllArgsConstructor

@NoArgsConstructor

public class ProductDTO implements Serializable {

private static final long serialVersionUID = 2184784038009791692L;

private String productId;

private ProductDetailDTO productDetail;

}

@Data

@AllArgsConstructor

@NoArgsConstructor

public class ProductDetailDTO {

private String detailId;

}

@Mapper(componentModel = "spring")

public interface Demo6Assembler {

ProductDTO toDTO(Product product);

@Mapping(target = "detailId", source = "id")

ProductDetailDTO toDetailDTO(ProductDetail detail);

}

@Component

public class Demo6AssemblerImpl implements Demo6Assembler {

@Override

public ProductDTO toDTO(Product product) {

if ( product == null ) {

return null;

}

ProductDTO productDTO = new ProductDTO();

productDTO.setProductId( product.getProductId() );

productDTO.setProductDetail( toDetailDTO( product.getProductDetail() ) ); //查找使用存在的转换方法

return productDTO;

}

@Override

public ProductDetailDTO toDetailDTO(ProductDetail detail) {

if ( detail == null ) {

return null;

}

ProductDetailDTO productDetailDTO = new ProductDetailDTO();

productDetailDTO.setDetailId( detail.getId() );

return productDetailDTO;

}

}

(3)嵌套bean的转换

可以使用mapping声明嵌套bean转换的规则,mapstruct生成子映射方法时,会使用者声明的规则。同时支持跨层级的属性转换。

@Data

@AllArgsConstructor

@NoArgsConstructor

public classProduct {

privateString productId;

privateProductDetail productDetail;

}

@Data

@AllArgsConstructor

@NoArgsConstructor

public classProductDetail {

privateString id;

}

@Data

@AllArgsConstructor

@NoArgsConstructor

public class ProductDTO implementsSerializable {

private static final long serialVersionUID = 2184784038009791692L;

privateString productId;

privateProductDetailDTO productDetail;

}

@Data

@AllArgsConstructor

@NoArgsConstructor

public classProductDetailDTO {

privateString productId;

privateString detailId;

}

@Mapper(componentModel = "spring")

public interfaceDemo7Assembler {

@Mapping(target = "productDetail.detailId", source = "productDetail.id") //声明productDetail下的属性转换规则

@Mapping(target = "productDetail.productId", source = "productId") //跨层级的属性转换,把product层级的productId放到productDetail层级

ProductDTO toDTO(Product product);

}

@Component

public class Demo7AssemblerImpl implementsDemo7Assembler {

@Override

publicProductDTO toDTO(Product product) {

if ( product == null) {

return null;

}

ProductDTO productDTO = newProductDTO();

if ( product.getProductDetail() != null) {

if ( productDTO.getProductDetail() == null) {

productDTO.setProductDetail( newProductDetailDTO() );

}

productDetailToProductDetailDTO( product.getProductDetail(), productDTO.getProductDetail() );

}

if ( productDTO.getProductDetail() == null) {

productDTO.setProductDetail( newProductDetailDTO() );

}

productToProductDetailDTO( product, productDTO.getProductDetail() );

productDTO.setProductId( product.getProductId() );

returnproductDTO;

}

//detail的转换方法

protected voidproductDetailToProductDetailDTO(ProductDetail productDetail, ProductDetailDTO mappingTarget) {

if ( productDetail == null) {

return;

}

mappingTarget.setDetailId( productDetail.getId() );

}

//product转成detail(更新处理)

protected voidproductToProductDetailDTO(Product product, ProductDetailDTO mappingTarget) {

if ( product == null) {

return;

}

mappingTarget.setProductId( product.getProductId() );

}

}

3.3.4 自定义映射器

MapStatuct支持自定义映射器,实现自定义类型之间的转换。

一个自定义映射器可以定义多个映射方法,匹配时,是以方法的入参和出参进行匹配的。如果绑定的映射中,存在多个相同的入参和出参方法,将会报错。

如果多个入参或者出参方法存在继承关系,将会匹配最具体的那一个方法。

@Data

@AllArgsConstructor

@NoArgsConstructor

public classProduct {

privateString productId;

private Listimages;

}

@Data

@AllArgsConstructor

@NoArgsConstructor

public class ProductDTO implementsSerializable {

private static final long serialVersionUID = 2184784038009791692L;

privateString productId;

privateString images;

}

@Mapper(componentModel = "spring", uses = ImageFormater.class)

public interfaceDemo8Assembler {

ProductDTO toDTO(Product product);

}

@Component

public class Demo8AssemblerImpl implementsDemo8Assembler {

@Autowired

privateImageFormater imageFormater;

@Override

publicProductDTO toDTO(Product product) {

if ( product == null) {

return null;

}

ProductDTO productDTO = newProductDTO();

productDTO.setProductId( product.getProductId() );

//调用自定义的映射器进行映射,把list转成string

productDTO.setImages( imageFormater.format( product.getImages() ) );

returnproductDTO;

}

}

3.3.5 使用限定符限定使用映射方法

自定义映射器时,存在多个相同入参和出参的方法,报错是因为MapStruct无法选择使用哪个映射方法。但有时确实有这样的场景,这时可以使用限定符绑定每个属性转换时使用的映射方法。

(1)限定符使用自定义注解实现。

声明限定符:

import org.mapstruct.Qualifier;

//映射器上的限定符

@Qualifier //标记为限定符

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.CLASS)

public @interface Formators {

}

//映射方法上的限定符

@Qualifier //标记为限定符

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.CLASS)

public @interface FormatImages {

}

//映射方法上的限定符

@Qualifier //标记为限定符

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.CLASS)

public @interface FormatDetails {

}

绑定限定符到映射器的方法上面:

@Component

@Formators //绑定限定符

public class CusFormater {

@FormatImages //绑定限定符

public String formatImages(List images) {

return String.join(",", images);

}

@FormatDetails //绑定限定符

public String formatDetails(List images) {

return String.join(",", images);

}

}

映射时,绑定限定符,定位映射方法:

@Mapper(componentModel = "spring", uses = CusFormater.class)

public interfaceDemo9Assembler {

@Mapping(target = "images", qualifiedBy = FormatImages.class) //转换指定限定符,定位具体的映射方法

@Mapping(target = "details", qualifiedBy = FormatDetails.class)//转换指定限定符,定位具体的映射方法

ProductDTO toDTO(Product product);

}

@Component

public class Demo9AssemblerImpl implementsDemo9Assembler {

@Autowired

privateCusFormater cusFormater;

@Override

publicProductDTO toDTO(Product product) {

if ( product == null) {

return null;

}

ProductDTO productDTO = newProductDTO();

productDTO.setProductId( product.getProductId() );

productDTO.setImages( cusFormater.formatImages( product.getImages() ) ); //定位方法

productDTO.setDetails( cusFormater.formatDetails( product.getDetails() ) );

returnproductDTO;

}

}

(2)基于named注解实现(推荐)

除了使用自定义住注解的方法,还可以使用@Named注解实现限定符的绑定。

@Component

@Named("CusFormater")

public classCusFormater {

//绑定限定符

@Named("formatImages")

public String formatImages(Listimages) {

return String.join(",", images);

}

//绑定限定符

@Named("formatDetails")

public String formatDetails(Listimages) {

return String.join(",", images);

}

}

使用时绑定:

@Mapper(componentModel = "spring", uses = CusFormater.class)

public interfaceDemo10Assembler {

@Mapping(target = "images", qualifiedByName = "formatImages") //转换指定限定符,定位具体的映射方法

@Mapping(target = "details", qualifiedByName = "formatDetails")//转换指定限定符,定位具体的映射方法

ProductDTO toDTO(Product product);

}

3.4 Map的映射

可以使用@MapMapping实现对key和value的分别映射:

@Mapper(componentModel = "spring")

public interfaceDemo11Assembler {

@MapMapping(valueDateFormat = "yyyy-MM-dd HH:mm:ss")

Map toDTO(Mapmap);

}

3.5 枚举值之间的转换

MapStruct可以在多个枚举值之间转换,使用@ValueMapping注解。

public enumE1 {

E1_1,

E1_2,

E1_3,

;

}

public enumE2 {

E2_1,

E2_2,

E2_3,

;

}

@Mapper(componentModel = "spring")

public interfaceDemo11Assembler {

@ValueMapping(target = "E1_1", source = "E2_1")

@ValueMapping(target = "E1_2", source = "E2_2")

@ValueMapping(target = MappingConstants.NULL, source = "E2_3") //转换成null

E1 toDTO(E2 e2);

}

生成代码:

@Component

public class Demo11AssemblerImpl implementsDemo11Assembler {

@Override

publicE1 toDTO(E2 e2) {

if ( e2 == null) {

return null;

}

E1 e1;

switch( e2 ) {

case E2_1: e1 =E1.E1_1;

break;

case E2_2: e1 =E1.E1_2;

break;

case E2_3: e1 = null;

break;

default: throw new IllegalArgumentException( "Unexpected enum constant: " +e2 );

}

returne1;

}

}

3.6 定制Bean生成或者更新Bean

使用MapStruct可以使用对象工厂来创建bean,同时也可以更新bean。

3.6.1 对象工厂

定义对象工厂:

public classDTOFactory {

publicProductDTO createDTO() {

ProductDTO productDTO = newProductDTO();

productDTO.setStock(0);

returnproductDTO;

}

}

使用对象工厂:

@Mapper(componentModel = "spring", uses = DTOFactory.class) //指定使用的对象工厂

public interfaceDemo13Assembler {

ProductDTO toDTO(Product product);

}

生成代码:

@Component

public class Demo13AssemblerImpl implementsDemo13Assembler {

@Autowired

privateDTOFactory dTOFactory;

@Override

publicProductDTO toDTO(Product product) {

if ( product == null) {

return null;

}

ProductDTO productDTO =dTOFactory.createDTO(); //使用对象工厂创建对象

productDTO.setProductId( product.getProductId() );

returnproductDTO;

}

}

3.6.2 更新对象

某些场景下,我们只是需要对对象进行更新,可以把需要更新的对象作为方法参数传入,并且使用@MappingTarget指定。

@Mapper(componentModel = "spring")

public interfaceDemo14Assembler {

voidupdateDTO(Product product, @MappingTarget ProductDTO productDTO);

}

@Component

public class Demo14AssemblerImpl implementsDemo14Assembler {

@Override

public voidupdateDTO(Product product, ProductDTO productDTO) {

if ( product == null) {

return;

}

productDTO.setProductId( product.getProductId() );

}

}

3.7 使用表达式映射值

对于复杂的映射,允许使用java表达式实现字段的映射。

注意要导入使用到的类。

@Mapper(componentModel = "spring", imports = DecimalUtils.class) //导入java表达式使用的类

public interfaceDemo16Assembler {

@Mapping(target = "price", expression = "java(product.getPrice1() + product.getPrice2())") //直接相加

@Mapping(target = "price2", expression = "java(DecimalUtils.add(product.getPrice1(), product.getPrice2()))") //使用工具类处理

ProductDTO toDTO(Product product);

}

@Component

public class Demo16AssemblerImpl implementsDemo16Assembler {

@Override

publicProductDTO toDTO(Product product) {

if ( product == null) {

return null;

}

ProductDTO productDTO = newProductDTO();

productDTO.setProductId( product.getProductId() );

productDTO.setPrice( product.getPrice1() +product.getPrice2() );

productDTO.setPrice2( DecimalUtils.add(product.getPrice1(), product.getPrice2()) );

returnproductDTO;

}

}

3.8 缺省值和常量

MapStruct允许设置缺省值和常量,同时缺省值允许使用表达式。

注意:使用缺省值,源字段必须存在,否则缺省值不生效,否则应该使用常量。

@Mapper(componentModel = "spring", imports = UUID.class)

public interface Demo15Assembler {

@Mapping(target = "productId", source = "productId", defaultValue = "0") //当product的productId为null,设置为0

@Mapping(target = "random", source = "random", defaultExpression = "java(UUID.randomUUID().toString())") //缺省设置随机数

@Mapping(target = "stock", constant = "0") //固定设置为0

@Mapping(target = "createTime", dateFormat = "yyyy-MM-dd", constant = "2020-05-30") //固定格式化设置为2020-05-30,

ProductDTO toDTO(Product product);

}

@Component

public class Demo15AssemblerImpl implements Demo15Assembler {

@Override

public ProductDTO toDTO(Product product) {

if ( product == null ) {

return null;

}

ProductDTO productDTO = new ProductDTO();

if ( product.getProductId() != null ) {

productDTO.setRandom( product.getProductId() );

}

else {

productDTO.setRandom( UUID.randomUUID().toString() );

}

if ( product.getProductId() != null ) {

productDTO.setProductId( product.getProductId() );

}

else {

productDTO.setProductId( "0" );

}

productDTO.setStock( 0 );

try {

productDTO.setCreateTime( new SimpleDateFormat( "yyyy-MM-dd" ).parse( "2020-05-30" ) );

}

catch ( ParseException e ) {

throw new RuntimeException( e );

}

return productDTO;

}

}

3.9 存在继承关系的结果处理

当返回的结果类型存在继承关系时,可以使用 @BeanMapping注解指定真实返回的结果类型。

@Mapper(componentModel = "spring")

public interfaceDemo17Assembler {

@BeanMapping(resultType = DogDTO.class) //指定返回的结果类型

Animal toDTO(Dog dog);

}

@Component

public class Demo17AssemblerImpl implementsDemo17Assembler {

@Override

publicAnimal toDTO(Dog dog) {

if ( dog == null) {

return null;

}

DogDTO animal = newDogDTO();

animal.setId( dog.getId() );

returnanimal;

}

}

3.10 映射关系继承

MapStruct允许对映射关系进行继承,使用@InheritConfiguration标记当前方法继承其他映射方法的映射关系。会自动查找相同类型映射源、映射目标的方法进行继承,如果存在多个相同类型的方法,则需要手工指定。

@Mapper(componentModel = "spring")

public interface Demo18Assembler {

@Mapping(target = "productId", source = "id")

@Mapping(target = "detail", source = "detail1")

ProductDTO toDTO(Product product);

@Mapping(target = "productId", source = "id2")

@Mapping(target = "detail", source = "detail2")

ProductDTO toDTO2(Product product);

@InheritConfiguration(name = "toDTO") //对toDTO的映射关系进行继承

@Mapping(target = "detail", source = "detail2") //对继承的关系进行重写

void update(@MappingTarget ProductDTO productDTO, Product product);

}

除了正向继承规则外,还可以进行规则逆向继承,从被继承方法的目标对象映射到源对象。

@Mapper(componentModel = "spring")

public interface Demo18Assembler {

@Mapping(target = "productId", source = "id")

@Mapping(target = "detail", source = "detail1")

ProductDTO toDTO(Product product);

@Mapping(target = "productId", source = "id2")

@Mapping(target = "detail", source = "detail2")

ProductDTO toDTO2(Product product);

@InheritInverseConfiguration(name = "toDTO") //对toDTO的映射关系进行逆继承

@Mapping(target = "detail2", source = "detail") //对逆向继承的关系进行重写

Product toEntity(ProductDTO dto);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值