- 首先回顾一下原型模式,使用它的目的是在不同层之间拷贝对象用的。例如将Service层的DTO拷贝到Controller的VO,如果无脑使用get,set方法,是一种很不优雅的实现。
- 在开发中,常用的拷贝Bean工具是Spring提供的
BeanUtils,它的缺点是使用了反射,性能较低。而Cglib提供的BeanCopier利用动态代理提升拷贝性能。 - 当我们直接使用
BeanCopier时,需要先根据被拷贝类和目标类的类型new出一个BeanCopier实例,然后再调用这个实例的拷贝方法。因为根据两个类型new出的实例是唯一的,所以如果在多处拷贝,那么只需要一个BeanCopier实例即可。例如想把A的属性拷贝到B,那么我们只需要一个全局唯一的,根据A.Class和B.Class创建出的,专门为A拷贝B的,BeanCopier实例。 - 那么就可以使用享元+单例(double-check)的思想,为每一对拷贝类去创建一个
BeanCopier实例。即,这两个类共享这个实例中的元配置。
public class BeanCopierUtils { /** * BeanCopier缓存 */ public static Map<String, BeanCopier> beanCopierCacheMap = new HashMap<String, BeanCopier>(); /** * 将source对象的属性拷贝到target对象中去 * @param source source对象 * @param target target对象 */ public static void copyProperties(Object source, Object target){ String cacheKey = source.getClass().toString() + target.getClass().toString(); BeanCopier beanCopier = null; // 线程1和线程2,同时过来了 if (!beanCopierCacheMap.containsKey(cacheKey)) { // 两个线程都卡这儿了 // 但是此时线程1先获取到了锁,线程2就等着 synchronized(BeanCopierUtils.class) { // 线程1进来之后,发现这里还是没有那个BeanCopier实例 // 此时线程2,会发现缓存map中已经有了那个BeanCopier实例了,此时就不会进入if判断内的代码 if(!beanCopierCacheMap.containsKey(cacheKey)) { // 进入到这里会创建一个BeanCopier实例并且放在缓存map中 beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false); beanCopierCacheMap.put(cacheKey, beanCopier); } else { beanCopier = beanCopierCacheMap.get(cacheKey); } } } else { beanCopier = beanCopierCacheMap.get(cacheKey); } beanCopier.copy(source, target, null); } }
- 无论使用Spring提供的
BeanUtils还是Cglib提供的BeanCopier,都可以把拷贝方法集成到Object的clone()方法中去。这里我们使用继承的思想,使用一个AbstractObject去封装我们自定义的Object自带的方法。然后让后续我们创建的所有Bean都继承这个AbstractObject。 - 拷贝的形式可以分为浅拷贝和深拷贝两种。前者只拷贝基础属性值,后者会递归地拷贝引用。考虑到一个POJO类中的属性,除了基础属性,最多再来个
List(Map类型也可以转换为List),所以在深拷贝中,只考虑对List的处理即可。 - 深度克隆侧重于拷贝当前对象中的
List<T>属性,这种拷贝是递归拷贝,也就是说,如果T中还有List,那么也会给T拷贝这个List。 - 深度克隆的实现方式使用了大量的反射和泛型操作,在注释上使用了
CategoryDTO作为例子,它包含一个List<RelationDto>。假设要将其克隆成CategoryVO和List<RelationVo>。 - 正向克隆指的是从前端到后端的POJO拷贝,也就是
VO->DTO->DO。反向是DO->DTO->VO。
@SuppressWarnings({ "rawtypes", "unchecked" })public class AbstractObject { /** * 浅度克隆 * @param clazz * @return * @throws Exception */ public <T> T clone(Class<T> clazz) throws Exception { T target = clazz.newInstance(); BeanCopierUtils.copyProperties(this, target); return target; } /** * 浅度克隆 * @return * @throws Exception */ public <T> T clone(T target) throws Exception { BeanCopierUtils.copyProperties(this, target); return target; } /** * 深度克隆 * @param clazz * @param cloneDirection 克隆方向: * @return * @throws Exception */ public <T> T clone(Class<T> clazz, Integer cloneDirection) throws Exception { /** * 先完成基本字段的浅克隆 */ T target = clazz.newInstance(); BeanCopierUtils.copyProperties(this, target); /** * 完成所有List类型的深度克隆 */ //1.获取被拷贝对象实例的Class对象,CategoryDTO Class<?> thisClazz = this.getClass(); //2.获取被拷贝对象实例的所有属性 Field[] fields = thisClazz.getDeclaredFields(); //3.遍历所有属性,对List类型的属性做深度拷贝 for(Field field : fields) { //将所有属性设置为可访问的 field.setAccessible(true); // 只关心List类型的属性 // field = private List<Relation> relations; if(field.getType() != List.class) { continue; } //获取依赖的List的属性值 // List<RelationDTO>集合 List<?> list = (List<?>) field.get(this); if(list == null || list.size() == 0) { continue; } // 获取List集合中的泛型类型 // RelationDTO Class<?> listGenericClazz = getListGenericType(field); // 获取要克隆的目标类型 // 假设CloneDirection是反向,此时获取到的就是RelationVO Class<?> cloneTargetClazz = getCloneTargetClazz(listGenericClazz, cloneDirection); // 将list集合克隆到目标list集合中去 List clonedList = new ArrayList(); cloneList(list, clonedList, cloneTargetClazz, cloneDirection); // 获取设置克隆好的list的方法名称 // setRelations Method setFieldMethod = getSetCloneListFieldMethodName(field, clazz); setFieldMethod.invoke(target, clonedList); // target是CategoryVO对象,此时就是调用CategoryVO的setRelations方法, // 将克隆好的List<CategoryVO>给设置进去 } return target; } /** * 将一个list克隆到另外一个list * @param sourceList * @param targetList * @param cloneTargetClazz * @param cloneDirection * @throws Exception */ private void cloneList(List sourceList, List targetList, Class cloneTargetClazz, Integer cloneDirection) throws Exception { for(Object object : sourceList) { AbstractObject targetObject = (AbstractObject) object; // 将集合中的RelationDTO,调用其clone()方法,将其往RelationVO去克隆 AbstractObject clonedObject = (AbstractObject) targetObject.clone( cloneTargetClazz, cloneDirection); // RelationVO的集合 targetList.add(clonedObject); } } /** * 获取list集合的泛型类型 * @param field * @return * @throws Exception */ private Class<?> getListGenericType(Field field) throws Exception { // genericType = List<RelationDTO>,不是List Type genericType = field.getGenericType(); // ParameterizedType是参数化类型 if(genericType instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) genericType; //返回RelationDTO的Class对象 return (Class<?>)parameterizedType.getActualTypeArguments()[0]; } return null; } /** * 获取目标类名 * @param cloneDirection * @return * @throws Exception */ private Class<?> getCloneTargetClazz(Class<?> clazz, Integer cloneDirection) throws Exception { String cloneTargetClassName = null; // ReflectionDTO String className = clazz.getName(); if(cloneDirection.equals(CloneDirection.FORWARD)) { if(className.endsWith(DomainType.VO)) { cloneTargetClassName = className.substring(0, className.length() - 2) + "DTO"; } else if(className.endsWith(DomainType.DTO)) { cloneTargetClassName = className.substring(0, className.length() - 3) + "DO"; } } if(cloneDirection.equals(CloneDirection.OPPOSITE)) { if(className.endsWith(DomainType.DO)) { cloneTargetClassName = className.substring(0, className.length() - 2) + "DTO"; } else if(className.endsWith(DomainType.DTO)) { cloneTargetClassName = className.substring(0, className.length() - 3) + "VO"; } } return Class.forName(cloneTargetClassName); } /** * 获取设置克隆好的list的方法名称 * @return * @throws Exception */ private Method getSetCloneListFieldMethodName(Field field, Class<?> clazz) throws Exception { String name = field.getName(); String setMethodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1); Method setFieldMethod = null; for(Method method : clazz.getDeclaredMethods()) { if(method.getName().equals(setMethodName)) { setFieldMethod = method; break; } } return setFieldMethod; }}
- 使用以上的方法优化后,之前的一坨get和set方法可以被优化成以下代码:
/** * 根据id查询商品 * @param id 商品id * @return 商品 */ @GetMapping("/{id}") public GoodsVO getById(@PathVariable("id") Long id) { try { //就一行 return goodsService.getById(id).clone(GoodsVO.class); } catch (Exception e) { logger.error("error", e); return new GoodsVO(); } }