• 首先回顾一下原型模式,使用它的目的是在不同层之间拷贝对象用的。例如将Service层的DTO拷贝到Controller的VO,如果无脑使用get,set方法,是一种很不优雅的实现。
    • 在开发中,常用的拷贝Bean工具是Spring提供的BeanUtils,它的缺点是使用了反射,性能较低。而Cglib提供的BeanCopier利用动态代理提升拷贝性能。
    • 当我们直接使用BeanCopier时,需要先根据被拷贝类和目标类的类型new出一个BeanCopier实例,然后再调用这个实例的拷贝方法。因为根据两个类型new出的实例是唯一的,所以如果在多处拷贝,那么只需要一个BeanCopier实例即可。例如想把A的属性拷贝到B,那么我们只需要一个全局唯一的,根据A.ClassB.Class创建出的,专门为A拷贝B的,BeanCopier实例。
    • 那么就可以使用享元+单例(double-check)的思想,为每一对拷贝类去创建一个BeanCopier实例。即,这两个类共享这个实例中的元配置。
    1. public class BeanCopierUtils {
    2. /**
    3. * BeanCopier缓存
    4. */
    5. public static Map<String, BeanCopier> beanCopierCacheMap = new HashMap<String, BeanCopier>();
    6. /**
    7. * 将source对象的属性拷贝到target对象中去
    8. * @param source source对象
    9. * @param target target对象
    10. */
    11. public static void copyProperties(Object source, Object target){
    12. String cacheKey = source.getClass().toString() +
    13. target.getClass().toString();
    14. BeanCopier beanCopier = null;
    15. // 线程1和线程2,同时过来了
    16. if (!beanCopierCacheMap.containsKey(cacheKey)) {
    17. // 两个线程都卡这儿了
    18. // 但是此时线程1先获取到了锁,线程2就等着
    19. synchronized(BeanCopierUtils.class) {
    20. // 线程1进来之后,发现这里还是没有那个BeanCopier实例
    21. // 此时线程2,会发现缓存map中已经有了那个BeanCopier实例了,此时就不会进入if判断内的代码
    22. if(!beanCopierCacheMap.containsKey(cacheKey)) {
    23. // 进入到这里会创建一个BeanCopier实例并且放在缓存map中
    24. beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);
    25. beanCopierCacheMap.put(cacheKey, beanCopier);
    26. } else {
    27. beanCopier = beanCopierCacheMap.get(cacheKey);
    28. }
    29. }
    30. } else {
    31. beanCopier = beanCopierCacheMap.get(cacheKey);
    32. }
    33. beanCopier.copy(source, target, null);
    34. }
    35. }
    • 无论使用Spring提供的BeanUtils还是Cglib提供的BeanCopier,都可以把拷贝方法集成到Object的clone()方法中去。这里我们使用继承的思想,使用一个AbstractObject去封装我们自定义的Object自带的方法。然后让后续我们创建的所有Bean都继承这个AbstractObject
    • 拷贝的形式可以分为浅拷贝和深拷贝两种。前者只拷贝基础属性值,后者会递归地拷贝引用。考虑到一个POJO类中的属性,除了基础属性,最多再来个ListMap类型也可以转换为List),所以在深拷贝中,只考虑对List的处理即可。
    • 深度克隆侧重于拷贝当前对象中的List<T>属性,这种拷贝是递归拷贝,也就是说,如果T中还有List,那么也会给T拷贝这个List
    • 深度克隆的实现方式使用了大量的反射和泛型操作,在注释上使用了CategoryDTO作为例子,它包含一个List<RelationDto>。假设要将其克隆成CategoryVOList<RelationVo>
    • 正向克隆指的是从前端到后端的POJO拷贝,也就是VO->DTO->DO。反向是DO->DTO->VO
    1. @SuppressWarnings({ "rawtypes", "unchecked" })
    2. public class AbstractObject {
    3. /**
    4. * 浅度克隆
    5. * @param clazz
    6. * @return
    7. * @throws Exception
    8. */
    9. public <T> T clone(Class<T> clazz) throws Exception {
    10. T target = clazz.newInstance();
    11. BeanCopierUtils.copyProperties(this, target);
    12. return target;
    13. }
    14. /**
    15. * 浅度克隆
    16. * @return
    17. * @throws Exception
    18. */
    19. public <T> T clone(T target) throws Exception {
    20. BeanCopierUtils.copyProperties(this, target);
    21. return target;
    22. }
    23. /**
    24. * 深度克隆
    25. * @param clazz
    26. * @param cloneDirection 克隆方向:
    27. * @return
    28. * @throws Exception
    29. */
    30. public <T> T clone(Class<T> clazz, Integer cloneDirection) throws Exception {
    31. /**
    32. * 先完成基本字段的浅克隆
    33. */
    34. T target = clazz.newInstance();
    35. BeanCopierUtils.copyProperties(this, target);
    36. /**
    37. * 完成所有List类型的深度克隆
    38. */
    39. //1.获取被拷贝对象实例的Class对象,CategoryDTO
    40. Class<?> thisClazz = this.getClass();
    41. //2.获取被拷贝对象实例的所有属性
    42. Field[] fields = thisClazz.getDeclaredFields();
    43. //3.遍历所有属性,对List类型的属性做深度拷贝
    44. for(Field field : fields) {
    45. //将所有属性设置为可访问的
    46. field.setAccessible(true);
    47. // 只关心List类型的属性
    48. // field = private List<Relation> relations;
    49. if(field.getType() != List.class) {
    50. continue;
    51. }
    52. //获取依赖的List的属性值
    53. // List<RelationDTO>集合
    54. List<?> list = (List<?>) field.get(this);
    55. if(list == null || list.size() == 0) {
    56. continue;
    57. }
    58. // 获取List集合中的泛型类型
    59. // RelationDTO
    60. Class<?> listGenericClazz = getListGenericType(field);
    61. // 获取要克隆的目标类型
    62. // 假设CloneDirection是反向,此时获取到的就是RelationVO
    63. Class<?> cloneTargetClazz = getCloneTargetClazz(listGenericClazz, cloneDirection);
    64. // 将list集合克隆到目标list集合中去
    65. List clonedList = new ArrayList();
    66. cloneList(list, clonedList, cloneTargetClazz, cloneDirection);
    67. // 获取设置克隆好的list的方法名称
    68. // setRelations
    69. Method setFieldMethod = getSetCloneListFieldMethodName(field, clazz);
    70. setFieldMethod.invoke(target, clonedList);
    71. // target是CategoryVO对象,此时就是调用CategoryVO的setRelations方法,
    72. // 将克隆好的List<CategoryVO>给设置进去
    73. }
    74. return target;
    75. }
    76. /**
    77. * 将一个list克隆到另外一个list
    78. * @param sourceList
    79. * @param targetList
    80. * @param cloneTargetClazz
    81. * @param cloneDirection
    82. * @throws Exception
    83. */
    84. private void cloneList(List sourceList, List targetList,
    85. Class cloneTargetClazz, Integer cloneDirection) throws Exception {
    86. for(Object object : sourceList) {
    87. AbstractObject targetObject = (AbstractObject) object;
    88. // 将集合中的RelationDTO,调用其clone()方法,将其往RelationVO去克隆
    89. AbstractObject clonedObject = (AbstractObject) targetObject.clone(
    90. cloneTargetClazz, cloneDirection);
    91. // RelationVO的集合
    92. targetList.add(clonedObject);
    93. }
    94. }
    95. /**
    96. * 获取list集合的泛型类型
    97. * @param field
    98. * @return
    99. * @throws Exception
    100. */
    101. private Class<?> getListGenericType(Field field) throws Exception {
    102. // genericType = List<RelationDTO>,不是List
    103. Type genericType = field.getGenericType();
    104. // ParameterizedType是参数化类型
    105. if(genericType instanceof ParameterizedType){
    106. ParameterizedType parameterizedType = (ParameterizedType) genericType;
    107. //返回RelationDTO的Class对象
    108. return (Class<?>)parameterizedType.getActualTypeArguments()[0];
    109. }
    110. return null;
    111. }
    112. /**
    113. * 获取目标类名
    114. * @param cloneDirection
    115. * @return
    116. * @throws Exception
    117. */
    118. private Class<?> getCloneTargetClazz(Class<?> clazz,
    119. Integer cloneDirection) throws Exception {
    120. String cloneTargetClassName = null;
    121. // ReflectionDTO
    122. String className = clazz.getName();
    123. if(cloneDirection.equals(CloneDirection.FORWARD)) {
    124. if(className.endsWith(DomainType.VO)) {
    125. cloneTargetClassName = className.substring(0, className.length() - 2) + "DTO";
    126. } else if(className.endsWith(DomainType.DTO)) {
    127. cloneTargetClassName = className.substring(0, className.length() - 3) + "DO";
    128. }
    129. }
    130. if(cloneDirection.equals(CloneDirection.OPPOSITE)) {
    131. if(className.endsWith(DomainType.DO)) {
    132. cloneTargetClassName = className.substring(0, className.length() - 2) + "DTO";
    133. } else if(className.endsWith(DomainType.DTO)) {
    134. cloneTargetClassName = className.substring(0, className.length() - 3) + "VO";
    135. }
    136. }
    137. return Class.forName(cloneTargetClassName);
    138. }
    139. /**
    140. * 获取设置克隆好的list的方法名称
    141. * @return
    142. * @throws Exception
    143. */
    144. private Method getSetCloneListFieldMethodName(Field field, Class<?> clazz) throws Exception {
    145. String name = field.getName();
    146. String setMethodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
    147. Method setFieldMethod = null;
    148. for(Method method : clazz.getDeclaredMethods()) {
    149. if(method.getName().equals(setMethodName)) {
    150. setFieldMethod = method;
    151. break;
    152. }
    153. }
    154. return setFieldMethod;
    155. }
    156. }
    • 使用以上的方法优化后,之前的一坨get和set方法可以被优化成以下代码:
    1. /**
    2. * 根据id查询商品
    3. * @param id 商品id
    4. * @return 商品
    5. */
    6. @GetMapping("/{id}")
    7. public GoodsVO getById(@PathVariable("id") Long id) {
    8. try {
    9. //就一行
    10. return goodsService.getById(id).clone(GoodsVO.class);
    11. } catch (Exception e) {
    12. logger.error("error", e);
    13. return new GoodsVO();
    14. }
    15. }