真香系列,MapStruct 优雅转换bean对象

背景

大型项目采用分层开发,每层的数据模型都不同:在持久化层,模型层为 PO(Persistent Object)、在开放服务层,模型为数据传输对象 DTO(Data Transfer Object)。

如果开放服务直接将 PO (持久化模型对象)对外暴露,叫开放领域模型风格。
如果开放服务只能将 DTO(数据传输对象)对外暴露,叫封闭领域模型风格。

这样各层数据模型交互时不可避免需要做映射处理,常见的转换方式有:

  • 调用getter/setter方法进行属性赋值
  • 调用BeanUtil.copyPropertie进行反射属性赋值

第一种方式不必说,属性多了就需要写一大坨getter/setter代码;

第二种方式在简单场景可以使用,但它有局限性:

  • 首先是不适合对性能有严苛要求的情况,因为 BeanUtils.copyProperties 是基于 Java 反射实现的;
  • 其次不适合复杂映射场景:
  1. 比如性别在后台是通过 0 和 1,但是需要返回前端 男 或者 女,如何映射?
  2. 比如 PO <=> DTO 属性名不同,如何映射?
  3. 比如 多个 PO => DTO 如何映射?
  4. 再比如 属性名和属性类型都不同,又如何映射?

下面介绍的 MapStruct 就是专门应对为这种场景的。

 

MapStruct是什么

MapStruct就是一个属性映射工具。

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

和动态映射框架相比,MapStruct具有以下优点:

  • 通过使用普通方法调用而不是反射来快速执行
  • 编译时类型安全性
  • 构建是清除错误报告
    • 映射不完整(并非所有target属性都被映射)
    • 映射不正确(找不到正确的映射方法或类型转换)

话不多说,我们就来看下MapStruct 到底是怎么玩的~

 

MapStruct最佳实践

配置

<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>

基本用法

准备两个实体类,为了方便演示,用了lombok插件。准备两个实体类,一个是Car

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Car {
 
    private String make;
    private int numberOfSeats;
 
}

还有一个是CarDto

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CarDto {

    private String make;
    private int seatCount;
    private String name;
    private String description;

}

编写转换接口

@Mapper 
public interface CarMapper {
 
    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); 
 
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto carToCarDto(Car car); 
}

编写测试方法

public static void main(String[] args) {
    Car car = new Car( "测试", 10 );
    //转换对象
    CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);

    //测试
    System.out.println( carDto );
    System.out.println( carDto.getMake() );  // 测试
    System.out.println( carDto.getSeatCount() ); // 10
}

 

MapStruct原理说明

在 target/generated-sources/annotations 里可以看到,代码中可以看到其生成了一个实现类, 而代码也类似于我们手写。
这个在编译期生成的代码,性能上是比反射要快不少的。

MapStruct是利用编译期动态生成set/get代码的class文件 ,在运行时直接调用该class文件。 该方式实际上仍会存在set/get代码,只是不需要自己手写。

 

MapStruct 中注解的关键词

 

@Mapper 只有在接口加上这个注解, MapStruct 才会去实现该接口
    @Mapper 里有个 componentModel 属性,主要是指定实现类的类型,有如下4种方式
    default: 这是默认的情况,MapStruct不使用任何组件类型, 可以通过Mappers.getMapper(Class)方式获取自动生成的实例对象。
    cdi: the generated mapper is an application-scoped CDI bean and can be retrieved via @Inject。
    spring: 生成的实现类上面会自动添加一个@Component注解,可以通过Spring的 @Autowired方式进行注入。
    jsr330: 生成的实现类上会添加@javax.inject.Named 和@Singleton注解,可以通过 @Inject注解获取。

@Mapping:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性
    source:源属性
    target:目标属性
    dateFormat:String 到 Date 日期之间相互转换,通过 SimpleDateFormat,该值为 SimpleDateFormat 的日期格式
    numberFormat:数值格式化, 例:"0.00"
    expression: 自定义java代码实现属性映射
    ignore: 忽略这个字段
@Mappings:配置多个@Mapping
@MappingTarget 用于更新已有对象
@InheritConfiguration 用于继承配置

 

MapStruct 转换方式

属性名称相同,则进行转化

这种类似于BeanUtils转换的方式。

属性名不相同, 可通过 @Mapping 注解进行指定转化。

例 @Mapping(source = “numberOfSeats”, target = “seatCount”)

 

Mapper 中使用自定义的转换

