赞
踩
看了MapStruct使用入门后,相信小伙伴已经熟悉了MapStruct这款工具的初步使用。
今天接着进阶一下,来达到更深入了解和使用的目的。
我们已经知道基本类和包装类,以及String类这些,我们都不用做其他辅助操作,MapStruct就自动帮我们处理好了。
但是日常项目开发中,不可能总是简单对象属性直接的转换。有Map,List,其他对象,以及枚举类等都可能出现在我们的对象属性中,甚至你要转换的对象之间属性类型都不一样(当然不是基础类和包装类的区别哈),比如String和LocalDateTime之间的对象值copy,该如何处理?今天我们就一步步的探索,来解决这些问题。请往下看!
这种情况下,如果两个对象的属性名一样的话,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的使用
- @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;
- }
- }
- @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转换了。
最后,如果觉得写的对您有用,希望您留下您的素质三连,非常感谢。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。