看了MapStruct使用入门后,相信小伙伴已经熟悉了MapStruct这款工具的初步使用。
今天接着进阶一下,来达到更深入了解和使用的目的。
我们已经知道基本类和包装类,以及String类这些,我们都不用做其他辅助操作,MapStruct就自动帮我们处理好了。
但是日常项目开发中,不可能总是简单对象属性直接的转换。有Map,List,其他对象,以及枚举类等都可能出现在我们的对象属性中,甚至你要转换的对象之间属性类型都不一样(当然不是基础类和包装类的区别哈),比如String和LocalDateTime之间的对象值copy,该如何处理?今天我们就一步步的探索,来解决这些问题。请往下看!
No1. A对象中有属性为List类型,B对象中是String[]类型
这种情况下,如果两个对象的属性名一样的话,MapStruct可以帮我们自动处理。
@Data
public class UserBO {
private String name;
private Integer id;
private String address;
private String email;
private Integer age;
private String gender;
private List<String> hobbies;
}
@Data
public class UserDO {
private String name;
private Integer id;
private String address;
private String email;
private Integer age;
private String gender;
private String[] hobbies;
}
@Mapper
public interface MapStructConvertUtil {
MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
UserDO convert(UserBO userBO);
}
public class MapStructTest {
public static void main(String[] args) {
UserBO userBO = new UserBO();
userBO.setName("AwesomeJokerWang");
userBO.setAddress("TJ");
userBO.setAge(25);
userBO.setEmail("123456789@163.com");
userBO.setGender("Man");
userBO.setId(1);
userBO.setHobbies(new ArrayList<String>(){
{
add("唱");
add("跳");
add("Rap");
}
});
System.out.println(userDO);
}
}
执行结果自然是没得问题,hobbies的值完美copy。
看看生成后impl文件,是不是感觉有点牛批。别着急,厉害还没完呢。
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-06-18T18:00:46+0800",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_292 (Amazon.com Inc.)"
)
public class MapStructConvertUtilImpl implements MapStructConvertUtil {
@Override
public UserDO convert(UserBO userBO) {
if ( userBO == null ) {
return null;
}
UserDO userDO = new UserDO();
userDO.setName( userBO.getName() );
userDO.setId( userBO.getId() );
userDO.setAddress( userBO.getAddress() );
userDO.setEmail( userBO.getEmail() );
userDO.setAge( userBO.getAge() );
userDO.setGender( userBO.getGender() );
userDO.setHobbies( stringListToStringArray( userBO.getHobbies() ) );
return userDO;
}
protected String[] stringListToStringArray(List<String> list) {
if ( list == null ) {
return null;
}
String[] stringTmp = new String[list.size()];
int i = 0;
for ( String string : list ) {
stringTmp[i] = string;
i++;
}
return stringTmp;
}
}
这是两个对象的属性名一致的情况下,我们甚至都不要对我们的接口做任何改动就可以达到效果。但是,如果两个对象的属性名不一样怎么办呢。这下你再厉害也找不到了哇,我们试一下。
在UserDO上做一点点改动,让它和UserBO中的hobbies属性的属性名不一样。其他都不变,我们测试一下结果。
@Data
public class UserDO {
private String name;
private Integer id;
private String address;
private String email;
private Integer age;
private String gender;
private String[] d_hobbies;
}
果不其然,终于为null了哇,和我们猜想的一样,而且接口的实现类中也没有了hobbies属性的赋值操作。
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-06-18T18:09:04+0800",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_292 (Amazon.com Inc.)"
)
public class MapStructConvertUtilImpl implements MapStructConvertUtil {
@Override
public UserDO convert(UserBO userBO) {
if ( userBO == null ) {
return null;
}
UserDO userDO = new UserDO();
userDO.setName( userBO.getName() );
userDO.setId( userBO.getId() );
userDO.setAddress( userBO.getAddress() );
userDO.setEmail( userBO.getEmail() );
userDO.setAge( userBO.getAge() );
userDO.setGender( userBO.getGender() );
return userDO;
}
}
解决这个问题,需要引出MapStruct的关键注解@Mapping。showcode。
只需要在转换接口上加入@Mapping注解,指明source的值和target的值即可做匹配,这么一来,MapStruct就可以匹配到不同名字的属性了。@Mappings是可以包裹多组@Mapping对象的。在@Mapping映射较多的时候可以用@Mappings包裹,也可以累加多个@Mapping,这个没什么影响。
@Mapper
public interface MapStructConvertUtil {
MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
@Mappings({
@Mapping(source = "hobbies", target = "d_hobbies")
})
UserDO convert(UserBO userBO);
}
看结果。hobbies的值可以正确copy,而且接口的实现类也和上一个一样。
那么,List和List,Map和HashMap等类型的复制也是一样,只要两个对象的属性名一样,就不用使用@Mapping注解来手动匹配,即使copy对象之间的属性类型是子父关系,也可以直接复制值,具体实现我就不一一列举了,有兴趣的小伙伴可以自己试试。
再来看一下第二种情况,如果上例中的B对象中hobbies的属性是String类型,不使用@Mapping,MapStruct能自动帮我们匹配么?showcode
B对象的hobbies属性类型改为String,接口去掉@Mapping注解。
@Data
public class UserDO {
private String name;
private Integer id;
private String address;
private String email;
private Integer age;
private String gender;
private String hobbies;
}
@Mapper
public interface MapStructConvertUtil {
MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
UserDO convert(UserBO userBO);
}
测试结果:build的时候就报错了,List和String直接映射不了。
java: Can't map property "List<String> hobbies" to "String hobbies". Consider to declare/implement a mapping method: "String map(List<String> value)".
那如果把@Mapping注解加上,手动匹配后,能正确build么?可能此时有小伙伴就想到了,那A和B的对象中属性名不是一样么,你不是上面说了属性名一样的话,不用加@Mapping来手动映射么。没错,此时即使你用@Mapping注解匹配了,也还是报上面的错。因为错误的原因并不是两个对象找不到匹配,而是List和String cat not map(不能映射)。
看一下加了@Mapping的结果
@Mapper
public interface MapStructConvertUtil {
MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
@Mappings({
@Mapping(source = "hobbies", target = "hobbies")
})
UserDO convert(UserBO userBO);
}
java: Can't map property "List<String> hobbies" to "String hobbies". Consider to declare/implement a mapping method: "String map(List<String> value)".
那么,怎么解决?@Mapping注解里除了source和target,还有一个expression可以用。
@Repeatable(Mappings.class)
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface Mapping {
String target();
String source() default "";
String dateFormat() default "";
String numberFormat() default "";
String constant() default "";
String expression() default "";
String defaultExpression() default "";
boolean ignore() default false;
Class<? extends Annotation>[] qualifiedBy() default {};
String[] qualifiedByName() default {};
Class<?> resultType() default void.class;
String[] dependsOn() default {};
String defaultValue() default "";
NullValueCheckStrategy nullValueCheckStrategy() default NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default NullValuePropertyMappingStrategy.SET_TO_NULL;
Class<? extends Annotation> mappingControl() default MappingControl.class;
}
我们改一下接口里的@Mapping写法,用expression方法处理List和String。这里说明一下:使用expression方法后,就不用写source了,因为表达式参数里需要写source参数,不用重复写。
@Mapper
public interface MapStructConvertUtil {
MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
@Mappings({
@Mapping(target = "hobbies", expression = "java(userBO.getHobbies().get(0))")
})
UserDO convert(UserBO userBO);
}
再测试看一下结果。成功赋值,我们用userBO.getHobbies().get(arg)来获取需要匹配的参数
其实,介绍到这,小伙伴们也基本也就知道了,对象属性类型或名字不一样时,用@Mapping注解处理就可以,遇到从集合中取某一个值和单个值匹配时,用expression表达式处理就行。expression的写法也很简单,Java方法里写你的逻辑处理就行,就跟写Java代码一样。
expression = “java(处理逻辑)”
后面再简单看一下对象中有枚举,以及String和LocalDateTime之间的代码示例。因为核心的处理就是expression的使用
No2. A对象中有属性为Enum类型,B对象中是基本类型或包装类型
@Data
public class UserBO {
private String name;
private Integer id;
private String address;
private String email;
private Integer age;
private String gender;
private List<String> hobbies;
private SkillEnums role;
}
@Data
public class UserDO {
private String name;
private Integer id;
private String address;
private String email;
private Integer age;
private String gender;
private String hobbies;
private String d_role;
}
SkillEnums对象
@Getter
@AllArgsConstructor
public enum SkillEnums {
JAVA("java","25年了"),
PHP("php", "号称全世界最好的语言"),
C_Sharp("c#", "微软小宝贝儿"),
C("c","计算机原理");
private final String code;
private final String msg;
public static String getSkill(String code){
SkillEnums[] values = SkillEnums.values();
for (int i = 0; i < values.length; i++) {
if (code.equals(values[i].getCode())) {
return values[i].getCode();
}
}
return "";
}
}
对象名一样的话不用@Mapping,不一样的话加个@Mapping手动做匹配,经测试可以成功赋值。
我们主要看一MapStruct帮我们生成的接口实现类。不得不说MapStruct太厉害了。
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-06-19T23:33:43+0800",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_292 (Amazon.com Inc.)"
)
public class MapStructConvertUtilImpl implements MapStructConvertUtil {
@Override
public UserDO convert(UserBO userBO) {
if ( userBO == null ) {
return null;
}
UserDO userDO = new UserDO();
userDO.setName( userBO.getName() );
userDO.setId( userBO.getId() );
userDO.setAddress( userBO.getAddress() );
userDO.setEmail( userBO.getEmail() );
userDO.setAge( userBO.getAge() );
userDO.setGender( userBO.getGender() );
if ( userBO.getRole() != null ) {
userDO.setRole( userBO.getRole().name() );
}
userDO.setHobbies( userBO.getHobbies().get(0) );
return userDO;
}
}
No3. A对象中有属性为String类型,B对象中是LocalDate或LocalDateTime等类型
@Data
public class UserBO {
private String name;
private Integer id;
private String address;
private String email;
private Integer age;
private String gender;
private List<String> hobbies;
private SkillEnums role;
private String birthday;
}
@Data
public class UserDO {
private String name;
private Integer id;
private String address;
private String email;
private Integer age;
private String gender;
private String hobbies;
private String role;
private LocalDate birthday;
}
修改一下接口,增加expression表达式处理String和LocalDate的转换。DateUtils.convertDate是我写的一个工具类。
@Mapper
public interface MapStructConvertUtil {
MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
@Mappings({
@Mapping(target = "hobbies", expression = "java(userBO.getHobbies().get(0))"),
@Mapping(target = "birthday", expression = "java(DateUtils.convertDate(userBO.getBirthday()))")
})
UserDO convert(UserBO userBO);
}
DateUtils.class
public class DateUtils {
public static LocalDate convertDate(String strDate) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return LocalDate.parse(strDate, formatter);
}
}
OK,介绍到这,我想小伙伴就已经对MapStruct的使用有了更进一步的了解。
不得不说MapStruct这个小工具很强大,很简单,也很好用。后续有想要深入了解的小伙伴可以看看源码,核心的Mappers方法实现以及其他的注解。
好了,MapStruct的使用基本就介绍完了,项目中可以考虑使用这款强大而工具来实现对象之间的转换,而不用再写那么多繁琐的set转换了。
最后,如果觉得写的对您有用,希望您留下您的素质三连,非常感谢。