精华内容
下载资源
问答
  • java 属性拷贝

    千次阅读 2014-07-17 18:01:50
    在实际的应用中,总会有这样的情况:两个不同的bean对象,但是其属性字段是完全一一样的,有些应用是需要相互之间拷贝属性值得, 下面就贴个实用的copyutil类,实现属性拷贝工作。 直接看代码:  ...

    在实际的应用中,总会有这样的情况:两个不同的bean对象,但是其属性字段是完全一一样的,有些应用是需要相互之间拷贝属性值得,

    下面就贴个实用的copyutil类,实现属性的拷贝工作。

    直接看代码:

        public static void main(String[] args) {
            
            BaseInfo source = new BaseInfo();
            source.setId("20");
            source.setCateId(1);
            source.setName("dada");
            source.setImgUrl("asdfsdfsad");
            
            BaseInfo target = new BaseInfo();
            target.setId("40");
            target.setCateId(2);
            target.setName("dadadada");        
            target.setStatus(15);
            target.setOperator("88");
            target.setIsDel(12);
            
            //copyPropertiesO(target, source);
            BaseInfo info = PersonaeBeanUtils.comBaseInfo(target, source);
            
            //结果.
            System.out.println(info.getId()+" , "+info.getCateId()+","+info.getName()+","+info.getImgUrl()+","+info.getStatus()+","+info.getOperator()+","+info.getIsDel());
            //System.out.println(source.getId()+" , "+source.getCateId()+","+source.getName()+","+source.getImgUrl()+","+source.getStatus()+","+source.getOperator()+","+source.getIsDel());
        }
        
        /**
         * 拷贝基础信息属性.
         * 通过使用spring 的beanutils对象拷贝
         * @param souceInfo:源头
         * @param targetInfo:目的
         * @return
         */
        public static Object comPersonalInfo(Object souceInfo , Object targetInfo) {
            Object info = null;
            try {
                if (souceInfo==null && targetInfo!=null) {
                    info = targetInfo;
                }
                if (targetInfo==null && souceInfo!=null) {
                    info =  souceInfo;
                }
                if (souceInfo!=null && targetInfo!=null) {
                    info =  new PersonalInfo();
                    BeanUtils.copyProperties(souceInfo, info);
                    PersonalInfo tempInfo = new PersonalInfo();
                    BeanUtils.copyProperties(targetInfo, tempInfo);
                    PersonalInfoUtil.copyProperties(info, tempInfo);
                    BeanUtils.copyProperties(tempInfo, info);
                }
            } catch(Exception e) {
                return null;
            }
            return info;
        }
        
        /**
         * 没有使用get,set方法
         * @param source
         * @param target
         */
        public static void copyPropertiesO(Object source,Object target) {
            try {
                Assert.notNull(source, "Source must not be null");
                Assert.notNull(target, "Target must not be null");
                Field[] source_fields = source.getClass().getDeclaredFields();
                for (Field field : source_fields) {
                    String fieldName = field.getName();
                    PropertyDescriptor sourcePd = new PropertyDescriptor(fieldName, source.getClass());
                    PropertyDescriptor targetPd = new PropertyDescriptor(fieldName, target.getClass());
                    if(sourcePd == null || sourcePd.getReadMethod() == null){
                        continue;
                    }
                    if(targetPd == null || targetPd.getWriteMethod() == null){
                        continue;
                    }
                    Method readMethod = sourcePd.getReadMethod();
                    if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())){
                        readMethod.setAccessible(true);
                    }
                    Class<?> type =  source.getClass().getDeclaredField(fieldName).getType();
                    Object value = readMethod.invoke(source, new Object[0]);
                    if ((type == String.class && StringUtils.isNotBlank((String)value)) || (type != String.class && value != null)) {
                        Method writeMethod = targetPd.getWriteMethod();
                        if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())){
                            writeMethod.setAccessible(true);
                        }
                        writeMethod.invoke(target, new Object[] { value });
                    }
                }
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
        
        
        
        /**
         * 利用反射实现对象之间属性复制
         * @param from
         * @param to
         */
        public static void copyProperties(Object from, Object to){
            try {
                copyPropertiesExclude(from, to, null);
            } catch (Exception e) {
            }
        }
        
        /**
         * 复制对象属性:使用了get ,set 方法.
         * @param from
         * @param to
         * @param excludsArray 排除属性列表
         * @throws Exception
         */
        @SuppressWarnings("unchecked")
        public static void copyPropertiesExclude(Object from, Object to, String[] excludsArray) throws Exception {
            List<String> excludesList = null;
            if(excludsArray != null && excludsArray.length > 0) {
                //构造列表对象
                excludesList = Arrays.asList(excludsArray);    
            }
            
            Method[] fromMethods = from.getClass().getDeclaredMethods();
            Method[] toMethods = to.getClass().getDeclaredMethods();
            Method fromMethod = null;
            Method toMethod = null;
            String fromMethodName = null;
            String toMethodName = null;
            
            for (int i = 0; i < fromMethods.length; i++) {
                fromMethod = fromMethods[i];
                fromMethodName = fromMethod.getName();
                if (!fromMethodName.contains("get")){
                    continue;
                }
                //排除列表检测
                if(excludesList != null && excludesList.contains(fromMethodName.substring(3).toLowerCase())) {
                    continue;
                }
                toMethodName = "set" + fromMethodName.substring(3);
                toMethod = findMethodByName(toMethods, toMethodName);
                if (toMethod == null){
                    continue;
                }
                Object value = fromMethod.invoke(from, new Object[0]);
                if(value == null){
                    continue;
                }
                //集合类判空处理
                if(value instanceof Collection) {
                    Collection newValue = (Collection)value;
                    if(newValue.size() <= 0){
                        continue;
                    }
                }
                toMethod.invoke(to, new Object[] {value});
            }
        }
        
        /**
         * 对象属性值复制,仅复制指定名称的属性值
         * @param from
         * @param to
         * @param includsArray
         * @throws Exception
         */
        @SuppressWarnings("unchecked")
        public static void copyPropertiesInclude(Object from, Object to, String[] includsArray) throws Exception {
            List<String> includesList = null;
            if(includsArray != null && includsArray.length > 0) {
                includesList = Arrays.asList(includsArray);    //构造列表对象
            } else {
                return;
            }
            Method[] fromMethods = from.getClass().getDeclaredMethods();
            Method[] toMethods = to.getClass().getDeclaredMethods();
            Method fromMethod = null, toMethod = null;
            String fromMethodName = null, toMethodName = null;
            for (int i = 0; i < fromMethods.length; i++) {
                fromMethod = fromMethods[i];
                fromMethodName = fromMethod.getName();
                if (!fromMethodName.contains("get")){
                    continue;
                }
                //排除列表检测
                String str = fromMethodName.substring(3);
                if(!includesList.contains(str.substring(0,1).toLowerCase() + str.substring(1))) {
                    continue;
                }
                toMethodName = "set" + fromMethodName.substring(3);
                toMethod = findMethodByName(toMethods, toMethodName);
                if (toMethod == null){
                    continue;
                }
                Object value = fromMethod.invoke(from, new Object[0]);
                if(value == null){
                    continue;
                }
                //集合类判空处理
                if(value instanceof Collection) {
                    Collection newValue = (Collection)value;
                    if(newValue.size() <= 0){
                        continue;
                    }
                }
                toMethod.invoke(to, new Object[] {value});
            }
        }
        
        

        /**
         * 从方法数组中获取指定名称的方法
         *
         * @param methods
         * @param name
         * @return
         */
        public static Method findMethodByName(Method[] methods, String name) {
            for (int j = 0; j < methods.length; j++) {
                if (methods[j].getName().equals(name)){
                    return methods[j];
                }
            }
            return null;
        }
        
    代码上比较的乱,但是一定可以使用!

    瞎写写,记点东西,是点东西!!!

    展开全文
  • 最近在写项目的过程中,由于分层关系,经常会进行对象间的属性拷贝。但是源对象有空属性的又不想拷贝,不管是Apache的还是springframework的都会直接将空值也赋值过去。所以自己写了一个工具来实现,仅供参考: ...

      最近在写项目的过程中,由于分层关系,经常会进行对象间的属性拷贝。但是源对象有空属性的又不想拷贝,不管是Apache的还是springframework的都会直接将空值也赋值过去。所以自己写了一个工具来实现,仅供参考:

    public class BeanUtil {
    
        /**
         * source属性为空的不赋值给target
         * @param source
         * @param target
         */
        public static void beanCopierNotNull(Object source,Object target){
            CopyConverter copyConverter = new CopyConverter(target);
            BeanCopier copier = BeanCopier.create(source.getClass(),target.getClass(),true);
            copier.copy(source,target,copyConverter);
        }
    }
    
    import org.apache.commons.lang.StringUtils;
    import org.apache.commons.lang.exception.ExceptionUtils;
    import org.apache.commons.lang.reflect.FieldUtils;
    import org.springframework.cglib.core.Converter;
    import org.springframework.util.ObjectUtils;
    
    /**
     * @author zhangweiqiang
     * @Description
     */
    public class CopyConverter implements Converter {
        private static Log log = LogFactory.getLog(CopyConverter.class);
    
        private Object targetObject;
    
        @Override
        public Object convert(Object value, Class targetClass, Object context) {
            try {
                if (ObjectUtils.isEmpty(value)) {
                    String name = StringUtils.substring(ObjectUtils.nullSafeToString(context), 3);
                    String fiedName = StringUtils.uncapitalize(name);
                    return FieldUtils.readField(targetObject, fiedName, true);
                }
    
            } catch (Exception e) {
                log.debug(ExceptionUtils.getFullStackTrace(e));
            }
    
            return value;
        }
    
        public CopyConverter(Object targetObject) {
            this.targetObject = targetObject;
        }
    }
    

     

    展开全文
  • 在做业务的时候,为了隔离变化,我们会将DAO查询出来的DO和对前端提供的...大多时候时候使用的是Apache或Spring`BeanUtils,今天,我们来看一下一个更高效的属性拷贝方式:BeanCopier`。一、背景1.1 对象拷贝概念J...

    在做业务的时候,为了隔离变化,我们会将DAO查询出来的DO和对前端提供的DTO隔离开来。大概90%的时候,它们的结构都是类似的;但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代码,于是我们需要简化对象拷贝方式。

    大多时候时候使用的是Apache或Spring`BeanUtils,今天,我们来看一下一个更高效的属性拷贝方式:BeanCopier`。

    一、背景

    1.1 对象拷贝概念

    Java中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。

    对象拷贝分为浅拷贝(浅克隆)与深拷贝(深克隆)。

    浅拷贝与深拷贝差异

    分类

    浅拷贝

    深拷贝

    区别

    创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

    创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。

    1.2 示例前准备

    源对象属性类UserDO.class(以下示例,源对象都用这个)

    @Data

    public class UserDO {

    private int id;

    private String userName;

    /**

    * 以下两个字段用户模拟自定义转换

    */

    private LocalDateTime gmtBroth;

    private BigDecimal balance;

    public UserDO(Integer id, String userName, LocalDateTime gmtBroth, BigDecimal balance) {

    this.id = id;

    this.userName = userName;

    this.gmtBroth = gmtBroth;

    this.balance = balance;

    }

    }

    造数据工具类DataUtil.class

    public class DataUtil {

    /**

    * 模拟查询出一条数据

    * @return

    */

    public static UserDO createData() {

    return new UserDO(1, "Van", LocalDateTime.now(),new BigDecimal(100L));

    }

    /**

    * 模拟查询出多条数据

    * @param num 数量

    * @return

    */

    public static List createDataList(int num) {

    List userDOS = new ArrayList<>();

    for (int i = 0; i < num; i++) {

    UserDO userDO = new UserDO(i+1, "Van", LocalDateTime.now(),new BigDecimal(100L));

    userDOS.add(userDO);

    }

    return userDOS;

    }

    }

    二、对象拷贝之BeanUtils

    Apache和Spring均有BeanUtils工具类, Apache的BeanUtils稳定性与效率都不行;Spring的BeanUtils比较稳定,不会因为量大了,耗时明显增加,故一般都使用Spring的BeanUtils。

    2.1 源码解读

    Spring中的BeanUtils,其中实现的方式很简单,就是对两个对象中相同名字的属性进行简单get/set,仅检查属性的可访问性。

    可以看到, 成员变量赋值是基于目标对象的成员列表, 并且会跳过ignore的以及在源对象中不存在的, 所以这个方法是安全的, 不会因为两个对象之间的结构差异导致错误, 但是必须保证同名的两个成员变量类型相同。

    2.2 示例

    @Slf4j

    public class BeanUtilsDemo {

    public static void main(String[] args) {

    long start = System.currentTimeMillis();

    UserDO userDO = DataUtil.createData();

    log.info("拷贝前,userDO:{}", userDO);

    UserDTO userDTO = new UserDTO();

    BeanUtils.copyProperties(userDO,userDTO);

    log.info("拷贝后,userDO:{}", userDO);

    }

    }

    结果

    18:12:11.734 [main] INFO cn.van.parameter.bean.copy.demo.BeanUtilsDemo - 拷贝前,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T18:12:11.730, balance=100)

    18:12:11.917 [main] INFO cn.van.parameter.bean.copy.demo.BeanUtilsDemo - 拷贝后,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T18:12:11.730, balance=100)

    三、对象拷贝之BeanCopier

    BeanCopier是用于在两个bean之间进行属性拷贝的。BeanCopier支持两种方式:

    一种是不使用Converter的方式,仅对两个bean间属性名和类型完全相同的变量进行拷贝;

    另一种则引入Converter,可以对某些特定属性值进行特殊操作。

    3.1 基本使用

    依赖

    cglib

    cglib-nodep

    3.3.0

    注意:该依赖非必须,因为Spring中已经集成了cglib,博主使用的就是org.springframework.cglib.beans.BeanCopier。

    3.1.1 属性名称、类型都相同

    目标对象属性类

    @Data

    public class UserDTO {

    private int id;

    private String userName;

    }

    测试方法

    /**

    * 属性名称、类型都相同(部分属性不拷贝)

    */

    private static void normalCopy() {

    // 模拟查询出数据

    UserDO userDO = DataUtil.createData();

    log.info("拷贝前:userDO:{}", userDO);

    // 第一个参数:源对象, 第二个参数:目标对象,第三个参数:是否使用自定义转换器(下面会介绍),下同

    BeanCopier b = BeanCopier.create(UserDO.class, UserDTO.class, false);

    UserDTO userDTO = new UserDTO();

    b.copy(userDO, userDTO, null);

    log.info("拷贝后:userDTO:{}", userDTO);

    }

    结果:拷贝成功

    18:24:24.080 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝前:userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T18:24:24.077, balance=100)

    18:24:24.200 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝后:userDTO:UserDTO(id=1, userName=Van)

    3.1.2 属性名称相同、类型不同

    目标对象属性类

    @Data

    public class UserEntity {

    private Integer id;

    private String userName;

    }

    测试方法

    /**

    * 属性名称相同、类型不同

    */

    private static void sameNameDifferentType() {

    // 模拟查询出数据

    UserDO userDO = DataUtil.createData();

    log.info("拷贝前:userDO:{}", userDO);

    BeanCopier b = BeanCopier.create(UserDO.class, UserEntity.class, false);

    UserEntity userEntity = new UserEntity();

    b.copy(userDO, userEntity, null);

    log.info("拷贝后:userEntity:{}", userEntity);

    }

    结果

    19:43:31.645 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝前:userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T19:43:31.642, balance=100)

    19:43:31.748 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝后:userEntity:UserEntity(id=null, userName=Van)

    分析

    通过日志可以发现:UserDO的int类型的id无法拷贝到UserEntity的Integer的id。

    3.1.3 小节

    BeanCopier只拷贝名称和类型都相同的属性。

    即使源类型是原始类型(int, short和char等),目标类型是其包装类型(Integer, Short和Character等),或反之:都不会被拷贝。

    3.2 自定义转换器

    通过3.1.2可知,当源和目标类的属性类型不同时,不能拷贝该属性,此时我们可以通过实现Converter接口来自定义转换器

    3.2.1 准备

    目标对象属性类

    @Data

    public class UserDomain {

    private Integer id;

    private String userName;

    /**

    * 以下两个字段用户模拟自定义转换

    */

    private String gmtBroth;

    private String balance;

    }

    3.2.2 不使用Converter

    测试方法

    /**

    * 类型不同,不使用Converter

    */

    public static void noConverterTest() {

    // 模拟查询出数据

    UserDO userDO = DataUtil.createData();

    log.info("拷贝前:userDO:{}", userDO);

    BeanCopier copier = BeanCopier.create(UserDO.class, UserDomain.class, false);

    UserDomain userDomain = new UserDomain();

    copier.copy(userDO, userDomain, null);

    log.info("拷贝后:userDomain:{}", userDomain);

    }

    结果

    19:49:19.294 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝前:userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T19:49:19.290, balance=100)

    19:49:19.394 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝后:userDomain:UserDomain(id=null, userName=Van, gmtBroth=null, balance=null)

    分析

    通过打印日志的前后对比,属性类型不同的字段id,gmtBroth,balance未拷贝。

    3.2.3 使用Converter

    实现Converter接口来自定义属性转换

    public class UserConverter implements Converter {

    /**

    * 时间转换的格式

    */

    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**

    * 自定义属性转换

    * @param value 源对象属性类

    * @param target 目标对象里属性对应set方法名,eg.setId

    * @param context 目标对象属性类

    * @return

    */

    @Override

    public Object convert(Object value, Class target, Object context) {

    if (value instanceof Integer) {

    return value;

    } else if (value instanceof LocalDateTime) {

    LocalDateTime date = (LocalDateTime) value;

    return dtf.format(date);

    } else if (value instanceof BigDecimal) {

    BigDecimal bd = (BigDecimal) value;

    return bd.toPlainString();

    }

    // 更多类型转换请自定义

    return value;

    }

    }

    测试方法

    /**

    * 类型不同,使用Converter

    */

    public static void converterTest() {

    // 模拟查询出数据

    UserDO userDO = DataUtil.createData();

    log.info("拷贝前:userDO:{}", userDO);

    BeanCopier copier = BeanCopier.create(UserDO.class, UserDomain.class, true);

    UserConverter converter = new UserConverter();

    UserDomain userDomain = new UserDomain();

    copier.copy(userDO, userDomain, converter);

    log.info("拷贝后:userDomain:{}", userDomain);

    }

    结果:全部拷贝

    19:51:11.989 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝前:userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T19:51:11.985, balance=100)

    19:51:12.096 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝后:userDomain:UserDomain(id=1, userName=Van, gmtBroth=2019-11-02 19:51:11, balance=100)

    3.2.4 小节

    一旦使用Converter,BeanCopier只使用Converter定义的规则去拷贝属性,所以在convert()方法中要考虑所有的属性。

    但,使用Converter会使对象拷贝速度变慢。

    3.3 BeanCopier总结

    当源类和目标类的属性名称、类型都相同,拷贝没问题。

    当源对象和目标对象的属性名称相同、类型不同,那么名称相同而类型不同的属性不会被拷贝。注意,原始类型(int,short,char)和 他们的包装类型,在这里都被当成了不同类型,因此不会被拷贝。

    源类或目标类的setter比getter少,拷贝没问题,此时setter多余,但是不会报错。

    源类和目标类有相同的属性(两者的getter都存在),但是目标类的setter不存在,此时会抛出NullPointerException。

    四、BeanUtils与BeanCopier速度对比

    废话不多说,我这里直接演示两种工具10000条数据拷贝的耗时对比

    4.1 BeanUtils

    测试代码

    private static void beanUtil() {

    List list = DataUtil.createDataList(10000);

    long start = System.currentTimeMillis();

    List dtoList = new ArrayList<>();

    list.forEach(userDO -> {

    UserDTO userDTO = new UserDTO();

    BeanUtils.copyProperties(userDO,userDTO);

    dtoList.add(userDTO);

    });

    log.info("BeanUtils cotTime: {}ms", System.currentTimeMillis() - start);

    }

    结果(耗时:232ms)

    20:14:24.380 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopyComparedDemo - BeanUtils cotTime: 232ms

    4.2 BeanCopier

    测试代码

    private static void beanCopier() {

    // 工具类生成10w条数据

    List doList = DataUtil.createDataList(10000);

    long start = System.currentTimeMillis();

    List dtoList = new ArrayList<>();

    doList.forEach(userDO -> {

    BeanCopier b = BeanCopier.create(UserDO.class, UserDTO.class, false);

    UserDTO userDTO = new UserDTO();

    b.copy(userDO, userDTO, null);

    dtoList.add(userDTO);

    });

    log.info("BeanCopier costTime: {}ms", System.currentTimeMillis() - start);

    }

    结果(耗时:116ms)

    20:15:24.380 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopyComparedDemo - BeanCopier costTime: 116ms

    4.3 缓存BeanCopier实例提升性能

    BeanCopier拷贝速度快,性能瓶颈出现在创建BeanCopier实例的过程中。 所以,把创建过的BeanCopier实例放到缓存中,下次可以直接获取,提升性能。

    测试代码

    private static void beanCopierWithCache() {

    List userDOList = DataUtil.createDataList(10000);

    long start = System.currentTimeMillis();

    List userDTOS = new ArrayList<>();

    userDOList.forEach(userDO -> {

    UserDTO userDTO = new UserDTO();

    copy(userDO, userDTO);

    userDTOS.add(userDTO);

    });

    log.info("BeanCopier 加缓存后 costTime: {}ms", System.currentTimeMillis() - start);

    }

    public static void copy(Object srcObj, Object destObj) {

    String key = genKey(srcObj.getClass(), destObj.getClass());

    BeanCopier copier = null;

    if (!BEAN_COPIERS.containsKey(key)) {

    copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);

    BEAN_COPIERS.put(key, copier);

    } else {

    copier = BEAN_COPIERS.get(key);

    }

    copier.copy(srcObj, destObj, null);

    }

    private static String genKey(Class> srcClazz, Class> destClazz) {

    return srcClazz.getName() + destClazz.getName();

    }

    结果(耗时:6ms)

    20:32:31.405 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopyComparedDemo - BeanCopier 加缓存后 costTime: 6ms

    五、总结及源码

    场景

    耗时(10000次调用)

    原理

    BeanUtils

    232ms

    反射

    BeanCopier

    116ms

    修改字节码

    BeanCopier(加缓存)

    6ms

    修改字节码

    技术交流

    关注公众号,了解更多:

    展开全文
  • 从一个实例中拷贝属性值到另外一个实例(可以是相同class或不同class) 对于写业务代码同学来说,确实很“省”代码。员外近仨月就节省了很多代码。 例如,历史文章中有一篇,就是自己为了偷懒,写的一个工具方法:...

    写业务代码的同学,一定经常使用一个API:

    org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object)
    

    从一个实例中拷贝属性值到另外一个实例(可以是相同class或不同class)
    对于写业务代码同学来说,确实很“省”代码。员外近仨月就节省了很多代码。
    例如,历史文章中有一篇,就是自己为了偷懒,写的一个工具方法:传送门

    昨天review同事的一段代码(压测报告响应时间长),业务逻辑非常简单:数据库查询数据(POJO List),然后copyProperties到VO List。也就是数据量会大一些(目前返回测试数据千条级别,企业级应用,单条数据的字段值也蛮多)。同事采用的是序列化与反序列化的方式进行属性copy的。

    既然业务逻辑(不好意思称之为算法)简单,为什么会"慢"呢?
    自己能想到对象(属性值)copy的方式有三种:
    ①:基于ObjectMapper做序列化与反序列化
    source和target field大面积相同,例如一个dto对应的vo。

    public class BeanUtils {
        private static Logger logger = LogManager.getLogger(CollectionsUtils.class);
        private static ObjectMapper objectMapper = new ObjectMapper();
    
        /**
         * 从类实例中copy属性值
         * 并返回一个指定类的实例作为返回值
         * 基于{@link ObjectMapper}进行序列化与反序列化
         *
         * @param source
         * @param type
         * @param <T>
         * @return
         * @throws IOException
         */
        public static <T, E> T copyProperties(E source, Class<T> type) {
            String jsonString = null;
            try {
                jsonString = objectMapper.writeValueAsString(source);
                return objectMapper.readValue(jsonString, type);
            } catch (JsonProcessingException e) {
                logger.error(e.getMessage(), e);
                throw new RuntimeException();
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
                throw new RuntimeException();
            }
        }
    }
    

    ②Spring中的接口:

    org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object)
    

    当然还有其它API,不做列举
    ③Getter/Setter方法使用

    于是想着对三种方式进行实践测试:

    public class User implements Cloneable{
        private String name;
        private String sex;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", sex='" + sex + '\'' +
                    '}';
        }
    
    }
    
    public class UserMirror implements Cloneable{
        private String name;
        private String sex;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", sex='" + sex + '\'' +
                    '}';
        }
    
    }
    
    public class BeanUtilsTest {
        private static final int count = 1000000;
    
        @Test
        public void copyProperties() throws IOException {
            System.out.println(String.format("拷贝数据:%d条", count));
            this.copyBySerialize();
            this.copyByReflex();
            this.copyByMethod();
        }
    
        private List<UserMirror> copyBySerialize() throws IOException {
            long begin = System.currentTimeMillis();
            User user = new User();
            user.setName("Young");
            user.setSex("male");
            List<UserMirror> list = new ArrayList<>(count);
            for (int index = 0; index < count; index++) {
                list.add(BeanUtils.copyProperties(user, UserMirror.class));
            }
            long end = System.currentTimeMillis();
            System.out.println(String.format("序列化反序列化方式耗时:%d millis", end - begin));
            return list;
        }
    
        private List<UserMirror> copyByReflex() throws IOException {
            long begin = System.currentTimeMillis();
            User user = new User();
            user.setName("Young");
            user.setSex("male");
            List<UserMirror> list = new ArrayList<>(count);
            for (int index = 0; index < count; index++) {
                UserMirror userMirror = new UserMirror();
                org.springframework.beans.BeanUtils.copyProperties(user, userMirror);
                list.add(userMirror);
            }
            long end = System.currentTimeMillis();
            System.out.println(String.format("反射方式耗时:%d millis", end - begin));
            return list;
        }
    
        /**
         * Getter/Setter
         *
         * @return
         * @throws IOException
         */
        private List<UserMirror> copyByMethod(){
            long begin = System.currentTimeMillis();
            User user = new User();
            user.setName("Young");
            user.setSex("male");
            List<UserMirror> list = new ArrayList<>(count);
            for (int index = 0; index < count; index++) {
                UserMirror userMirror = new UserMirror();
                userMirror.setName(user.getName());
                userMirror.setSex(user.getSex());
                list.add(userMirror);
            }
            long end = System.currentTimeMillis();
            System.out.println(String.format("Getter/Setter方式耗时:%d millis", end - begin));
            return list;
        }
    }
    

    然后来看看测试结果:
    在这里插入图片描述
    然后用数据来说说化:
    从效率来看③优于②优于①,思考一下原因:
    ①为什么最慢?看看方法,细心点可以发现有一个IOException异常处理,因为序列化与反序列化是需要涉及到IO开销的。
    ②看看API的源码:其实质还是利用反射来调用Getter/Setter方法

    /**
    	 * Copy the property values of the given source bean into the given target bean.
    	 * <p>Note: The source and target classes do not have to match or even be derived
    	 * from each other, as long as the properties match. Any bean properties that the
    	 * source bean exposes but the target bean does not will silently be ignored.
    	 * @param source the source bean
    	 * @param target the target bean
    	 * @param editable the class (or interface) to restrict property setting to
    	 * @param ignoreProperties array of property names to ignore
    	 * @throws BeansException if the copying failed
    	 * @see BeanWrapper
    	 */
    	private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
    			@Nullable String... ignoreProperties) throws BeansException {
    
    		Assert.notNull(source, "Source must not be null");
    		Assert.notNull(target, "Target must not be null");
    
    		Class<?> actualEditable = target.getClass();
    		if (editable != null) {
    			if (!editable.isInstance(target)) {
    				throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
    						"] not assignable to Editable class [" + editable.getName() + "]");
    			}
    			actualEditable = editable;
    		}
    		PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
    		List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
    
    		for (PropertyDescriptor targetPd : targetPds) {
    			Method writeMethod = targetPd.getWriteMethod();
    			if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
    				PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
    				if (sourcePd != null) {
    					Method readMethod = sourcePd.getReadMethod();
    					if (readMethod != null &&
    							ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
    						try {
    							if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
    								readMethod.setAccessible(true);
    							}
    							Object value = readMethod.invoke(source);
    							if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
    								writeMethod.setAccessible(true);
    							}
    							writeMethod.invoke(target, value);
    						}
    						catch (Throwable ex) {
    							throw new FatalBeanException(
    									"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
    						}
    					}
    				}
    			}
    		}
    	}
    

    ③虽然代码量看起来多一点,可实质上就是方式②的一个详细解释。

    当然呢,①和②更具通用性,③的效率就高一点。那么当一个产品的功能十分稳定以后,想要进行效率上的提升,那么①②替换为③,不失为一个思路。

    总结一下:大道至简

    展开全文
  • java 属性拷贝工具类

    千次阅读 2015-06-10 11:08:16
    import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.List;... * 属性拷贝的 工具类 * @author zhangtengda * @version 1.0 * @creat
  • java对象属性拷贝

    2020-07-18 18:15:00
    对象属性拷贝 1、这四种对象属性拷贝方式,你都知道吗? 示例代码 该部分测试代码 完整代码地址 2、领域模型转换那些事儿 3、Java Bean Copy 性能大比拼、bean 拷贝工具 ...
  • 文章目录开头聊几句Java属性拷贝工具类使用总结字段和属性使用说明**org.springframework.beans.BeanUtils#copyProperties**org.apache.commons.beanutils.PropertyUtils#_copyProperties_org.apache.commons....
  • 使用org.springframework.beans.BeanUtils.copyProperties方法进行...copyProperties(Object source, Object target) 对象属性拷贝,将源对象的属性拷贝到目标对象。public User addUser(UserInputDTO userInputDT...
  • java对象属性拷贝,不拷贝空值

    千次阅读 2017-01-18 10:03:22
    java对象拷贝,不拷贝空值,对于数据库更新插入操作比较好用!
  • Java规范 对象属性拷贝 常见对象属性拷贝工具 1、Spring BeanUtils:效率其次 2、Cglib BeanCopier:效率最高 3、Apache BeanUtils:效率低,原因Apache BeanUtils力求做的完美,做了很多校验,兼容,日志打印等导致...
  • Java常用属性拷贝工具类使用总结对项目中经常使用的属性拷贝工具类进行总结:org.apache.commons.beanutils.BeanUtilsorg.apache.commons.beanutils.PropertyUtilsorg.springframework.beans.BeanUtils本文使用的...
  • 前言​ Java语言的一个优点就是取消了指针的概念,但也导致了许多程序员在编程中常常忽略了对象与引用的区别,本文会试图澄清这一概念。并且由于Java不能 通过简单的赋值来解决对象复制的问题,在开发过程中,也常常...
  • JavaBean 对象之间属性拷贝方法: 方法如下: 切记:赋值操作仅支持两个对象属性相同的情况 BeanUtils.copyProperties(A, B); 把对象A 的值 赋值给B 代码示例如下: 两个不同的包,两种不同的使用方式 package ...
  • Java常用属性拷贝工具类使用总结对项目中经常使用的属性拷贝工具类进行总结:org.apache.commons.beanutils.BeanUtilsorg.apache.commons.beanutils.PropertyUtilsorg.springframework.beans.BeanUtils本文使用的...
  • 使用org.springframework.beans.BeanUtils.copyProperties方法进行...copyProperties(Object source, Object target) 对象属性拷贝,将源对象的属性拷贝到目标对象。public User addUser(UserInputDTO userInputDT...
  • 说到对象属性拷贝作者脑海中第一反应就是spring和apatch的beanUtils以及cglib的beanCopier,前者的实现原理是利用java的反射,后者是加了动态代理提高拷贝速度。下面是作者利用java反射实现的一个极简单的例子,仅供...
  • java bean的拷贝一般采用两种办法 1.Spring里面的方法, org.springframework.beans.BeanUtils.copyProperties 2.Apache里面的方法,org.apache.commons.beanutils.BeanUtils.copyProperties 但是有需要注意的地方,...
  • java初学者经常会问,我这个方法要改变一个对象的属性,可以把参数传进去了,为什么没有改变了?——基本数据类型传值,而对象传引用或引用的拷贝。而有时候我们要获取到一个当前状态的对象复制品,他们是两个独立...
  • 技术的更新很快,曾经几何时,还在纠结数据库连接池是c3p0还是dbcp,反复测试哪个更快。...Java的Bean对象属性拷贝的是个很小的功能点,一直以来都是BeanUtil的copyProperties,无论是用的commmons-utils还是spring...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,457
精华内容 582
关键字:

java属性拷贝

java 订阅