tags: [MapStruct]
categories: [工具]
前言
在实际项目开发中,我们常常要新建很多DTO、VO对象用于数据的展示或者对象的传输
痛点在于
- 很多对象的字段名不一致,但是却是同一个字段
- 部分字段类型需要转换,比如字符串解析为时间,比如设置默认值
繁琐的工作常常耽误很多时间,最近了解到类MapStruct工具可以相对来说比较方便的完成这项工作
开始之前
开始之前,先分析一下之前的做法
自己书写代码
user.setName(userInfo.getUserName);
这种方式数据多了会非常繁琐!
使用反射来做到自动注入
List<USER> users =new ArrayList<USER>(8) ;
users.add(USER.builder().name("张三").password("123").build());
users.add(USER.builder().name("里斯").build());
List<HashMap<String, Object>> hashMapList = users.stream().map(x -> {
HashMap<String, Object> map = new HashMap(8);
Class<? extends USER> clazz = x.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
String fieldName = field.getName();
try {
Object o = field.get(x);
map.put(fieldName, o);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return map;
}).collect(Collectors.toList());
这里也能通过注解来做到不同的字段名做到相互转换,但是问题在于反射对于性能影像过大,而且需要新建注解
- 使用工具类,比如beanUtil或者dozer工具
字段名称一致较为方便,但是字段不一致,beanUtil不支持,dozer需要自己来配置xml实现不同字段的映射关系,xml都懂的,比较麻烦DozerBeanMapper dozerBeanMapper = new DozerBeanMapper();
HashMap map = dozerBeanMapper.map(USER.builder()
.name("张三").password("123").build(), HashMap.class);
MapStruct
简介
MapStruct实际上会在编译的时候就将指定的set/get方法生生产,编译为class文件,实际接口方法调用时是通过它的是实现类来操作的,而它的赋值规则就是油我们注解配置而生成从而达到,帮你写代码的作用
推荐IntelliJ IDEA
可以配置对应的插件Mapstruct Support
基本使用
导包,这里用的目前的最新版本 ```xml
<mapstruct-version>1.3.1.Final</mapstruct-version>
<!--map struct start-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${mapstruct-version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct-version}</version>
</dependency>
<!--map struct end-->
2. 书写转换接口
```java
@Mapper(componentModel = "spring" )
@Component
public interface TestConverter {
TestConverter INSTANCE = Mappers.getMapper(TestConverter.class);
/**
* AAA 转 TestPo
*
*
* @param aaa aaa
* @return TestPo
*/
@Mappings({
@Mapping(source = "aaa", target = "aa"),
@Mapping(source = "bbb", target = "bb"),
@Mapping(target = "ccc", expression = "java(aaa.getCcc().toString())")
})
BwLsdMxPo dto2TestPo(AAA aaa);
}
使用时,直接注入,然后调用接口中的方法就行了
@Autowired
private TestConverter testConverter;
...
TestPo testPo = TestConverter.dto2TestPo(x);
进阶使用
依赖注入
@Mapper(componentModel = "spring" )
在接口上@Mapper
注解中的componentModel
属性设置为spring代表将改接口的是实现类作为bean
注入到spring中,这样我们就能使用@Autowired
注解来注入该bean
隐式转换
在许多情况下,MapStruct会自动处理类型转换
- 基本数据类型和包装类的转换,如
int
和Integer
- 基本数据类型之间和包装类之间,如
int
和long
或byte
和Integer
- 在
enum
类型和之间String
- 在大数字类型(
java.math.BigInteger
,java.math.BigDecimal
)和Java基本类型(包括其包装器)以及String之间。java.text.DecimalFormat
可以指定理解的格式字符串 日期类型之间,这个需要指定格式化模板
@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);
}
显示的表达式转换
我们可以通过
Expression
标签来在字符串中书写JAVA代码,完成指定属性的转换,值得注意的是,若该方法的调用者未导包需要带上包名defaultExpression
为默认表达式@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "timeAndFormat",
expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )")
Target sourceToTarget(Source s);
@Mapping(target="id", source="sourceId", defaultExpression = "java( UUID.randomUUID().toString() )")
Target sourceToTarget(Source s);
}
常量 默认值 属性忽略
有些情况下,部分属性为null时,我们需要设置默认值,常量和默认值的区别在于当不为空时,默认值会被替换为实际值
@Mapper(uses = StringListMapper.class)
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")
@Mapping(target = "longProperty", source = "longProp", defaultValue = "-1")
@Mapping(target = "stringConstant", constant = "Constant Value")
@Mapping(target = "integerConstant", constant = "14")
@Mapping(target = "longWrapperConstant", constant = "3001")
@Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014")
@Mapping(target = "stringListConstants", constant = "jack-jill-tom")
Target sourceToTarget(Source s);
}
装饰器完成额外的操作
列举一个场景,比如实体类中价税合计 = 税额 + 合计金额,我们需要对赋完值的税额和合计金额做一个累加操作,这里可以通过装饰器来实现
一般实现
public abstract class PersonMapperDecorator implements PersonMapper {
private final PersonMapper delegate;
public PersonMapperDecorator(PersonMapper delegate) {
this.delegate = delegate;
}
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setFullName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}
通过spring注入来实现
public abstract class PersonMapperDecorator implements PersonMapper {
@Autowired
@Qualifier("delegate")
private PersonMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}