Bean copy实战

1.报错现象

sed by: org.apache.dubbo.remoting.RemotingException: Failed to send response: Response [id=4321, version=2.0.2, status=20, event=false, error=null, result=AppResponse [value=RecommendAtConnectRes(messageBase=MessageBaseRes(pubList=null, noteList=null, tipList=[PushBaseDto(ct=null, content=机器人冰冰为您服务)], saveList=null)), exception=null]], cause: java.lang.RuntimeException: Serialized class com.dewu.kefu.bot.customer.domain.dto.PushBaseDto must implement java.io.Serializable
 Java field: private java.util.List MessageBase.tipList
 Java field: private .base.MessageBaseRes RecommendAtConnectRes.messageBase
java.lang.RuntimeException: Serialized class .PushBase must implement java.io.Serializable
 Java field: private java.util.List base.MessageBase.tipList
        at com.alibaba.com.caucho.hessian.io.JavaSerializer$FieldSerializer.serialize(JavaSerializer.java:304)
        at com.alibaba.com.caucho.hessian.io.JavaSerializer.writeInstance(JavaSerializer.java:284)
        at com.alibaba.com.caucho.hessian.io.JavaSerializer.writeObject(JavaSerializer.java:251)
        at com.alibaba.com.caucho.hessian.io.Hessian2Output.writeObject(Hessian2Output.java:412)
        at org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectOutput.writeObject(Hessian2ObjectOutput.java:97)
        at org.apache.dubbo.rpc.protocol.dubbo.DubboCodec.encodeResponseData(DubboCodec.java:203)
        at org.apache.dubbo.remoting.exchange.codec.ExchangeCodec.encodeResponse(ExchangeCodec.java:283)
        at org.apache.dubbo.remoting.exchange.codec.ExchangeCodec.encode(ExchangeCodec.java:71)
MessageBase  messageBase  = getRecommendContent(request)
//返回给远程接口的对象
MessageBaseRes messageBaseRes = new MessageBaseRes();
BeanUtil.copyProperties(messageBase, messageBaseRes);

@Data
public class PushBase {
    private String ct;
    private String content;
}

@Data
public class MessageBase {
    private List<PushBase> tipList;
}

@Data //远程接口返回对象
public class PushBaseRes implements Serializable {
    private static final long serialVersionUID = 8434107544771588426L;
    private String ct;
    private String content;
}

@Data  //远程接口返回对象
public class MessageBaseRes implements Serializable {
    private static final long serialVersionUID = -6492447071340256545L;
    private List<PushBaseRes> tipList;
}

2.cn.hutool.core.bean.BeanUtil (5.1.0)

  • 属性泛型处理:TargetBean拷贝的成员属性实际类型可能跟声明不一致

@Data
public class HuUnsameListSource {
    private Date testDate;
    private List<Integer> ids;
}

@Data
public class HuUnsameListTarget {
    private Date testDate;
    private List<String> ids;
}

HuUnsameListSource source = new HuUnsameListSource();
source.setTestDate(new Date());
source.setIds(Arrays.asList(1, 2, 3));

HuUnsameListTarget target = new HuUnsameListTarget();
BeanUtil.copyProperties(source,target);
System.out.println(JSON.toJSON(target));

  • 对象类型不一样属性值可以复制

@Data
public class HuUnSameInnerSource {
    private Date testDate;
    private Long id;
}

@Data
public class HuUnSameInnerTarget {
    private Date testDate;
    private String id;
}

HuUnSameInnerSource source = new HuUnSameInnerSource();
source.setTestDate(new Date());
source.setId(123L);
HuUnSameInnerTarget target = new HuUnSameInnerTarget();
BeanUtil.copyProperties(source,target);
System.out.println(JSON.toJSON(target));

3.org.springframework.beans.BeanUtils

  • List copy 类型不一样 结果为空 (spring-beans-5.3.14.jar) 会判断属性的泛型是否一致,如不一致,直接忽略属性的拷贝

@Data
public class SpringListSource {
    private Date testDate;
    private List<Integer> ids;
}

@Data
public class SpringListTarget {
    private Date testDate;
    private List<String> ids;
}

SpringListSource source = new SpringListSource();
source.setTestDate(new Date());
source.setIds(Arrays.asList(1, 2, 3));
SpringListTarget target = new SpringListTarget();
BeanUtils.copyProperties(source,target);
System.out.println(target);

spring-beans-5.2.4.RELEASE

  • 对象类型不一样 结果为空

