两对象间的属性复制

来源:互联网 发布:淘宝晒单福利图2000p 编辑:程序博客网 时间:2024/06/06 11:49

业务系统中经常需要两个对象进行属性的拷贝,不能否认逐个的对象拷贝是最快速最安全的做法,但是当数据对象的属性字段数量超过程序员的容忍的程度,代码因此变得臃肿不堪,使用一些方便的对象拷贝工具类将是很好的选择。

性能对比: BeanCopier > PropertyUtils > BeanUtils. 其中BeanCopier的性能高出另外两个100数量级。

目前流行的较为公用认可的工具类:

Apache的两个版本:(反射机制)
org.apache.commons.beanutils.PropertyUtils.copyProperties(Object dest, Object orig)
org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)
Spring版本:(反射机制)
org.springframework.beans.BeanUtils.copyProperties(Object source, Object target, Class editable, String[] ignoreProperties)
cglib版本:(使用动态代理,效率高)
net.sf.cglib.beans.BeanCopier.copy(Object paramObject1, Object paramObject2, Converter paramConverter)

缺陷预防:

@Document(collection = "project_info")public class ProjectInfo extends BaseEntity {    @TextIndexed(weight = 3)    private String name;    @TextIndexed(weight = 2)    private String description;    @Field("nick_name")    private String nickName;    @Field("is_available")    private boolean isAvailable = true;    @Field("is_public")    private boolean isPublic;    @Field("create_at")    private Date createAt;    @Field("last_activity_at")    private Date lastActivityAt;    @Field("web_url")    private String webUrl;    @Field("repo_url")    private String repoUrl;    @Field("avatar_url")    private String avatarUrl;    private User owner;    @Indexed    private long stars;    private Map<String, Collection<String>> categories;    private Collection<ProjectMember> members;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getDescription() {        return description;    }    public void setDescription(String description) {        this.description = description;    }    public boolean getIsAvailable() {        return isAvailable;    }    public void setIsAvailable(boolean available) {        isAvailable = available;    }    public boolean getIsPublic() {        return isPublic;    }    public void setIsPublic(boolean aPublic) {        isPublic = aPublic;    }    public Date getCreateAt() {        return createAt;    }    public void setCreateAt(Date createAt) {        this.createAt = createAt;    }    public Date getLastActivityAt() {        return lastActivityAt;    }    public void setLastActivityAt(Date lastActivityAt) {        this.lastActivityAt = lastActivityAt;    }    public String getWebUrl() {        return webUrl;    }    public void setWebUrl(String webUrl) {        this.webUrl = webUrl;    }    public String getRepoUrl() {        return repoUrl;    }    public void setRepoUrl(String repoUrl) {        this.repoUrl = repoUrl;    }    public String getAvatarUrl() {        return avatarUrl;    }    public void setAvatarUrl(String avatarUrl) {        this.avatarUrl = avatarUrl;    }    public Map<String, Collection<String>> getCategories() {        return categories;    }    public void setCategories(Map<String, Collection<String>> categories) {        this.categories = categories;    }    public User getOwner() {        return owner;    }    public void setOwner(User owner) {        this.owner = owner;    }    public long getStars() {        return stars;    }    public void setStars(long stars) {        this.stars = stars;    }    public Collection<ProjectMember> getMembers() {        return members;    }    public void setMembers(Collection<ProjectMember> members) {        this.members = members;    }    public String getNickName() {        return nickName;    }    public void setNickName(String nickName) {        this.nickName = nickName;    }}