对于某些类型, 无法通过代码生成器的形式来进行处理,就需要自定义的方法来进行转换。利用java8新特性,在接口中定义一个默认方法。

 default CarDto carToCarDto2(Car car) {
    if (car == null) {
        return null;
    }
    CarDto carDto = new CarDto();
    carDto.setMake("China:" + car.getMake());
    carDto.setSeatCount(car.getNumberOfSeats());
    return carDto;
}

 

多个对象装换成一个对象

新建一个Brand类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Brand {
    private String name;

    private String description;
}

添加转换方法

CarDto carToCarDto(Car car, Brand brand);

测试方法

 

Car car1 = new Car( "测试", 10 );
Brand brand = new Brand( "bwm", "宝马" );

CarDto toCarDto = CarMapper.INSTANCE.carToCarDto(car, brand);
System.out.println( toCarDto );
System.out.println( toCarDto.getMake() ); // 测试
System.out.println( toCarDto.getSeatCount() ); // 10
System.out.println( toCarDto.getName() );  //bwm
System.out.println( toCarDto.getDescription() );  // 宝马

 

注意:

  • 当多个对象中, 有其中一个为 null, 则会直接返回 null
  • 如一对一转换一样, 属性通过名字来自动匹配。 因此, 名称和类型相同的不需要进行特殊处理
  • 当多个原对象中,有相同名字的属性时,需要通过 @Mapping 注解来具体的指定, 以免出现歧义

 

类型不一致

在Car加一个属性为Date类型的createTime,而在CarDto加一个属性为String类型的。接口就可以这么写

@Mapper
public interface CarCovertBasic {
    CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class);
    @Mappings({
        @Mapping(source = "carName", target = "name"),
        @Mapping(target = "createTime", expression = "java(com.guduyan.util.DateUtil.dateToStr(source.getCreateTime()))")
     })
    CarDto toConvertDto(Car source);

    List<CarDto> toConvertDtos(List<Car> source);
}

 

集合类型转换

如果我们要从List转换为List怎么办呢?简单,接口里加一个方法就行

@Mapper
public interface CarCovertBasic {
    CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class);

    @Mapping(source = "carName", target = "name")
    CarDto toConvertDtos(Carsource);

    List<CarDto> toConvertDtos(List<Car> source);
}

 

其他

关于MapStruct还有其他很多的高级功能,我就不一一介绍了。大家可以参考下MapStruct官方文档

 

MapStruct 性能分析

对于性能来说MapStruct的性能在映射框架中同样也属于佼佼者的地位。
 

QqLVFU.jpg

 

总结

文章中介绍了MapStruct中个人觉得实用的功能,此外还有很多高级特性在文章中没有进行描述,感兴趣可以去阅读MapStruct官方文档了解更多。
相对于我们目前对象转换的方式来说,MapStruct有着和Lombok一样提高工作效率的能力。可以使我们从繁琐的transfer中脱离出来,专心于业务代码的实现。

 

 

在 Java 开发中,经常会遇到需要将一个类的属性转换为另一个类的属性的情况。这通常需要写很多重复的代码。为了减少这些重复的代码,可以使用 MapStruct 这个工具来处理恼人的 bean 转换MapStruct 是一个基于注解的 Java Bean 映射工具,它可以根据注解自动生成 Bean 映射代码。使用 MapStruct 可以让 bean 转换变得更加简单和高效。 接下来,我将介绍如何使用 MapStruct 处理 bean 转换。 1. 添加依赖 首先,需要在项目中添加 MapStruct 的依赖。可以在 pom.xml 文件中添加以下依赖: ```xml <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.4.2.Final</version> </dependency> ``` 2. 创建转换接口 创建一个转换接口,用于定义源类和目标类之间的映射关系。例如,如果要将 User 类转换为 UserDto 类,可以创建以下接口: ```java @Mapper public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); UserDto toUserDto(User user); } ``` 在接口上添加 @Mapper 注解,表示这是一个 MapStruct 映射接口。在接口中定义一个 toUserDto 方法,用于将 User 类转换为 UserDto 类。 3. 编写转换代码 在 toUserDto 方法中,使用 MapStruct 提供的映射注解来指定属性的映射关系。例如,以下代码将 User 类的 name 属性映射到 UserDto 类的 username 属性: ```java @Mapping(source = "name", target = "username") UserDto toUserDto(User user); ``` 4. 执行转换 创建一个 User 对象,并将其转换为 UserDto 对象: ```java User user = new User("Tom", 18); UserDto userDto = UserMapper.INSTANCE.toUserDto(user); ``` 使用 MapStruct 处理 bean 转换非常简单。只需定义一个转换接口,并在其中使用映射注解指定属性的映射关系,就可以自动生成 bean 映射代码。这样可以大大减少编写重复代码的工作量,提高开发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏目 "

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值