Java 之对象拷贝

一、getter/setter

常规方式,不再赘余。。。

二、BeanCopier

详细使用案例:使用CGlib实现Bean拷贝(BeanCopier)

  • BeanCopier 使用 cglib 修改字节码,实现真的动态 Read Writer getter/setter

注意:

  1. 当源类和目标类的属性名称、类型都相同,拷贝结果最优
  2. 当源对象和目标对象的属性名称相同、类型不同,则名称相同而类型不同的属性不会被拷贝

    注意:原始类型(int,short,char)和其包装类型,在这里都被当成了不同类型,因此不会被拷贝

  3. 源类或目标类的 setter 比 getter 少,拷贝没问题,此时 setter 多余,但不会报错
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.beans.BeanCopier;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description: 基于BeanCopier的属性拷贝
 * 凡是和反射相关的操作,基本都是低性能的。凡是和字节码相关的操作,基本都是高性能的
 */
@Slf4j
public class BeanCopyUtils {
     
    //创建过的BeanCopier实例放到缓存中,下次可以直接获取,提升性能
    private static final Map<String, BeanCopier> BEAN_COPIERS = new ConcurrentHashMap<>();

    /**
     * 该方法没有自定义Converter,只简单进行常规属性拷贝
     *
     * @param srcObj  源对象
     * @param destObj 目标对象
     */
    public static void copy(Object srcObj, Object destObj) {
     
        String key = genKey(srcObj.getClass(), destObj.getClass());
        BeanCopier copier;
        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();
    }
}

BeanCopier 性能比 Spring BeanUtils,Apache BeanUtils 和 PropertyUtils 好很多

三、spring 和 Apache 的 BeanUtils

  • 使用反射,先加载字节码、反编译、再实例化、再映射属性,因此性能较差

1、测试案例

public class TestSpringBeanUtils {
     
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
     
       //下面只是用于单独测试
        PersonSource personSource = new PersonSource(1, "pjmike", "12345", 21);
        PersonDest personDest = new PersonDest();
        BeanUtils.copyProperties(personSource,personDest);
        System.out.println("persondest: "+personDest);
    }
}

2、源码参考(可参考并封装自定义工具类)

(1) spring.BeanUtils

必须保证同名的两个成员变量类型相同

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);
					}
				}
			}
		}
	}
}

(2) Apache.BeanUtils

public void copyProperties(final Object dest, final Object orig) throws IllegalAccessException, InvocationTargetException {
     
	// Validate existence of the specified beans
    if (dest == null) {
     
    	throw new IllegalArgumentException("No destination bean specified");
    }
    if (orig == null) {
     
    	throw new IllegalArgumentException("No origin bean specified");
    }
    if (log.isDebugEnabled()) {
     
        log.debug("BeanUtils.copyProperties(" + dest + ", " + orig + ")");
    }
    // Copy the properties, converting as necessary
    if (orig instanceof DynaBean) {
     
    	final DynaProperty[] origDescriptors = ((DynaBean) orig).getDynaClass().getDynaProperties();
        for (DynaProperty origDescriptor : origDescriptors) {
     
        	final String name = origDescriptor.getName();
            // Need to check isReadable() for WrapDynaBean
            // (see Jira issue# BEANUTILS-61)
            if (getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name)) {
     
            	final Object value = ((DynaBean) orig).get(name);
                copyProperty(dest, name, value);
             }
        }
	} else if (orig instanceof Map) {
     
    	@SuppressWarnings("unchecked")
        final Map<String, Object> propMap = (Map<String, Object>) orig;
        for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
     
        	final String name = entry.getKey();
            if (getPropertyUtils().isWriteable(dest, name)) {
     
            	copyProperty(dest, name, entry.getValue());
            }
        }
	} else {
     
    	final PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig);
        for (PropertyDescriptor origDescriptor : origDescriptors) {
     
        	final String name = origDescriptor.getName();
            if ("class".equals(name)) {
     
            	continue; // No point in trying to set an object's class
             }
             if (getPropertyUtils().isReadable(orig, name) &&
             	getPropertyUtils().isWriteable(dest, name)) {
     
                try {
     
                	final Object value = getPropertyUtils().getSimpleProperty(orig, name);
                    copyProperty(dest, name, value);
                 } catch (final NoSuchMethodException e) {
     
                        // Should not happen
                 }
            }
        }
    }
}

注意:BeanUtils 为浅拷贝

三、MapStruct(推荐)

官网文档:MapStruct 1.3.1.Final Reference Guide

1、依赖

<properties>
    <org.mapstruct.version>1.3.1.Finalorg.mapstruct.version>
properties>

