目录

前言

1. 设置

1.1 Maven

2. 定义一个映射器

2.1 基本映射

2.2 指定默认值

2.2 指定默认值

2.4 dateFormat()

2.5 组合映射

2.5.1 多个源对象

2.5.2 使用其他的值

2.6 嵌套映射

2.7 numberFormat()

2.8 逆映射

2.9 继承与共享配置

2.9.1 继承配置

2.9.2 共享配置

3. 使用自定义方法

3.1 自定义类型转换方法

3.2 使用@Qualifier

3.3 使用@namd

4.集合映射

5.集成到 spring

6 高级运用

6.1 spi的运用

6.2 freemarker生成代码


Mapstruct 版本1.5.0.Beta1
官方文档
案例-github

前言

MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类。

您要做的就是定义一个映射器接口,该接口声明任何必需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即没有反射或类似内容。

与手动编写映射代码相比,MapStruct通过生成繁琐且易于出错的代码来节省时间。遵循配置方法上的约定,MapStruct使用合理的默认值,但在配置或实现特殊行为时不加理会。
与动态映射框架相比,MapStruct具有以下优点:

  1. 通过使用普通方法调用(settter/getter)而不是反射来快速执行
  2. 编译时类型安全性:只能映射相互映射的对象和属性,不能将order实体意外映射到customer DTO等。
  3. 如果有如下问题,编译时会抛出异常
    3.1 映射不完整(并非所有目标属性都被映射)
    3.2 映射不正确(找不到正确的映射方法或类型转换)
  4. 可以通过freemarker定制化开发

1. 设置

MapStruct是基于JSR 269的Java注释处理器,因此可以在命令行构建(javac,Ant,Maven等)以及您的IDE中使用。

它包含以下工件:

1.org.mapstruct:mapstruct:包含必需的注释,例如@Mapping
2.org.mapstruct:mapstruct-processor:包含注释处理器,该注释处理器生成映射器实现

1.1 Maven

对于基于Maven的项目,将以下内容添加到您的POM文件中以使用MapStruct:

<!--mapStruct依赖 高性能对象映射-->
            <!--mapstruct核心-->
            <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct</artifactId>
                <version>1.5.0.Beta1</version>
            </dependency>
            <!--mapstruct编译-->
            <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.5.0.Beta1</version>
            </dependency>

 Lombok依赖:(版本最好在1.16.16以上,否则会出现问题)通常是和lombok一起使用的

<dependency>
            <groupid>org.projectlombok</groupid>
            <artifactid>lombok</artifactid>
            <version>${lombok.version}</version>
          	// 版本号 1.18.12
        </dependency>

下载插件(不是必须的,但是挺好用)
idea中下载 mapstruct support 插件,安装重启Idea:

在参数上,按 ctrl + 鼠标左键 ,能够自动进入参数所在类文件

 

 

2. 定义一个映射器

2.1 基本映射

要创建映射器,只需使用所需的映射方法定义一个Java接口,并用注释对其进行org.mapstruct.Mapper注释:
该@Mapper注释将使得MapStruct代码生成器创建的执行PersonMapper 过程中生成时的界面。

在生成的方法实现中,源类型(例如Person)的所有可读属性都将被复制到目标类型(例如PersonDTO)的相应属性中:

  1. 当一个属性与其目标实体对应的名称相同时,它将被隐式映射。

  2. 当属性在目标实体中具有不同的名称时,可以通过@Mapping注释指定其名称。

如果不指定@Mapping,默认映射name相同的field
如果映射的对象field name不一样,通过 @Mapping 指定。
忽略字段加@Mapping#ignore() = true

@Data
public class Person {

    String describe;

    private String id;

    private String name;

    private int age;

    private BigDecimal source;

    private double height;

    private Date createTime;
	
}

@Data
public class PersonDTO {

    String describe;

    private Long id;

    private String personName;

    private String age;

    private String source;

    private String height;
    
}
// mapper
@Mapper
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
	
	@Mapping(target = "name", source = "personName")
	@Mapping(target = "id", ignore = true) // 忽略id,不进行映射
    PersonDTO conver(Person person);

}

生成的实现类:

 public class PersonMapperImpl implements PersonMapper {
    public PersonMapperImpl() {
    }

    public PersonDTO conver(Person person) {
        if (person == null) {
            return null;
        } else {
            PersonDTO personDTO = new PersonDTO();
            personDTO.setDescribe(person.getDescribe());
            if (person.getId() != null) {
                personDTO.setId(Long.parseLong(person.getId()));
            }

            personDTO.setPersonName(person.getName());
            personDTO.setAge(String.valueOf(person.getAge()));
            if (person.getSource() != null) {
                personDTO.setSource(person.getSource().toString());
            }

            personDTO.setHeight(String.valueOf(person.getHeight()));
            return personDTO;
        }
    }
}