@Data
public class SpringUnSameInnerSource {
    private Date testDate;
    private Long id;
}

@Data
public class SpringUnSameInnerTarget {
    private Date testDate;
    private String id;
}

SpringUnSameInnerSource source = new SpringUnSameInnerSource();
source.setTestDate(new Date());
source.setId(123L);
SpringUnSameInnerTarget target = new SpringUnSameInnerTarget();
BeanUtils.copyProperties(source,target);
System.out.println(JSON.toJSON(target));

4.org.apache.commons.beanutils.BeanUtils(性能比较差,不推荐使用)

对于对象拷贝加了很多的检验,包括类型的转换,甚至还会检验对象所属的类的可访问性,可谓相当复杂,这也造就了它的差劲的性能

  • 类型一样 转换类型

@Data
public class CommonUnsameInnerSource {
    private Date testDate;
    private Integer id;
}
@Data
public class CommonUnsameInnerTarget {
    private Date testDate;
    private String id;
}

CommonBeanListSource source = new CommonBeanListSource();
source.setTestDate(new Date());
source.setIds(Arrays.asList(1, 2, 3));
CommonBeanListTarget target = new CommonBeanListTarget();
BeanUtils.copyProperties(target,source);
System.out.println(target);

  • List泛型转换

@Data
public class CommonBeanListSource {
    private Date testDate;
    private List<Integer> ids;
}

@Data
public class CommonBeanListTarget {
    private Date testDate;
    private List<String> ids;
}

5.net.sf.cglib.beans.BeanCopier

  • 基于CGlib字节码操作生成get、set方法

  • 整体性能很不错,使用也不复杂,可以使用

CglibTestSource source = new CglibTestSource();
source.setTestDate(new Date());
source.setIds(Arrays.asList(1, 2, 3));

final BeanCopier copier = BeanCopier.create(CglibTestSource.class, CglibTestTarget.class, false);
CglibTestTarget target = new CglibTestTarget();
copier.copy(source, target, null);
System.out.println(JSON.toJSON(target));

6.org.mapstruct.Mapper

  1. MapStruct 是一个代码生成器,主要用于 Java Bean 之间的映射,如 entity 到 DTO 的映射。
  2. 灵活性高支持简单,复杂,嵌套,自定义扩展等多种手段
  3. 编译期生成,没有效率问题
  4. 方便发现问题,方便debug
  5. 缺点:不利于重构,比如变量名字由a改成b,感知不到
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class Person {
        private Long id;
        private String name;
        private String email;
        private Date birthday;
    }
    
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class PersonDTO {
        private Long id;
        private String name;
        private String email;
        private Date birthday;
    }
    
    @Mapper
    public interface DtoConverter {
        DtoConverter INSTANCE = Mappers.getMapper(DtoConverter.class);
        List<PersonDTO> domain2dto(List<Person> people);
    }
    
    List<Person> people = new ArrayList<>();
    Person person = new Person();
    person.setBirthday(new Date());
    people.add(person);
    List<PersonDTO> personDTOs = DtoConverter.INSTANCE.domain2dto(people);
    assertNotNull(personDTOs);
    
    //编译之后
    package com.example.beancopydemo.mapstruct;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class DtoConverterImpl implements DtoConverter {
        public DtoConverterImpl() {
        }
    
        public List<PersonDTO> domain2dto(List<Person> people) {
            if (people == null) {
                return null;
            } else {
                List<PersonDTO> list = new ArrayList(people.size());
                Iterator var3 = people.iterator();
    
                while(var3.hasNext()) {
                    Person person = (Person)var3.next();
                    list.add(this.personToPersonDTO(person));
                }
    
                return list;
            }
        }
    
        protected PersonDTO personToPersonDTO(Person person) {
            if (person == null) {
                return null;
            } else {
                PersonDTO personDTO = new PersonDTO();
                personDTO.setId(person.getId());
                personDTO.setName(person.getName());
                personDTO.setEmail(person.getEmail());
                personDTO.setBirthday(person.getBirthday());
                return personDTO;
            }
        }
    }

7.性能对比

8.总结


  • 不同的Bean Copy工具,功能存在不同;同一个工具,不同版本实现可能存在些许差异,复杂对象copy要慎重
  • 从性能来讲,Apache BeanUtils.copyProperties存在性能问题,其他工具性能可用
  • mapStruct性能不过,使用也比较方便,代码也会比较清晰,可以考虑引入使用,感觉很不错

9.参考


版权声明:本文为weixin_38068301原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。