<dependency>
	 <groupId>org.mapstructgroupId>
     <artifactId>mapstructartifactId>
     <version>${org.mapstruct.version}version>
dependency>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.pluginsgroupId>
            <artifactId>maven-compiler-pluginartifactId>
            <version>3.5.1version>
            <configuration>
                <source>1.8source>
                <target>1.8target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstructgroupId>
                        <artifactId>mapstruct-processorartifactId>
                        <version>${org.mapstruct.version}version>
                    path>
                annotationProcessorPaths>
            configuration>
        plugin>
    plugins>
build>

2、常用注解

  • @Mapper:只有在接口加上这个注解, MapStruct 才会去实现该接口
    componentModel 属性:主要是指定实现类的类型,一般用到两个
    • default:默认,可以通过 Mappers.getMapper(Class) 方式获取实例对象
    • spring:在接口的实现类上自动添加注解 @Component,可通过 @Autowired 方式注入
  • @Mapping:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性
    • source:源属性
    • target:目标属性
    • dateFormat:String 到 Date 日期之间相互转换,通过 SimpleDateFormat,该值为 SimpleDateFormat 的日期格式
    • ignore: 忽略这个字段
  • @Mappings:配置多个 @Mapping
  • @MappingTarget:用于更新已有对象
  • @InheritConfiguration:用于继承配置

3、封装接口

具体 copy 实现可继承or实现该接口

public interface BasicObjectMapping<SOURCE, TARGET> {
     
    @InheritConfiguration
    TARGET to(SOURCE source);

    @InheritConfiguration
    List<TARGET> to(Collection<SOURCE> sources);

    @InheritInverseConfiguration
    SOURCE from(TARGET target);

    @InheritInverseConfiguration
    List<SOURCE> from(Collection<TARGET> targets);
}

注:配合 spring,要具体实现加注解 @Mapper(componentModel = "spring")

4、特殊案例

(1) 类型不一致

@Mappings({
     
	@Mapping(target = "createTime", expression = "java(com.java.mmzsblog.util.DateTransform.strToDate(source.getCreateTime()))"),
})
UserVO3 toConvertVO3(User source);

上面 expression 指定的表达式内容如下:

public class DateTransform {
     
    public static LocalDateTime strToDate(String str){
     
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss");
        return LocalDateTime.parse("2018-01-12 17:07:05",df);
    }
}

当字段类型不一致时,以下的类型之间是 mapstruct 自动进行类型转换的:

  1. 基本类型及其他们对应的包装类型
  2. 基本类型的包装类型和 string 类型之间

除此之外的类型转换需要通过定义表达式来进行指定转换

(2) 字段名不一致

@Mappings({
     
	@Mapping(source = "id", target = "userId"),
    @Mapping(source = "name", target = "userName")
})
UserVO4 toConvertVO(User source);

(3) 属性是枚举类型

案例一

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserEnum {
     
    private Integer id;
    private String name;
    private UserTypeEnum userTypeEnum;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserVO5 {
     
    private Integer id;
    private String name;
    private String type;
}

@Getter
@AllArgsConstructor
public enum UserTypeEnum {
     
    Java("000", "Java开发工程师"),
    DB("001", "数据库管理员"),
    LINUX("002", "Linux运维员");
    
    private String value;
    private String title;
}

@Mapping(source = "userTypeEnum", target = "type")
UserVO5 toConvertVO5(UserEnum source);

案例二

public class User {
     
    public enum Grade{
     
        AVERAGE,BRONZE,GOLD,DIAMOND;
    }
    private Grade grade;
//  setters, getters, toString()
}

public class UserDao {
     
    public enum Level{
     
        AVERAGE,BRONZE,GOLD,DIAMOND;
    }
    private Level level;
//  setters, getters, toString()
}

public class UserDojo {
     
    public enum RANK{
     
        NORMAL,JUNIOR,SENIOR,MASTER;
    }
    private RANK rank;
//  setters, getters, toString()
}

@Mapper
public interface UserMapper {
     
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mappings({
     
        @Mapping(source="grade", target="rank")
    })
    UserDojo userToUserDojo(User user);

    @ValueMappings({
     
        @ValueMapping(source="AVERAGE",target="NORMAL"),
        @ValueMapping(source="BRONZE",target="JUNIOR"),
        @ValueMapping(source="GOLD",target="SENIOR"),
        @ValueMapping(source="DIAMOND",target="MASTER"),
        @ValueMapping(source=MappingConstants.ANY_UNMAPPED, target=MappingConstants.NULL)
    })
    RANK customConveter(Grade grade);
}

你可能感兴趣的:(工具类)