测试:

@Test
public void test(){
     Person person = new Person();
     person.setDescribe("测试");
     person.setAge(18);
     person.setName("张三");
     person.setHeight(170.5);
     person.setSource(new BigDecimal("100"));

     PersonDTO dto = PersonMapper.INSTANCT.conver(person);

     System.out.println(dto);
    // PersonDTO(describe=测试, id=null, personName=张三, age=18, source=100, height=170.5)
}

 

2.2 指定默认值

在@Mapper接口类里面的转换方法上添加@Mapping注解
target() 必须添加,source()可以不添加,则直接使用defaultValue

@Mapping(target = "describe", source = "describe", defaultValue = "默认值")
PersonDTO conver(Person person);

 

2.2 指定默认值

在@Mapper接口类里面的转换方法上添加@Mapping注解
target() 必须添加,source()可以不添加,则直接使用defaultValue

@Mapping(target = "describe", source = "describe", defaultValue = "默认值")
PersonDTO conver(Person person);

2.4 dateFormat()

如果属性从字符串映射到日期,则该格式字符串可由SimpleDateFormat处理,反之亦然。当映射枚举常量时,将忽略所有其他属性类型。

....
@Mapping(target = "createTime" ,source = "createTime", dateFormat = "yyyy-MM-dd")
PersonDTO conver(Person person);
...

impl:

try {
	if (person.getCreateTime() != null) {
    	personDTO.setCreateTime((new SimpleDateFormat("yyyy-MM-dd")).parse(person.getCreateTime()));
	}
} catch (ParseException var4) {
    throw new RuntimeException(var4);
}

2.5 组合映射

2.5.1 多个源对象

@Data
public class BasicEntity {

    private Date createTime;

    private String createBy;

    private Date updateTime;

    private String updateBy;

    private int _ROW;

}

@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "personName",source = "name")
    PersonDTO conver(Person person);

    @Mapping(target = "createTime",source = "basicEntity.createTime")
    PersonDTO combinationConver(Person personC, BasicEntity basicEntity);

}

2.5.2 使用其他的值

...
@Mapping(target = "id", source = "id")
PersonDTO mapTo(Person person, String id);
...

虽然Person和Person有相同的id字段,但是映射器会使用mapTo方法里面的id参数。

2.6 嵌套映射

@Data
public class Person {
	...
	private Child personChild;
	...
}
@Data
public class PersonDTO {
	...
    private Child child;
    ...
}
// mapper
@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "child", source = "personChild")
    PersonDTO conver(Person person);

}

如果field name一样则不需要指定@Mapping

2.7 numberFormat()

如果带注释的方法从数字映射到字符串,则使用DecimalFormat将格式字符串作为可处理的格式。反之亦然。对于所有其他元素类型,将被忽略。
从基本2.1 基本映射可以看出,number类型与字符串直接的转换是通过valueOf(),如果字符串格式不正确会抛出java.lang.NumberFormatException异常,例如:Integer.valueOf(“10.2”)

使用numberFormat()之后DecimalFormat格式转换,还是会抛出NFE异常

// mapper
....
@Mapping(target = "age",source = "age", numberFormat = "#0.00")
PersonDTO conver(Person person);
...
// imppl
personDTO.setAge((new DecimalFormat("#0.00")).format((long)person.getAge()));

2.8 逆映射

在双向映射的情况下,例如从实体到DTO以及从DTO到实体,前向方法和反向方法的映射规则通常是相似的,并且可以通过切换source和来简单地反转target。

使用注释@InheritInverseConfiguration表示方法应继承相应反向方法的反向配置

....
@Mapping(target = "age",source = "age", numberFormat = "#0.00")
PersonDTO conver(Person person);

@InheritInverseConfiguration
Person conver(PersonDTO dto);
...

2.9 继承与共享配置

2.9.1 继承配置

方法级配置注解,例如@Mapping,@BeanMapping,@IterableMapping,等等,都可以继承从一个映射方法的类似使用注释方法@InheritConfiguration

@Mapper 
public interface CarMapper {     
    @Mapping(target = "numberOfSeats", source = "seatCount")    
    Car carDtoToCar(CarDto car);     
    
    @InheritConfiguration    
    void carDtoIntoCar(CarDto carDto, @MappingTarget Car car); 
}

上面的示例声明了一种carDtoToCar()具有配置的映射方法,该配置定义了应如何映射numberOfSeats类型中的属性Car。在现有Instance实例上执行映射的update方法Car需要相同的配置才能成功映射所有属性。通过声明@InheritConfiguration该方法,MapStruct可以搜索继承候选,以应用继承自该方法的注释。

