选择Cglib的BeanCopier进行Bean拷贝的理由是, 其性能要比Spring的BeanUtils,Apache的BeanUtils和PropertyUtils要好很多, 尤其是数据量比较大的情况下。
package cn.dearth.common.utils; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.cglib.beans.BeanCopier; import org.springframework.cglib.core.Converter; import java.math.BigDecimal; import java.sql.Timestamp; import java.text.SimpleDateFormat; import static org.junit.jupiter.api.Assertions.*; class BeanCopyUtilsTest { /** * 测试说明: 类型不同BeanCopier不会拷贝 */ @Test void copy() { User user = new User(); user.setId(1); user.setName("dearth"); BeanCopier copier = BeanCopier.create(User.class, UserVo.class, false); UserVo userVo = new UserVo(); copier.copy(user, userVo, null); Assertions.assertNull(userVo.getId()); } /** * 测试说明: 提供转换器可以实现不同类型的拷贝 */ @Test void copyUseConverter() { User user = new User(); user.setId(1); user.setName("dearth"); user.setCreateTime(new Timestamp(10043143243L)); user.setBalance(BigDecimal.valueOf(4000L)); BeanCopier copier = BeanCopier.create(User.class, UserVo.class, true); UserVo vo = new UserVo(); copier.copy(user, vo, new UserConverter()); Assertions.assertNotNull(vo.getCreateTime()); Assertions.assertNotNull(vo.getBalance()); } @Data @NoArgsConstructor @AllArgsConstructor static class User { private String name; private int id; private Timestamp createTime; private BigDecimal balance; } @Data @NoArgsConstructor @AllArgsConstructor static class UserVo { private String name; private Integer id; private String createTime; private String balance; } static class UserConverter implements Converter { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public Object convert(Object o, Class aClass, Object o1) { if (o instanceof Integer) { return (Integer) o; } else if (o instanceof Timestamp) { Timestamp data = (Timestamp) o; return sdf.format(data); } else if (o instanceof BigDecimal) { BigDecimal bg = (BigDecimal) o; return bg.toPlainString(); } else { return null; } } } }
总结
- BeanCopier只拷贝名称和类型都相同的属性。
- 当目标类的setter数目比getter少时,创建BeanCopier会失败而导致拷贝不成功。
- 自定义Converter转换器可以转换不同类型的属性
工具类
package cn.dearth.common.utils; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.SimpleCache; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.springframework.cglib.beans.BeanCopier; import org.springframework.cglib.core.Converter; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class BeanCopyUtils { /** * 单对象基于对象创建拷贝 * * @param source 数据来源实体 * @param desc 转换后的对象 * @return desc */ public static <T, V> V copy(T source, V desc) { if (ObjectUtil.isNull(source)) { return null; } if (ObjectUtil.isNull(desc)) { return null; } BeanCopier beanCopier = BeanCopierCache.INSTANCE.get(source.getClass(), desc.getClass(), null); beanCopier.copy(source, desc, null); return desc; } /** * 单对象基于class创建拷贝 * * @param source 数据来源实体 * @param desc 描述对象 转换后的对象 * @return desc */ public static <T, V> V copy(T source, Class<V> desc) { if (ObjectUtil.isNull(source)) { return null; } if (ObjectUtil.isNull(desc)) { return null; } final V target = ReflectUtil.newInstanceIfPossible(desc); return copy(source, target); } /** * 列表对象基于class创建拷贝 * * @param sourceList 数据来源实体列表 * @param desc 描述对象 转换后的对象 * @return desc */ public static <T, V> List<V> copyList(List<T> sourceList, Class<V> desc) { if (ObjectUtil.isNull(sourceList)) { return null; } if (CollUtil.isEmpty(sourceList)) { return CollUtil.newArrayList(); } return sourceList.stream() .map(source -> { V target = ReflectUtil.newInstanceIfPossible(desc); copy(source, target); return target; }).collect(Collectors.toList()); } public enum BeanCopierCache { INSTANCE; private final SimpleCache<String, BeanCopier> cache = new SimpleCache<>(); public BeanCopier get(Class<?> sourceClass, Class<?> targetClass, Converter converter) { final String key = genKey(sourceClass, targetClass, converter); return cache.get(key, () -> BeanCopier.create(sourceClass, targetClass, converter != null)); } private String genKey(Class<?> sourceClass, Class<?> targetClass, Converter converter) { StringBuilder key = StrUtil.builder() .append(sourceClass.getName()) .append('#') .append(targetClass.getName()); Optional.ofNullable(converter) .ifPresent(c -> key.append(c.getClass().getName())); return key.toString(); } } }