** * Created by ${denghb} on 2016/8/9. */public class ProjectInfoDto {    private String id;    private String name;    private String nickName;    private String description;    private String avatarUrl;    private Date createTime;    private Date updateTime;    private String status;    private boolean isAvailable;    private boolean isPublic;    private UserDto creator;    private Collection<ProjectMemberDto> members;    private Collection<ProjectReleasedVersionDto> releasedVersions;    private Map<String, Collection<String>> categories;    public Map<String, Collection<String>> getCategories() {        return categories;    }    public void setCategories(Map<String, Collection<String>> categories) {        this.categories = categories;    }    public String getId() {        return id;    }    public void setId(String id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getNickName() {        return nickName;    }    public void setNickName(String nickName) {        this.nickName = nickName;    }    public String getDescription() {        return description;    }    public void setDescription(String description) {        this.description = description;    }    public String getAvatarUrl() {        return avatarUrl;    }    public void setAvatarUrl(String avatarUrl) {        this.avatarUrl = avatarUrl;    }    public Date getCreateTime() {        return createTime;    }    public void setCreateTime(Date createTime) {        this.createTime = createTime;    }    public Date getUpdateTime() {        return updateTime;    }    public void setUpdateTime(Date updateTime) {        this.updateTime = updateTime;    }    public String getStatus() {        return status;    }    public void setStatus(String status) {        this.status = status;    }    public boolean getIsAvailable() {        return isAvailable;    }    public void setIsAvailable(boolean available) {        isAvailable = available;    }    public boolean getIsPublic() {        return isPublic;    }    public void SetIsPublic(boolean aPublic) {        isPublic = aPublic;    }    public UserDto getCreator() {        return creator;    }    public void setCreator(UserDto creator) {        this.creator = creator;    }    public Collection<ProjectMemberDto> getMembers() {        return members;    }    public void setMembers(Collection<ProjectMemberDto> members) {        this.members = members;    }    public Collection<ProjectReleasedVersionDto> getReleasedVersions() {        return releasedVersions;    }    public void setReleasedVersions(Collection<ProjectReleasedVersionDto> releasedVersions) {        this.releasedVersions = releasedVersions;    }}

private ProjectInfo trans2ProjectInfo(ProjectInfoDto infoDto) {    ProjectInfo projectInfo = getProjectInfo(infoDto);    if ("DELETE".equalsIgnoreCase(infoDto.getStatus())) {        projectInfo.setDeleteFlag(true);        return projectInfo;    }    // 复制简单属性到ProjectInfo    BeanUtils.copyProperties(infoDto, projectInfo, "members", "categories");    projectInfo.setCreateAt(infoDto.getCreateTime());    projectInfo.setLastActivityAt(infoDto.getUpdateTime());    projectInfo.setCategories(getCategories(infoDto, projectInfo));    // 添加owner属性    User owner = getOwner(infoDto);    projectInfo.setOwner(owner);    // 添加members属性    Collection<ProjectMember> members = getProjectMembers(infoDto);    projectInfo.setMembers(members);    return projectInfo;}

注意1:Spring 的BeanUtils.copyProperties在拷贝属性时忽略空值解决办法。

当src对象的键值为Null时,就会把target对象的对应键值覆盖成空了。

package com.dhb.springmvc.util.beanUtil;import org.springframework.beans.BeanUtils;import org.springframework.beans.BeanWrapper;import org.springframework.beans.BeanWrapperImpl;import java.util.HashSet;import java.util.Set;/** * src对象的键值为Null,使用如下方式就不会把target对象的对应键值覆盖成空了 * Created by ${denghb} on 2016/9/6. */public class BeanUtilIgnore {    public static String[] getNullPropertyNames ( Object source) {        final BeanWrapper src = new BeanWrapperImpl(source);        java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();        Set<String> emptyNames = new HashSet<>();        for(java.beans.PropertyDescriptor pd : pds) {            Object srcValue = src.getPropertyValue(pd.getName());            if (srcValue == null) emptyNames.add(pd.getName());        }        String[] result = new String[emptyNames.size()];        return emptyNames.toArray(result);    }    public static void copyPropertiesIgnoreNull( Object src, Object target){        BeanUtils.copyProperties(src, target, getNullPropertyNames(src));    }}:

然后在调用这个方法进行复制:
BeanUtils.copyProperties(examLifeStyle, examDetail, getNullPropertyNames(examLifeStyle));

注意2:beanutils.Converter使用案例,增强apache的beanUtils的拷贝属性,注册一些新的类型转换。

package com.dhb.springmvc.util.beanUtil;import org.apache.commons.beanutils.BeanUtils;import org.apache.commons.beanutils.ConvertUtils;import java.lang.reflect.InvocationTargetException;/** * Created by ${denghb} on 2016/9/6. */public class BeanUtilsEx extends BeanUtils {    public static void copyProperties(Object dest, Object orig) {        try {            BeanUtils.copyProperties(dest, orig);        } catch (IllegalAccessException ex) {            ex.printStackTrace();        } catch (InvocationTargetException ex) {            ex.printStackTrace();        }    }    static {        ConvertUtils.register(new DateConverter(null), java.util.Date.class);        //ConvertUtils.register(new BigDecimalConvert(), BigDecimal.class);    }}
package com.dhb.springmvc.util.beanUtil;import org.apache.commons.beanutils.Converter;import org.apache.commons.lang.StringUtils;import java.text.ParseException;import java.text.SimpleDateFormat;/** * 简易DateConverter. * Apache BeanUtils 做转换,默认时间格式为yyyy-MM-dd,可由构造函数改变. * Created by ${denghb} on 2016/9/6. */public class DateConverter implements Converter {    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");    public DateConverter(String formatPattern) {        if (StringUtils.isNotBlank(formatPattern)) {            format = new SimpleDateFormat(formatPattern);        }    }    @Override    public Object convert(Class aClass, Object value) {        String dateStr = (String) value;        if (StringUtils.isNotBlank(dateStr)) {            try {                return format.parse(dateStr);            } catch (ParseException e) {                e.printStackTrace();            }        }        return null;    }}

注意3:BeanUtils拷贝null属性的问题

问题现象
       1.当源对象(a)中存在一个java.sql.Date类型的属性并且值为null,目标对象(b)中也存在这个同名同类型的属性。把a对象属性值copy给b时BeanUtils.copyProperties(b, a);会抛出异常;
       2.当源对象(a)中存在一个java.sql.Date类型的属性并且值为null,目标对象(b)不存在这个同名同类型的属性,copy时没问题。

解决方法
       从convert方法可以看出只要想办法把useDefault和defaultValue设值就能解决,SqlDateConverter的另外一个构造方法可以设置这两个值,问题是从哪里把这个自己构造的converter 注册进去。从BeanUtils到BeanUtilsBean再到ConvertUtilsBean的找,发现都是写死的调用ConvertUtilsBean.deregister()方法注册的,最后发现ConvertUtilsBean有个register(Converter converter, Class clazz)方法可以注册,而ConvertUtilsBean又是在BeanUtilsBean里new出来的,那么只要获取到这个ConvertUtilsBean即可,BeanUtilsBean提供了获取的方法。因此在BeanUtils.copyProperties(b, a);加上下面这句代码即可
BeanUtilsBean.getInstance().getConvertUtils().register(new SqlDateConverter(null),Date.class);
另外有一个辅助类也可以,
ConvertUtils.register(new SqlDateConverter(null),Date.class);

上图一张,你们就明白了。


关于bean复制,如果属性较少,建议直接写个方法完成get/set即可。如果属性较多,可以自己采用反射实现一个满足自己需要的工具类,或者使用spring的那个beanutils类,不建议使用commons-beanutils包中的那个BeanUtils类,刚看了下,这个类对于内部静态类的对象复制也会出现问题,检验太复杂了,常会出现一些诡异的问题。毕竟我们bean复制一般就是简单的属性copy而已。

而且,由于这些BeanUtils类都是采用反射机制实现的,对程序的效率也会有影响。因此,慎用BeanUtils.copyProperties!!!

参考资料:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp37

                    http://jen.iteye.com/blog/1032521