Spring工具 BeanCopier异常问题

本文深入探讨Spring框架中BeanCopier工具在属性复制时可能遇到的NullPointerException异常及属性复制失败的问题,分析原因并提供解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring工具 BeanCopier异常问题

我们经常遇到相同属性的不同类时,为了代码简洁性和可复用性,当然最重要的是节省时间,我们会用到复制这个功能,一般有普遍用的有spring、apache-commons、net.sf.cglib。当然如果时间允许可以基于反射实现此功能,下次带大家看下两种复制的性能比较。现在大多数项目用spring框架,所以本文主要来告诉大家使用spring的BeanCopier所遇到的坑。

坑1:抛出NullPointerException异常

大家看到此异常第一步就想自己是不是原实例是不是空,但是本猿仔细检查了下,有实例化。具体错误如下:

Caused by: java.lang.NullPointerException
	at org.springframework.cglib.core.ReflectUtils.getMethodInfo(ReflectUtils.java:424)
	at org.springframework.cglib.beans.BeanCopier$Generator.generateClass(BeanCopier.java:133)
	at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
	at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
	at org.springframework.cglib.beans.BeanCopier$Generator.create(BeanCopier.java:90)
	at org.springframework.cglib.beans.BeanCopier.create(BeanCopier.java:50)
	at com.xx.framework.commons.utils.BeanCopierUtils.copyProperties(BeanCopierUtils.java:48)
	at com.xx.framework.commons.utils.BeanCopierUtils.copyOne2One(BeanCopierUtils.java:77)

来看下用法:

public class BeanCopierUtils {
    private static final LoggerAdapter LOGGER;
    private static final Map<String, BeanCopier> BEANCOPIER_MAP;
    public static final Map<String, BeanCopier> BEAN_COPIER_MAP;
    
    static {    
        BEANCOPIER_MAP = new HashMap();
        BEAN_COPIER_MAP = new HashMap();
    }
    private BeanCopierUtils() {
    }

    public static void copyProperties(Object source, Object target) {
        if (source != null && target != null) {
            String beanKey = generateKey(source.getClass(), target.getClass());
            BeanCopier copier = (BeanCopier)BEAN_COPIER_MAP.get(beanKey);
            if (copier == null) {
                copier = BeanCopier.create(source.getClass(), target.getClass(), false);
                BEAN_COPIER_MAP.put(beanKey, copier);
            }

            copier.copy(source, target, (Converter)null);
        }
    }

    private static String generateKey(Class<?> class1, Class<?> class2) {
        return class1.toString() + class2.toString();
    }

    public static <T> T copyOne2One(Object source, Class<T> target) {
        Object instance = target.newInstance();
            copyProperties(source, instance);
        return instance;
    }

    public static <T> List<T> copyList2List(List<?> source, Class<T> target) {
        if (source.isEmpty()) {
            return new ArrayList();
        } else {
            List<T> result = new ArrayList();
            Iterator iter= source.iterator();

            while(iter.hasNext()) {
                Object obj = iter.next();
                result.add(copyOne2One(obj, target));
            }

            return result;
        }
    }

    public static void copyAttribute(Object source, Object target) {
        if (source != null && target != null) {
            String key = source.getClass().toString() + target.getClass().toString();
            BeanCopier beanCopier = (BeanCopier)BEANCOPIER_MAP.get(key);
            if (beanCopier == null) {
                beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);
                BEANCOPIER_MAP.put(key, beanCopier);
            }

            beanCopier.copy(source, target, (Converter)null);
        }
    }
}

为了避免每次BeanCopier创建太慢和节省堆空间,这里每个两个类之间转换创建一个实例。复制方法起作用是如下

	beanCopier.copy(source, target, (Converter)null);

根据打印的错误信息,我们可以追踪最底层到如下行错误:
通常我们采用AES加密会采用如下方式,

 public static MethodInfo getMethodInfo(Member member) {
      return getMethodInfo(member, member.getModifiers());
  }

这个很明显是member为空,然后往上层走,创建BeanCopier时调用的此方法:

MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());

settter 结果如下获取:

PropertyDescriptor[] setters = ReflectUtils.getBeanGetters(this.target);
PropertyDescriptor setter = setters[i];

