问题描述:在copyProperties(A,B)拷贝对象时,需要将A.type(Integer)拷贝到B.type(String),但是A.type是类型的数字编码,B.type是类型的描述,通过给B.type加一个注解指定枚举类,实现在拷贝属性的时候,自动转换类型编码到类型描述并赋值B.type;
JDK版本 >= 1.8
枚举接口定义:
package com.example.visy.test;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @author visy.wang
* @date 2022/1/10 9:43
*/
public interface EnumConvert{
//将映射关系缓存到Map中,便于快速查找
Map<String,Object> map = new HashMap<>();
//key映射道value
default Object mapping(Object key){
this.initMap();
return map.get(String.valueOf(key));
}
default void initMap(){
if(!map.isEmpty()){
return;
}
for (EnumConvert item : getValues()) {
map.put(String.valueOf(item.getKey()), item.getValue());
}
}
Object getKey();
// 返回的实际类型必须和目标属性的类型一致
Object getValue();
EnumConvert[] getValues();
}
枚举定义:
package com.example.visy.test;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 方向枚举 (必须实现EnumConvert接口)
* @author visy.wang
* @date 2022/1/10 9:42
*/
@Getter
@AllArgsConstructor
public enum DirectionEnum implements EnumConvert {
TOP(1, "上"),
DOWN(2, "下"),
LEFT(3, "左"),
RIGHT(4, "右");
private final Integer code;
private final String desc;
@Override
public Object getKey() {
return code;
}
@Override
public Object getValue() {
//注意:必须和待转换的目标属性类型相同,否则不会生效
return desc;
}
@Override
public EnumConvert[] getValues() {
return DirectionEnum.values();
}
}
注解定义:
package com.example.visy.test;
import java.lang.annotation.*;
import java.lang.annotation.Target;
/**
* 枚举转换注解
* @author visy.wang
* @date 2022/1/10 10:11
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumMapper {
//指定枚举Class
Class<? extends Enum<?>> value();
//指定源对象中对应的属性别名(属性名相同时,无需指定alias)
String alias() default "";
}
拷贝工具定义:
package com.example.visy.test;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author visy.wang
* @date 2022/1/10 11:28
*/
public class BeanUtils {
private static final Map<String, List<Field>> TARGET_FIELD_MAP = new HashMap<>();
private static final Map<String, Map<String,Field>> SOURCE_FIELD_MAP = new HashMap<>();
public static void copyProperties(Object source, Object target) throws IllegalAccessException {
List<Field> targetFields = getFields(target);
if(Objects.isNull(targetFields) || targetFields.isEmpty()){
return;
}
for(Field targetField: targetFields){
Field sourceField = getField(source, targetField.getName());
if(targetField.isAnnotationPresent(EnumMapper.class)){ //检查注解
EnumMapper enumMapper = targetField.getAnnotation(EnumMapper.class);
String aliasName = enumMapper.alias();
if(Objects.nonNull(aliasName) && !aliasName.isEmpty()){
//指定了别名的情况,优先使用别名
sourceField = getField(source, aliasName);
}
if(Objects.isNull(sourceField)){
continue; //源属性为空->不拷贝
}
if(enumMapper.value().isEnum()){//注解中指定的Class必须是枚举
Enum<?> mapper = enumMapper.value().getEnumConstants()[0]; //随便取一个枚举值
if(mapper instanceof EnumConvert){//枚举必须实现了EnumConvert接口
Object oldValue = sourceField.get(source);
//转换后拷贝
Object newValue = ((EnumConvert) mapper).mapping(oldValue);
if(Objects.nonNull(newValue) && targetField.getType().equals(newValue.getClass())){
targetField.set(target, newValue);
continue;
}
}
}
}
if(Objects.isNull(sourceField)){
continue; //源属性为空->不拷贝
}
if(!targetField.getType().equals(sourceField.getType())){
continue; //两个属性类型不一致->不拷贝
}
//拷贝
targetField.set(target, sourceField.get(source));
}
}
private static List<Field> getFields(Object obj){
Class<?> clazz = obj.getClass();
String key = clazz.getName();
List<Field> fields = TARGET_FIELD_MAP.get(key);
if(Objects.isNull(fields)){
Field[] fieldArray = clazz.getDeclaredFields();
Field.setAccessible(fieldArray, true);
fields = Arrays.stream(fieldArray).collect(Collectors.toList());
TARGET_FIELD_MAP.put(key, fields);
}
return fields;
}
private static Field getField(Object obj, String fieldName){
Class<?> clazz = obj.getClass();
String key = clazz.getName();
Map<String,Field> map = SOURCE_FIELD_MAP.get(key);
if(Objects.isNull(map)){
Field[] fieldArray = clazz.getDeclaredFields();
Field.setAccessible(fieldArray, true);
map = Arrays.stream(fieldArray).collect(Collectors.toMap(Field::getName, Function.identity()));
SOURCE_FIELD_MAP.put(key, map);
}
return map.get(fieldName);
}
}
源对象:
package com.example.visy.test;
import lombok.Data;
/**
* 源对象
* @author visy.wang
* @date 2022/1/10 11:26
*/
@Data
public class Source {
private Integer top = 1;
private Integer down = 2;
private Integer left = 3;
private Integer right = 4;
private Integer center = 5;
private String name = "张三";
}
目标对象:
package com.example.visy.test;
import lombok.Data;
/**
* 目标对象
* @author visy.wang
* @date 2022/1/10 10:11
*/
@Data
public class Target {
//和Source.top同名
//Long和枚举的getValue()的实际类型(String)不匹配
//Long和Source.top类型(Integer)不匹配
//结果为:null
@EnumMapper(DirectionEnum.class)
private Long top;
//和Source.down同名
//Integer和枚举的getValue()的实际类型(String)不匹配
//Integer和Source.down类型(Integer)匹配
//结果为:2
@EnumMapper(DirectionEnum.class)
private Integer down;
//和Source.left同名
//String和枚举的getValue()的实际类型(String)匹配
//结果为:"左"
@EnumMapper(DirectionEnum.class)
private String left;
//和Source.right不同名,但指定了别名"right"
//String和枚举的getValue()的实际类型(String)匹配
//结果为:"右"
@EnumMapper(value = DirectionEnum.class, alias = "right")
private String rightStr;
//无需转换的属性只要同名且类型一致就可正常拷贝
private String name;
}
测试类:
package com.example.visy.test;
/**
* @author visy.wang
* @date 2022/1/10 10:47
*/
public class Test {
public static void main(String[] args) throws Exception{
Source source = new Source();
Target target = new Target();
BeanUtils.copyProperties(source, target);
System.out.println(target);
}
}
打印结果:
Target(top=null, down=2, left=左, rightStr=右, name=张三)