一种框架的出现都要解决个痛点,我想下面这这种不方便的操做常常有人写吧。假如Car
类是数据库映射类:java
package cn.felord.mapstruct.entity;
import lombok.Data;
/**
* Car
*
* @author Felordcn
* @since 13:35 2019/10/12
**/
@Data
public class Car {
private String make;
private int numberOfSeats;
private CarType type;
}复制代码
CarType
类:spring
package cn.felord.mapstruct.entity;
import lombok.Data;
/**
* CarType
*
* @author Felordcn
* @since 13:36 2019/10/12
**/
@Data
public class CarType {
private String type;
}复制代码
CarDTO
是DTO类:数据库
package cn.felord.mapstruct.entity;
import lombok.Data;
/**
* CarDTO
*
* @author Felordcn
* @since 13:37 2019/10/12
**/
@Data
public class CarDTO {
private String make;
private int seatCount;
private String type;
} 复制代码
咱们从数据库查询Car
而后须要转换为CarDTO
,一般咱们会这么写一个方法进行转换:express
public CarDTO carToCarDTO(Car car) {
CarDTO carDTO = new CarDTO();
carDTO.setMake(car.getMake());
carDTO.setSeatCount(car.getNumberOfSeats());
carDTO.setType(car.getCarType().getType());
// 有可能更长
return carDTO;
} 复制代码
这种写法很是繁琐无味,并且没有技术含量。甚至中间还牵涉了不少类型转换,嵌套之类的繁琐操做,而咱们想要的只是创建它们之间的映射关系而已。有没有一种通用的映射工具来帮咱们搞定这一切。固然有并且还很多。有人说apache的BeanUtil.copyProperties
能够实现,可是性能差并且容易出异常,不少规范严禁使用这种途径。如下是对几种对象映射框架的对比,大多数状况下 MapStruct
性能最高。原理相似于lombok
,MapStruct
都是在编译期进行实现,并且基于Getter
、Setter
,没有使用反射因此通常不存在运行时性能问题。apache
今天就搞一搞MapStruct, 并跟Spring Boot 2.x 集成如下。 不管是idea 仍是eclipse 都建议安装 MapStruct Plugin 插件,固然不安装也是能够的。编程
在 Spring Boot 的 pom.xml
下引入 MapStruct
的 maven 依赖坐标:数组
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
<scope>compile</scope>
</dependency>
<!-- other dependencies -->
</dependencies>复制代码
咱们把开始的痛点解决一下。看看 MapStruct 如何下降你的编程成本。安全
编写Car
到CarDTO
的映射:markdown
package cn.felord.mapstruct.mapping;
import cn.felord.mapstruct.entity.Car;
import cn.felord.mapstruct.entity.CarDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* CarMapping
*
* @author Felordcn
* @since 14 :02 2019/10/12
*/
@Mapper
public interface CarMapping {
/**
* 用来调用实例 实际开发中可以使用注入Spring 不写
*/
CarMapping CAR_MAPPING = Mappers.getMapper(CarMapping.class);
/**
* 源类型 目标类型 成员变量相同类型 相同变量名 不用写{@link org.mapstruct.Mapping}来映射
*
* @param car the car
* @return the car dto
*/
@Mapping(target = "type", source = "carType.type")
@Mapping(target = "seatCount", source = "numberOfSeats")
CarDTO carToCarDTO(Car car);
}
复制代码
上面短短几行代码就能够了十分简单!解释一下操做步骤:mybatis
首先声明一个映射接口用@org.mapstruct.Mapper
(不要跟mybatis注解混淆)标记,说明这是一个实体类型转换接口。这里咱们声明了一个 CAR_MAPPING
来方便咱们调用,CarDTO toCarDTO(Car car)
是否是很熟悉, 像mybatis
同样抽象出咱们的转换方法。@org.mapstruct.Mapping
注解用来声明成员属性的映射。该注解有两个重要的属性:
source
表明转换的源。这里就是Car
。 target
表明转换的目标。这里是CarDTO
。 这里以成员变量的参数名为依据,若是有嵌套好比 Car
里面有个 CarType
类型的成员变量 carType
,其 type
属性 来映射 CarDTO
中的 type
字符串,咱们使用 type.type
来获取属性值。若是有多层以此类推。MapStruct
最终调用的是 setter
和 getter
方法,而非反射。这也是其性能比较好的缘由之一。numberOfSeats
映射到 seatCount
就比较好理解了。咱们是否是忘记了一个属性 make
,由于他们的位置且名称彻底一致,因此能够省略。并且对于包装类是自动拆箱封箱操做的,而且是线程安全的。MapStruct不仅仅有这些功能,还有其余一些复杂的功能:
设置转换默认值和常量。当目标值是 null
时咱们能够设置其默认值,注意这些都是基本类型以及对应都 boxing
类型,以下
@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")复制代码
须要注意的是常量不能对源进行引用(不能指定 source
属性),下面是正确的操做:
@Mapping(target = "stringConstant", constant = "Constant Value")复制代码
当你的应用编译后。你会在编译后的目录好比 maven是 targetgenerated-sourcesannotations
下的子目录发现生成了一个实现类 好比 咱们上面的CarMapping
会生成CarMappingImpl
以下:
package cn.felord.mapstruct.mapping;
import cn.felord.mapstruct.entity.Car;
import cn.felord.mapstruct.entity.CarDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import javax.annotation.Generated;
import org.springframework.stereotype.Component;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2019-10-12T15:05:36+0800",
comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_222 (Amazon.com Inc.)"
)
@Component
public class CarMappingImpl implements CarMapping {
@Override
public CarDTO carToCarDTO(Car car) {
if ( car == null ) {
return null;
}
CarDTO carDTO = new CarDTO();
carDTO.setType( carCarTypeType( car ) );
carDTO.setSeatCount( car.getNumberOfSeats() );
carDTO.setMake( car.getMake() );
return carDTO;
}
private String carCarTypeType(Car car) {
if ( car == null ) {
return null;
}
CarType carType = car.getCarType();
if ( carType == null ) {
return null;
}
String type = carType.getType();
if ( type == null ) {
return null;
}
return type;
}
}复制代码
下面介绍几种 MapStruct 的进阶操做:
格式化也是咱们常用的操做,好比数字格式化,日期格式化。这是处理数字格式化的操做,遵循java.text.DecimalFormat
的规范:
@Mapping(source = "price", numberFormat = "$#.00")复制代码
下面展现了将一个日期集合映射到日期字符串集合的格式化操做上:
@IterableMapping(dateFormat = "dd.MM.yyyy")
List<String> stringListToDateList(List<Date> dates);复制代码
下面演示如何使用LocalDateTime
做为当前的时间值注入 addTime
属性中。
首先在@org.mapstruct.Mapper
的 imports
属性中导入 LocalDateTime
,该属性是数组意味着你能够根据须要导入更多的处理类:
@Mapper(imports = {LocalDateTime.class})复制代码
接下来只须要在对应的方法上添加注解@org.mapstruct.Mapping
,其属性expression
接收一个 java()
包括的表达式:
@Mapping(target = "addTime", expression = "java(LocalDateTime.now())")复制代码
Car
的出厂日期字符串manufactureDateStr
注入到 CarDTO
的 LocalDateTime
类型属性addTime
中去: @Mapping(target = "addTime", expression = "java(LocalDateTime.parse(car.manufactureDateStr))")
CarDTO carToCarDTO(Car car);复制代码
若是使用要把Mapper 注入Spring IoC 容器咱们只须要这么声明,不用再构建一个单例,就能够像其余 spring bean同样对CarMapping 进行引用了:
package cn.felord.mapstruct.mapping;
import cn.felord.mapstruct.entity.Car;
import cn.felord.mapstruct.entity.CarDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* CarMapping 注入spring 写法
*
* @author Felordcn
* @since 14 :02 2019/10/12
*/
@Mapper(componentModel = "spring")
public interface CarMapping {
/**
* 用来调用实例 实际开发中可以使用注入Spring 不写
*/
// CarMapping CAR_MAPPING = Mappers.getMapper(CarMapping.class);
/**
* 源类型 目标类型 成员变量相同类型 相同变量名 不用写{@link Mapping}来映射
*
* @param car the car
* @return the car dto
*/
@Mapping(target = "type", source = "carType.type")
@Mapping(target = "seatCount", source = "numberOfSeats")
CarDTO carToCarDTO(Car car);
}
复制代码
其实MapStruct 还有不少的功能。可是从可读性来讲,我建议使用以上几种容易理解的功能便可。若是你感兴趣能够去mapstruct.org进一步学习。配合lombok和我介绍的jsr303,让你更加专一于业务,并且代码更加清晰。
关注公众号:Felordcn获取更多资讯