我也比较奇怪为何用getBeanGetters,应该用getBeanSetters的,我们可以追踪到

如何避免

1.加密后返回byte数组转换成16进制字符串或者base64编码字符串,如下

	public static PropertyDescriptor[] getBeanGetters(Class type) {
        return getPropertiesHelper(type, true, false);
    }
    private static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) {
        try {
            BeanInfo info = Introspector.getBeanInfo(type, class$java$lang$Object == null ? (class$java$lang$Object = class$("java.lang.Object")) : class$java$lang$Object);
            PropertyDescriptor[] all = info.getPropertyDescriptors();
            if (read && write) {
                return all;
            } else {
                List properties = new ArrayList(all.length);

                for(int i = 0; i < all.length; ++i) {
                    PropertyDescriptor pd = all[i];
                    if (read && pd.getReadMethod() != null || write && pd.getWriteMethod() != null) {
                        properties.add(pd);
                    }
                }

                return (PropertyDescriptor[])((PropertyDescriptor[])properties.toArray(new PropertyDescriptor[properties.size()]));
            }
        } catch (IntrospectionException var8) {
            throw new CodeGenerationException(var8);
        }
    }

然后PropertyDescriptor.getReadMethod和getWriteMethod方法:

 public synchronized Method getReadMethod() {
        Method readMethod = this.readMethodRef.get();
        if (readMethod == null) {
            Class<?> cls = getClass0();
            if (cls == null || (readMethodName == null && !this.readMethodRef.isSet())) {
                // The read method was explicitly set to null.
                return null;
            }
            String nextMethodName = Introspector.GET_PREFIX + getBaseName();
            if (readMethodName == null) {
                Class<?> type = getPropertyType0();
                if (type == boolean.class || type == null) {
                    readMethodName = Introspector.IS_PREFIX + getBaseName();
                } else {
                    readMethodName = nextMethodName;
                }
            }

            // Since there can be multiple write methods but only one getter
            // method, find the getter method first so that you know what the
            // property type is.  For booleans, there can be "is" and "get"
            // methods.  If an "is" method exists, this is the official
            // reader method so look for this one first.
            readMethod = Introspector.findMethod(cls, readMethodName, 0);
            if ((readMethod == null) && !readMethodName.equals(nextMethodName)) {
                readMethodName = nextMethodName;
                readMethod = Introspector.findMethod(cls, readMethodName, 0);
            }
            try {
                setReadMethod(readMethod);
            } catch (IntrospectionException ex) {
                // fall
            }
        }
        return readMethod;
    }
     public synchronized Method getWriteMethod() {
        Method writeMethod = this.writeMethodRef.get();
        if (writeMethod == null) {
            Class<?> cls = getClass0();
            if (cls == null || (writeMethodName == null && !this.writeMethodRef.isSet())) {
                // The write method was explicitly set to null.
                return null;
            }

            // We need the type to fetch the correct method.
            Class<?> type = getPropertyType0();
            if (type == null) {
                try {
                    // Can't use getPropertyType since it will lead to recursive loop.
                    type = findPropertyType(getReadMethod(), null);
                    setPropertyType(type);
                } catch (IntrospectionException ex) {
                    // Without the correct property type we can't be guaranteed
                    // to find the correct method.
                    return null;
                }
            }

            if (writeMethodName == null) {
                writeMethodName = Introspector.SET_PREFIX + getBaseName();
            }

            Class<?>[] args = (type == null) ? null : new Class<?>[] { type };
            writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
            if (writeMethod != null) {
                if (!writeMethod.getReturnType().equals(void.class)) {
                    writeMethod = null;
                }
            }
            try {
                setWriteMethod(writeMethod);
            } catch (IntrospectionException ex) {
                // fall through
            }
        }
        return writeMethod;
    }

可以看到获取前缀为get或者is的为readMethod,获取无返参的为writeMethod。writeMethod只有返参不为void时才会为空,其实从上面就知道了,我目标类里有个isSuccess方法,而setSuccess方法没有,所以返回为空,就异常了。这里的spring 版本是4.17,经测试返现到4.2.5此bug才修复。

坑2.有时属性值copy不了

这个bug经测试发现当目标类型和源类型的属性一个为基础类型如:int时,另一个为包装类型时:Integer,才有该问题,其他问题都是属性名对应不上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值