BeanUtil

实现bean之间属性copy

Posted by Static on June 16, 2020

自实现JavaBean 属性复制,对象转为Map等功能

1. 对象转为Map

@see toMap方法

2. 对象之间属性复制

@see transfer方法

3. 代码实现

依赖beansettergetter方法,将bean对象转为BeanInfo对象,通过反射获取getter和setter方法对属性赋值

/**
 * 无参构造器之间的类型转换
 * 属性之间浅copy
 * <p>
 * Created by wangzhixiang on 2020/6/16.
 */
@UtilityClass
public class BeanUtil {

    // 将before转为after,支持自定义setter操作
    public static <Before, After> After transfer(Before before, Class<After> afterClass, Processor<Before, After> processor) {
        try {
            After after = transfer(before, afterClass);
            processor.accept(before, after);
            return after;
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    // 根据bean的getter方法将bean转为map
    public static Map<String, Object> toMap(Object bean) {
        if (bean == null) return null;
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass(), Object.class);
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            Map<String, Object> map = new HashMap<>();
            for (PropertyDescriptor descriptor : propertyDescriptors) {
                Object propertyValue = descriptor.getReadMethod().invoke(bean);
                map.put(descriptor.getName(), propertyValue);
            }
            return map;
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    // 将Before中的字段复制到after中,注:需要setter和getter方法
    public static <Before, After> void copyProperty(Before before, After after) throws IntrospectionException {
        PropertyDescriptor[] beforePropertyDescriptors = getPropertyDescriptor(before);
        PropertyDescriptor[] afterPropertyDescriptors = getPropertyDescriptor(after);
        try {
            for (PropertyDescriptor descriptor : afterPropertyDescriptors) {
                for (PropertyDescriptor beforePropertyDescriptor : beforePropertyDescriptors) {
                    Object value = beforePropertyDescriptor.getReadMethod().invoke(before);
                    descriptor.getWriteMethod().invoke(after, value);
                }
            }
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    public static <Before, After> After transfer(Before before, Class<After> afterClass) {
        try {
            After after = afterClass.getDeclaredConstructor().newInstance();
            PropertyDescriptor[] beforePropertyDescriptors = getPropertyDescriptor(before);
            PropertyDescriptor[] afterPropertyDescriptors = getPropertyDescriptor(after);

            for (PropertyDescriptor descriptor : afterPropertyDescriptors) {
                for (PropertyDescriptor beforePropertyDescriptor : beforePropertyDescriptors) {
                    if (beforePropertyDescriptor.getName().equals(descriptor.getName())) {
                        Object value = beforePropertyDescriptor.getReadMethod().invoke(before);
                        descriptor.getWriteMethod().invoke(after, value);
                        break;
                    }
                }
            }
            return after;
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    private static PropertyDescriptor[] getPropertyDescriptor(Object bean) {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass(), Object.class);
            return beanInfo.getPropertyDescriptors();
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    // 支持自定义修改字段
    public interface Processor<Before, After> extends BiConsumer<Before, After> {
    }

}

4. 测试

/**
 * Created by wangzhixiang on 2020/6/15.
 */
class BeanUtilTest extends BaseTest {
    def "test_transfer"() {
        when:
        def after = BeanUtil.transfer(before, afterClass)
        def expectedAfterMap = BeanUtil.toMap(expectedAfter)

        then:
        BeanUtil.toMap(after).forEach(new BiConsumer<String, Object>() {
            @Override
            void accept(String k, Object v) {
                if (expectedAfterMap.containsKey(k)) {
                    if (k == "metaClass") {
                        return
                    }
                    assert v == expectedAfterMap.get(k)
                }
            }
        })
        where:
        before                                                | afterClass  || expectedAfter
        new Before(id: 1, age: 20, name: "test", score: 99.0) | After.class || new After(id: 1, age: 20, name: "test", score: 99.0)
        new Before(id: 1, age: 20, score: 99.0)               | After.class || new After(id: 1, age: 20, score: 99.0)
    }

    @Data
    static class Before {
        long id
        int age
        String name
        double score
    }

    @Data
    static class After {
        long id
        int age
        String name
        double score
    }

}