一、mapstruct简介
mapstruct是一种实体类映射框架,能够通过Java注解将一个实体类的属性安全地赋值给另一个实体类。有了mapstruct,只需要定义一个映射器接口,声明需要映射的方法,在编译过程中,mapstruct会自动生成该接口的实现类,实现将源对象映射到目标对象的效果。
二、快速使用
1、引入依赖
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
2、编写接口
@Mapper
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
PersonDTO convert(Person person);
}
@Data
public class PersonDTO {
private String describe;
private String id;
private String name;
private int age;
private BigDecimal source;
private double height;
private Date createTime;
}
@Data
public class Person {
private String describe;
private String id;
private String name;
private int age;
private BigDecimal source;
private double height;
private Date createTime;
}
3、使用
@Test
public void convertTest() {
Person person = new Person();
person.setId("123");
person.setAge(1);
person.setName("wgj");
person.setDescribe("描述");
person.setHeight(123.12);
person.setCreateTime(new Date());
person.setSource(new BigDecimal("3.14"));
PersonDTO convert = PersonMapper.INSTANCT.convert(person);
System.out.println(JSON.toJSONString(convert));
}
三、用法
1、属性名称不同
@Data
public class Person {
private String name;
}
@Data
public class PersonDTO {
private String personName;
}
@Mapper
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
@Mapping(target = "personName", source = "name")
PersonDTO convert(Person person);
}
2、属性类型不同(1)
@Data
public class Person {
private Date createTime;
}
@Data
public class PersonDTO {
private String createTime;
}
@Mapper
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
@Mapping(target = "createTime" ,source = "createTime", dateFormat = "yyyy-MM-dd")
PersonDTO convert(Person person);
}
3、属性类型不同(2)
@Data
public class Person {
private String age;
}
@Data
public class PersonDTO {
private int age;
}
@Mapper
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
PersonDTO convert(Person person);
}
@Test
public void convertTest() {
Person person = new Person();
person.setAge("abc");
}
4、指定默认值
@Mapping(target = "personName", source = "name", defaultValue = "金网络")
PersonDTO convert(Person person);
5、忽略属性
@Mapping(target = "id", ignore = true)// 忽略id,不进行映射
PersonDTO convert(Person person);
6、自定义类型转换方法
@Data
public class Person {
private String createTime;
private Date updateTime;
}
@Data
public class PersonDTO {
private Date createTime;
private String updateTime;
}
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(uses = DateMapper.class)
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
PersonDTO convert(Person person);
}
public class PersonMapperImpl implements PersonMapper {
private final DateMapper dateMapper = new DateMapper();
public PersonMapperImpl() {
}
public PersonDTO convert(Person person) {
personDTO.setCreateTime(this.dateMapper.asDate(person.getCreateTime()));
personDTO.setUpdateTime(this.dateMapper.asString(person.getUpdateTime()));
return personDTO;
}
}
7、使用其他的值
@Data
public class Person {
}
@Data
public class PersonDTO {
private String other;
}
@Mapper(uses = DateMapper.class)
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
PersonDTO convert(Person person, String other);
}
PersonDTO personDTO = PersonMapper.INSTANCT.convert(person, "other");
8、@Qualifier:标记的自定义注解标记
public class DateFormtUtil {
@DateFormat
public static String plusDate(String date) {
return DateUtils.plusDate(date, DateUtils.YYYY_MM_DD, 1);
}
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface DateFormat {
}
}
@Mapping(target = "endBizTime", source = "bizEndDate", qualifiedBy = DateFormtUtil.DateFormat.class)
@Mapping(target = "endPaymentTime", source = "paymentEndDate", qualifiedBy = DateFormtUtil.DateFormat.class)
PaymentVO convertToPaymentVO(FeePaymentQueryRequest request);
9、@namd
@Named("plusDate")
public static String plusDate_(String date){
return DateUtils.plusDate(date, DateUtils.YYYY_MM_DD, 1);
}
@Mapping(target = "endBizTime", source = "bizEndDate", qualifiedByName = "plusDate")
@Mapping(target = "endPaymentTime", source = "paymentEndDate", qualifiedByName = "plusDate")
PaymentVO convertToPaymentVO(FeePaymentQueryRequest request);
10、使用表达式
@Mapping(target = "endPaymentTime", expression = "java(DateUtils.plusDate(request.getPaymentEndDate(), DateUtils.YYYY_MM_DD, 1))")
PaymentVO convertToPaymentVO(FeePaymentQueryRequest request);
@Mapper(uses = {DateFormtUtil.class}, imports = {DateUtils.class})
11、组合映射-多个源对象
@Data
public class PersonDTO {
private String describe;
private String id;
private String personName;
private int age;
private BigDecimal source;
private double height;
private Date createTime;
private String other;
}
@Data
public class BasicEntity {
private Date createTime;
private String createBy;
private Date updateTime;
private String updateBy;
private int _ROW;
}
@Mapping(target = "createTime", source = "basicEntity.createTime")
PersonDTO convertToPersonDTO(Person person, BasicEntity basicEntity);
12、嵌套映射
@Data
public class Person {
private Child personChild;
}
@Data
public class PersonDTO {
private Child child;
}
@Mapping(target = "child", source = "personChild")
PersonDTO convert(Person person);
13、numberFormat()
public class Person {
private int age;
}
@Data
public class PersonDTO {
private String age;
}
@Mapping(target = "age", source = "age", numberFormat = "#0.00")
PersonDTO convert(Person person);
14、逆映射
@Mapping(target = "child", source = "personChild")
@Mapping(target = "age", source = "age", numberFormat = "#0.00")
PersonDTO convert(Person person);
@InheritInverseConfiguration
Person convert(PersonDTO personDTO);
15、集合映射
@Data
public class Person {
private String describe;
private String id;
private String name;
private int age;
private BigDecimal source;
private double height;
private String createTime;
private Date updateTime;
private Child personChild;
}
@Data
public class PersonDTO {
private String describe;
private String id;
private String personName;
private String age;
private BigDecimal source;
private double height;
private Date createTime;
// private String updateTime;
private String other;
private ChildDTO childDTO;
}
@Data
public class ChildDTO {
private String childName;
private String descDto;
}
@Data
public class Child {
private String childName;
private String desc;
}
@Data
public class PagePersonDTO {
private List<PersonDTO> personDTOS;
private int total;
private int curr;
private int size;
}
@Data
public class PagePerson {
private List<Person> personList;
private int total;
private int curr;
private int size;
}
@Mapper
public interface PagePersonConverter {
PagePersonConverter INSTANCT = Mappers.getMapper(PagePersonConverter.class);
@Mapping(target = "personDTOS", source = "personList")
PagePersonDTO convert(PagePerson pagePerson);
@Mapping(target = "createTime", source = "createTime", dateFormat = "yyyy-MM-dd")
@Mapping(target = "childDTO", source = "personChild")
@Mapping(target = "childDTO.descDto", source = "personChild.desc")
PersonDTO personToPersonDTO(Person person);
}
16、集成到 spring
// 接口
@Mapper(componentModel = "spring")
public interface FeeBusinessConverter {
OpenAccountVO convert(OpenAccountRequest request);
PaymentAccountDTO convert(PaymentAccount paymentAccount);
}
// 使用
@RestController
@Slf4j
public class FeeController implements FeeClient {
// 注入
@Resource
private FeeBusinessConverter feeBusinessConverter;
// 使用
@Override
public R<PaymentAccountDTO> openAccount(OpenAccountRequest request) {
// 省略
OpenAccountVO openAccountVO = feeBusinessConverter.convert(request);
// 省略
}
}
四、原理
1、mapstruct原理
mapstruct是基于JSR 269实现的,JSR 269是JDK引进的一种规范。有了它,能够实现在编译期处理注解,并且读取、修改和添加抽象语法树中的内容。JSR 269使用Annotation Processor在编译期间处理注解,Annotation Processor相当于编译器的一种插件,因此又称为插入式注解处理。想要实现JSR 269,主要有以下几个步骤:
1、继承AbstractProcessor类,并且重写process方法,在process方法中实现自己的注解处理逻辑。
2、在META-INF/services目录下创建javax.annotation.processing.Processor文件注册自己实现的Annotation Processor
执行过程
(1)、生成抽象语法树。Java编译器对Java源码进行编译,生成抽象语法树(Abstract Syntax Tree,AST)。
(2)、调用实现了JSR 269 API的程序。只要程序实现了JSR 269 API,就会在编译期间调用实现的注解处理器。
(3)、修改抽象语法树。在实现JSR 269 API的程序中,可以修改抽象语法树,插入自己的实现逻辑。
(4)、生成字节码。修改完抽象语法树后,Java编译器会生成修改后的抽象语法树对应的字节码文件件。
2、mapstruct源码
public class MappingProcessor extends AbstractProcessor {
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnvironment) {
// nothing to do in the last round
if ( !roundEnvironment.processingOver() ) {
RoundContext roundContext = new RoundContext( annotationProcessorContext );
// process any mappers left over from previous rounds
Set<TypeElement> deferredMappers = getAndResetDeferredMappers();
processMapperElements( deferredMappers, roundContext );
// get and process any mappers from this round
Set<TypeElement> mappers = getMappers( annotations, roundEnvironment );
processMapperElements( mappers, roundContext );
}
else if ( !deferredMappers.isEmpty() ) {
// If the processing is over and there are deferred mappers it means something wrong occurred and
// MapStruct didn't generate implementations for those
for ( DeferredMapper deferredMapper : deferredMappers ) {
TypeElement deferredMapperElement = deferredMapper.deferredMapperElement;
Element erroneousElement = deferredMapper.erroneousElement;
String erroneousElementName;
if ( erroneousElement instanceof QualifiedNameable ) {
erroneousElementName = ( (QualifiedNameable) erroneousElement ).getQualifiedName().toString();
}
else {
erroneousElementName =
erroneousElement != null ? erroneousElement.getSimpleName().toString() : null;
}
// When running on Java 8 we need to fetch the deferredMapperElement again.
// Otherwise the reporting will not work properly
deferredMapperElement = annotationProcessorContext.getElementUtils()
.getTypeElement( deferredMapperElement.getQualifiedName() );
processingEnv.getMessager()
.printMessage(
Kind.ERROR,
"No implementation was created for " + deferredMapperElement.getSimpleName() +
" due to having a problem in the erroneous element " + erroneousElementName + "." +
" Hint: this often means that some other annotation processor was supposed to" +
" process the erroneous element. You can also enable MapStruct verbose mode by setting" +
" -Amapstruct.verbose=true as a compilation argument.",
deferredMapperElement
);
}
}
return ANNOTATIONS_CLAIMED_EXCLUSIVELY;
}
}