Java MapStruct
Mapstruct可以代替BeanUtil来进行DTOVOPO之间的转换。它使用的是Java编译期的 annotation processor 机制,说白了它就是一个代码生成器,代替手工进行类型转换期间的取值赋值操作。

  1. @Mapper(componentModel = "spring")
  2. public interface AreaMapping {
  3. List<AreaInfoListVO> toVos(List<Area> areas);
  4. }

就这么几行就把一个PO的集合转换成了对应VO的集合。

  1. // spring bean
  2. @Autowired
  3. AreaMapping areaMapping
  4. // 转换源 areas
  5. List<Area> areas = ……;
  6. // 转换目标 vos
  7. List<AreaInfoListVO> vos = areaMapping.toVos(areas)

换成手写试试,起码得五分之一炷香的功夫。
但是这样写还是不太爽,每次都要挂对应的Mapper类。

Converter

Spring framework提供了一个Converter<S,T>接口:

  1. @FunctionalInterface
  2. public interface Converter<S, T> {
  3. @Nullable
  4. T convert(S source);
  5. default <U> Converter<S, U> andThen(Converter<? super T, ? extends U> after) {
  6. Assert.notNull(after, "After Converter must not be null");
  7. return (s) -> {
  8. T initialResult = this.convert(s);
  9. return initialResult != null ? after.convert(initialResult) : null;
  10. };
  11. }
  12. }

它的作用是将S转换为T,这和Mapstruct的作用不谋而合。
Converter会通过ConverterRegistry这个注册接口注册到ConversionService,然后就可以通过ConversionServiceconvert方法来进行转换:

  1. <T> T convert(@Nullable Object source, Class<T> targetType);

MapStruct Spring Extensions

根据上面的机制官方推出了MapStruct Spring Extensions插件, 它实现了一种机制,所有的Mapstruct映射接口(Mapper)只要实现了Converter,都会自动注册到ConversionService,只需要通过ConversionService就能完成任何转换操作。

  1. /**
  2. * @since 1.0.0
  3. */
  4. @Mapper(componentModel = "spring")
  5. public interface CarMapper extends Converter<Car, CarDto> {
  6. @Mapping(target = "seats", source = "seatConfiguration")
  7. CarDto convert(Car car);
  8. }

调用时:

  1. @Autowired
  2. private ConversionService conversionService;
  3. Car car = ……;
  4. CarDto carDto = conversionService.convert(car,CarDto.class);

MapStruct Spring Extensions 会自动生成一个适配类处理Mapper注册:

  1. package org.mapstruct.extensions.spring.converter;
  2. import cn.fcant.mapstruct.entity.Car;
  3. import cn.fcant.mapstruct.entity.CarDto;
  4. import org.springframework.context.annotation.Lazy;
  5. import org.springframework.core.convert.ConversionService;
  6. import org.springframework.stereotype.Component;
  7. /**
  8. * @since 1.0.0
  9. */
  10. @Component
  11. public class ConversionServiceAdapter {
  12. private final ConversionService conversionService;
  13. public ConversionServiceAdapter(@Lazy final ConversionService conversionService) {
  14. this.conversionService = conversionService;
  15. }
  16. public CarDto mapCarToCarDto(final Car source) {
  17. return (CarDto)this.conversionService.convert(source, CarDto.class);
  18. }
  19. }

自定义

自定义适配类的包路径和名称

默认情况下,生成的适配类将位于包org.mapstruct.extensions.spring.converter中,名称固定为ConversionServiceAdapter。如果希望修改包路径或者名称,可以这样:

  1. package cn.fcant.mapstruct.config;
  2. import org.mapstruct.MapperConfig;
  3. import org.mapstruct.extensions.spring.SpringMapperConfig;
  4. /**
  5. * @since 1.0.0
  6. */
  7. @MapperConfig(componentModel = "spring")
  8. @SpringMapperConfig(conversionServiceAdapterPackage = "cn.fcant.mapstruct.config",
  9. conversionServiceAdapterClassName = "MapStructConversionServiceAdapter")
  10. public class MapperSpringConfig {
  11. }

不指定conversionServiceAdapterPackage元素,生成的 Adapter 类将与注解的 Config 驻留在同一个包中,所以上面的路径是可以省略的。

指定ConversionService

如果Spring IoC容器中有多个ConversionService,可以通过@SpringMapperConfig注解的conversionServiceBeanName 参数指定。

  1. package cn.fcant.mapstruct.config;
  2. import org.mapstruct.MapperConfig;
  3. import org.mapstruct.extensions.spring.SpringMapperConfig;
  4. /**
  5. * @since 1.0.0
  6. */
  7. @MapperConfig(componentModel = "spring")
  8. @SpringMapperConfig(conversionServiceAdapterPackage = "cn.fcant.mapstruct.config",
  9. conversionServiceAdapterClassName = "MapStructConversionServiceAdapter",
  10. conversionServiceBeanName = "myConversionService")
  11. public class MapperSpringConfig {
  12. }

集成Spring的内置转换

Spring内部提供了很多好用的Converter<S,T>实现,有的并不直接开放,如果想用Mapstruct的机制使用它们,可以通过@SpringMapperConfig注解的 externalConversions注册它们。

  1. @MapperConfig(componentModel = "spring")
  2. @SpringMapperConfig(
  3. externalConversions = @ExternalConversion(sourceType = String.class, targetType = Locale.class))
  4. public interface MapstructConfig {}

会在适配器中自动生成相应的转换:

  1. @Component
  2. public class ConversionServiceAdapter {
  3. private final ConversionService conversionService;
  4. public ConversionServiceAdapter(@Lazy final ConversionService conversionService) {
  5. this.conversionService = conversionService;
  6. }
  7. public Locale mapStringToLocale(final String source) {
  8. return conversionService.convert(source, Locale.class);
  9. }
  10. }

总结

mapstruct-spring-annotations 使开发人员能够通过ConversionService使用定义的 Mapstruct 映射器,而不必单独导入每个 Mapper,从而允许 Mapper 之间的松散耦合。,它本身不会影响Mapstruct的机制。