如果所有类型的A(源类型和结果类型)都可以分配给B的相应类型,则一个方法A可以从另一种方法B继承配置。
如果可以使用多个方法作为继承的源,则必须在注释中指定方法名称:@InheritConfiguration( name = “carDtoToCar” )。

一种方法,可以使用==@InheritConfiguration==和覆盖或通过另外施加修改的配置@Mapping,@BeanMapping等等。

2.9.2 共享配置

MapStruct提供了通过指向带注释的中央接口来定义共享配置的可能性@MapperConfig。为了使映射器使用共享配置,需要在@Mapper#config属性中定义配置接口。

@MapperConfig注释具有相同的属性@Mapper注释。任何未通过via指定的属性@Mapper都将从共享配置中继承。中指定@Mapper的属性优先于通过引用的配置类指定的属性。列表属性例如uses可以简单组合:

@MapperConfig(unmappedTargetPolicy = ReportingPolicy.ERROR ) 
public interface CentralConfig { 
}

@Mapper(config = CentralConfig.class } ) 
// Effective configuration: 
// @Mapper(uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class },
//     unmappedTargetPolicy = ReportingPolicy.ERROR // ) 
public interface SourceTargetMapper {  ... } 

共享配置config,配置一些检查策略
例如:

  1. unmappedSourcePolicy()、unmappedTargetPolicy() : 源或者目标没有标注映射的属性怎么报告
  2. typeConversionPolicy() :应该报告如何进行有损(缩小)转换,例如:long到integer的转换。
  3. collectionMappingStrategy(): 集合类型映射策略
    其他的,请阅读源码

 

3. 使用自定义方法

3.1 自定义类型转换方法

public class DateMapper {

    public String asString(Date date) {
        return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
            .format( date ) : null;
    }

    public Date asDate(String date) {
        try {
            return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
                .parse( date ) : null;
        }
        catch ( ParseException e ) {
            throw new RuntimeException( e );
        }
    }
}

mapper:

@Mapper(uses=DateMapper.class)
public interface PersonMapper{
  PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
  PersonDTO conver(Person person);
}

impl:

public class PersonMapperImpl implements PersonMapper {
    private final DateMapper dateMapper = new DateMapper();

    public PersonMapperImpl() {
    }

    public PersonDTO conver(Person person) {
      ....
      personDTO.setCreateTime(this.dateMapper.asDate(person.getCreateTime()));
	  ...
      return personDTO;
       
    }
}


在进行类型转换的时候直接调用改转换方法
@Mapper#uses可以使用多个类

3.2 使用@Qualifier

@Qualifier标记的自定义注解标记的方法,必须有输入, 否则编译时会抛出异常

public class DateFormtUtil {

    @DateFormat
    public static String dateToString(Date date){
        return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
    }

    @Qualifier
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface DateFormat{}
}


mapper:

@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "createTime",source = "createTime",qualifiedBy = DateFormat.class)
    PersonDTO conver(Person person);

}

3.3 使用@namd

public class DateFormtUtil {

    @Named("dateToString")
    public static String dateToString(Date date){
        return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
    }
}

mapper:

@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

   @Mapping(target = "createTime",source = "createTime",qualifiedByName = "dateToString")
    PersonDTO conver(Person person);
}

效果跟@Qualifier是一样的

4.集合映射

MapStructCollectionMappingStrategy,与可能的值:ACCESSOR_ONLY,SETTER_PREFERRED,ADDER_PREFERRED和TARGET_IMMUTABLE

在下表中,破折号-表示属性名称。接下来,尾部s表示复数形式。该表解释了这些选项以及它们是如何施加到存在/不存在的set-s,add-s和/或get-s在目标对象上的方法:

选项仅目标set-s可用仅目标add-可用既可以set-s/add-没有set-s/add-现有目标(@TargetType)
ACCESSOR_ONLYset-sget-sset-sget-sget-s
SETTER_PREFERREDset-sadd-set-sget-sget-s
ADDER_PREFERREDset-sadd-add-get-sget-s
TARGET_IMMUTABLEset-sexceptionset-sexceptionset-s

 

5.集成到 spring

@Mapper#componentModel 中指定依赖注入框架

@Mapper(componentModel = "spring")
public interface ModelMapper {

    ModelMapper INSTANT = Mappers.getMapper(ModelMapper.class);

    ModelVO conver(Model model);

}
// 直接在类中使用Autowired注入就行了
@RestController
class MapperSpringController {

    @Autowired
    ModelMapper modelMapper;

    @GetMapping("/get")
    ModelVO getModle(){
       Model model = new Model();
       model.setId("123456");
       model.setName("张三");
       model.setCreate(new Date());
       return modelMapper.conver(model);
    }
}

6 高级运用

6.1 spi的运用

官方文档 关于spi的运用描述

6.2 freemarker生成代码

github链接

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