一、概述
MapStruct是一个用于生成类型安全、性能良好且无依赖关系的bean映射代码的注释处理器。
之前使用的是 BeanUtils.copyProperties() 方法进行实体类属性赋值,但是这个方法不灵活。对于不同字段名或不同属性值无法赋值,使用 MapStruct 可以自定义,更加灵活地进行bean映射。
官方文档:mapstruct.org/documentati…
二、依赖
添加依赖
<properties>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>
。。。
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
。。。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source> <!-- depending on your project -->
<target>1.8</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
三、使用
1、基础使用
创建实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Car {
private String make;
private int numberOfSeats;
private CarType type;
private Wheel wheel;
//constructor, getters, setters etc.
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CarDto {
private String make;
private int seatCount;
private String type;
private String wheelColor;
private String wheelPrice;
//constructor, getters, setters etc.
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Wheel {
private String price;
private String color;
}
public enum CarType {
AI001("ai", "车1"),
AC002("ac", "车2");
private String code;
private String desc;
CarType(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
创建映射接口
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
@Mapping(target = "seatCount", source = "numberOfSeats")
@Mapping(target = "wheelColor", source = "wheel.color")
@Mapping(target = "wheelPrice", source = "wheel.price")
CarDto carToCarDto(Car car);
}
创建测试类
public class TestSpring {
public static void main(String[] args) {
Wheel wheel = new Wheel("1000","red");
Car car = new Car("Morris" , 5 , CarType.AI001 , wheel);
//when
CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
System.out.println(carDto.toString());
}
}
输出为:CarDto(make=Morris, seatCount=5, type=AI001, wheelColor=red, wheelPrice=1000)
上面的例子可以用于大多数的情况,针对不同字段,通过@Mapping注解进行映射,source 就是转换的源,target 就是转换的目标。对于实体类内的对象也可以通过形如wheel.price这样的方式进行映射。
2、数字与字符串转换
在Car中添加属性
private Double price;
在CarDto中添加属性
private String price;
在CarMapper中添加方法
@Mapping(source = "price",numberFormat = "#0.00",target = "price")
CarDto carToCarDtoPrice(Car car);
@IterableMapping(numberFormat = "#0.00")
List<String> listIntegerFormat(List<Integer> prices);
进行测试:
public static void main(String[] args) {
Wheel wheel = new Wheel("1000","red");
Car car = new Car("Morris", 5, CarType.AI001,wheel,0.0);
//when
CarDto carDto = CarMapper.INSTANCE.carToCarDtoPrice(car);
System.out.println("原始的car");
System.out.println(car.toString());
System.out.println("转换后的carDto");
System.out.println(carDto.toString());
List<Integer> sourceList = new ArrayList<>();
IntStream.rangeClosed(0,10).forEach(i->sourceList.add(i));
List<String> stringList = CarMapper.INSTANCE.listIntegerFormat(sourceList);
System.out.println("原始的list");
System.out.println(sourceList);
System.out.println("转换后的list");
System.out.println(stringList);
}
打印结果如下,可以看到对应的数字已经转换成字符串了,并且保留两位小数。
原始的car
Car(make=Morris, numberOfSeats=5, type=AI001, wheel=Wheel(price=1000, color=red), price=0.0)
转换后的carDto
CarDto(make=Morris, seatCount=0, type=AI001, wheelColor=null, wheelPrice=null, price=0.00)
原始的list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
转换后的list
[0.00, 1.00, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00, 9.00, 10.00]
需要注意的是,在官方文档中,使用的注解为:
@Mapping(source = "price",numberFormat = "$#.00",target = "price")。
$ 代表要在前面添加的字符,可以自定义。
#.00 代表要保留的小数位,但是如果不写成#0.00,那么如果为0转换成的字符串是.00。
3、日期与字符串转换
日期转换与数字转换相似,使用如下注解,具体就不再赘述。
@Mapper
public interface CarMapper {
@Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy")
CarDto carToCarDto(Car car);
@IterableMapping(dateFormat = "dd.MM.yyyy")
List<String> stringListToDateList(List<Date> dates);
}
4、使用表达式
@Mapper
public interface SmsPassagewayMap {
@Mapping(target = "accessMsg",
expression = "java(com.jd.icity.tools.JsonHelper.object2Json(addPassagewayParam.getAccessMap()))"
)
SmsPassageway addPassagewayParam2SmsPassageway(AddPassagewayParam addPassagewayParam);
}
可以使用这样的方式,进行表达式注入。
5、更新属性
如果已经有了接收对象,更新目标对象
@InheritConfiguration(name = "carToCarDtoDate")
CarDto carToCarDtoInherit(Car car, @MappingTarget CarDto carDto);
InheritConfiguration 代表要继承的方法,name中的转换方法也会执行,MappingTarget表示映射的目标,只能声明一个参数为MappingTarget。 注意:作为映射目标传递的参数不能为空。
以上方法表示 car 来更新 carDto ,car 中的值会覆盖 carDto的值。
6、注入Spring方式
@Mapper(componentModel="spring")
public interface PersonConverter {
@Mappings({
@Mapping(source = "birthday", target = "birth"),
@Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
@Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),"yyyy-MM-dd HH:mm:ss"))"),
@Mapping(source = "user.age", target = "age"),
@Mapping(target = "email", ignore = true)
})
PersonDTO domain2dto(Person person);
}
测试使用:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BaseTestConfiguration.class)
public class PersonConverterTest {
//这里把转换器装配进来
@Autowired
private PersonConverter personConverter;
@Test
public void test() {
Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1));
PersonDTO personDTO = personConverter.domain2dto(person);
}
}