声明:
1、DO(业务实体对象),DTO(数据传输对象)。
在一个成熟的工程中,尤其是现在的分布式系统中,应用与应用之间,还有单独的应用细分模块之后,DO 一般不会让外部依赖,这时候需要在提供对外接口的模块里放 DTO 用于对象传输,也即是 DO 对象对内,DTO对象对外,DTO 可以根据业务需要变更,并不需要映射 DO 的全部属性。
/** * 获取营销信息 * * @param hallId 场馆编号 * @param modelCode 车型代码 * @return */ BrandMarketInfoDTO getBrandMarketInfo(String hallId, String modelCode);
/** * 获取水牌营销信息 * * @param hallId * @param modelCode * @return */ HallCarManageDO getBoardMarketInfo(@Param("hallId") String hallId, @Param("modelCode") String modelCode);
但是我们HallCarManageDO和BrandMarketInfoDTO中的属性和属性类型不是相等的。这种 对象与对象之间的互相转换,就需要有一个专门用来解决转换问题的工具,毕竟每一个字段都 get/set 会很麻烦。
以下列举被广大开发工程师常用的Bean属性复制工具
以下选取属性赋值的功能来对比每个工具类的不同
import org.springframework.beans.BeanUtils; /** * @author james mu * @date 2019/10/22 */ public class PojoUtils { /** * * @param source 源对象 * @param clazz 目标对象 * * @return 复制属性后的对象 */ public static <T> T copyProperties(Object source, Class<T> clazz) { if (source == null) { return null; } T target; try { target = clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException("通过反射创建对象失败"); } BeanUtils.copyProperties(source, target); return target; } }
springframework
的BeanUtils也是通过java 内省机制
获取getter/setter,然后通过反射调用从而实现属性复制。
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency>
import net.sf.cglib.beans.BeanCopier; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author james mu * @date 2019/10/22 */ public class BeanCopierUtils { /** * BeanCopier拷贝速度快,性能瓶颈出现在创建BeanCopier实例的过程中。 * 所以,把创建过的BeanCopier实例放到缓存中,下次可以直接获取,提升性能 */ public static Map<String, BeanCopier> beanCopierMap = new ConcurrentHashMap<String, BeanCopier>(); /** * cp 对象赋值 * * @param source 源对象 * @param target 目标对象 */ public static void copyProperties(Object source, Object target) { String beanKey = generateKey(source.getClass(), target.getClass()); BeanCopier copier = null; if (!beanCopierMap.containsKey(beanKey)) { copier = BeanCopier.create(source.getClass(), target.getClass(), false); beanCopierMap.put(beanKey, copier); } else { copier = beanCopierMap.get(beanKey); } copier.copy(source, target, null); } private static String generateKey(Class<?> class1, Class<?> class2) { return class1.toString() + class2.toString(); } }
copier
,因为 BeanCopier.create 使用了缓存,该过程也消耗资源,建议全局只初始化一次。
MapSturct
是一个生成类型安全, 高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。
抓一下重点:
JavaBean
从字面的理解, 我们可以知道, 该工具可以帮我们实现 JavaBean
之间的转换, 通过注解的方式。
同时, 作为一个工具类,相比于手写, 其应该具有便捷, 不容易出错的特点。
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.3.0.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.3.0.Final</version> <scope>provided</scope> </dependency>
UserDO
import lombok.Data; import lombok.ToString; import java.util.Date; /** * @author james mu * @date 2019/10/22 */ @Data @ToString public class UserDO { private String name; private String password; private Integer age; private Date birthday; private String sex; }
UserDTO
import lombok.Data; import lombok.ToString; /** * @author james mu * @date 2019/10/22 */ @Data @ToString public class UserDTO { private String name; private String age; private String birthday; private String gender; }
UserConvertUtils
import com.sanshengshui.javabeanconvert.DO.UserDO; import com.sanshengshui.javabeanconvert.DTO.UserDTO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; /** * @author james mu * @date 2019/10/22 */ @Mapper public interface UserConvertUtils { UserConvertUtils INSTANCE = Mappers.getMapper(UserConvertUtils.class); /** * 类型转换 * * @param userDO UserDO数据持久层类 * @return 数据传输类 */ @Mappings({ @Mapping(target = "gender", source = "sex") }) UserDTO doToDTO(UserDO userDO); }
@Mapping
结果如下:
UserDO(name=snow, password=123, age=20, birthday=Tue Oct 22 17:10:19 CST 2019, sex=男) +-+-+-+-+-+-+-+-+-+-+- UserDTO(name=snow, age=20, birthday=19-10-22 下午5:10, gender=男)
上面中, 我写了3个步骤来实现了从 UserDTO
到 UserDO
的转换。
那么, 作为一个注解处理器, 通过 MapStruct
生成的代码具有怎么样的优势呢?
Java反射原理和反射低的原因: https://juejin.im/post/5da33b2351882509334fc0d3
这是相对反射来说的, 反射需要去读取字节码的内容, 花销会比较大。 而通过 MapStruct
来生成的代码, 其类似于人手写。 速度上可以得到保证。
前面例子中生成的代码可以在编译后看到。 在 target/generated-sources/annotations 里可以看到。
对应的代码
@Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2019-10-22T17:10:17+0800", comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_222 (Azul Systems, Inc.)" ) public class UserConvertUtilsImpl implements UserConvertUtils { @Override public UserDTO doToDTO(UserDO userDO) { if ( userDO == null ) { return null; } UserDTO userDTO = new UserDTO(); userDTO.setGender( userDO.getSex() ); userDTO.setName( userDO.getName() ); if ( userDO.getAge() != null ) { userDTO.setAge( String.valueOf( userDO.getAge() ) ); } if ( userDO.getBirthday() != null ) { userDTO.setBirthday( new SimpleDateFormat().format( userDO.getBirthday() ) ); } return userDTO; } }
可以看到其生成了一个实现类, 而代码也类似于我们手写, 通俗易懂。
测试在两个简单的Bean之间转换的耗时,执行次数分别为10、100、1k、10k、100k,时间单位为ms。
虽然反射效率低,但这个时间是很小很小的。根据不同工具的性能及功能维度,个人建议当对象转换操作较少或者应用对性能要求较高时,尽量不采用工具,而是手写getter/setter;在不考虑性能的情况下,普通的对象转换可以使用Cglib.BeanCopier,复杂的对象转换使用MapStruct。