JAVA_解决反射Introspector.getBeanInfo()得到的writeMethodRef与Lombok注解@Accessors(chain = true)冲突导致空指针问题

解决反射Introspector.getBeanInfo()得到的writeMethodRef与Lombok注解@Accessors(chain = true)冲突导致空指针问题

一、报错信息,空指针异常

java.lang.RuntimeException: java.lang.NullPointerException
	at com.bailian.pay.mer.platform.pay.controller.dto.CommonDto.lambda$fromJSON$1(CommonDto.java:61)
	at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
	at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:647)
	at com.bailian.pay.mer.platform.pay.controller.dto.CommonDto.fromJSON(CommonDto.java:53)
	at com.bailian.pay.mer.platform.pay.service.impl.TransConfigServiceImpl.lambda$main$19(TransConfigServiceImpl.java:1620)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.ArrayList$Itr.forEachRemaining(ArrayList.java:901)
	at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566)
	at com.bailian.pay.mer.platform.pay.service.impl.TransConfigServiceImpl.main(TransConfigServiceImpl.java:1634)
Caused by: java.lang.NullPointerException
	at com.bailian.pay.mer.platform.pay.controller.dto.CommonDto.lambda$fromJSON$1(CommonDto.java:58)
	... 13 more

二、错误解析

1、看哪有异常,定位后发现是json的反序列化出现了空指针异常,用的是自己写的反射逻辑实现

2、一路debug下来发现是 通过json反射为实体类出了问题,p.getWriteMethod()为空导致了空指针问题

    /**
     * 从JSON 转实体类
     */
    @SneakyThrows
    public void fromJSON(JSONObject o){
        if(o != null){
            PropertyDescriptor[] info = beanInfo();
            if(info != null){
                Arrays.stream(info).forEach(
                        p -> {
                            try {
                                Object value = o.getObject(p.getName(),p.getPropertyType());
                                if(value != null){
                                    p.getWriteMethod().invoke(this,value);
                                }
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        }
                );
            }
        }
    }

    /**
     * 通过内省获取bean的所有属性
     */
    @SneakyThrows
    private PropertyDescriptor[] beanInfo(){
        BeanInfo info = Introspector.getBeanInfo(this.getClass());
        return  info.getPropertyDescriptors();
    }

3、进入 getWriteMethod() 方法发现 writeMethodRef 为空

在这里插入图片描述

4、那就往上找,发现是 info.getPropertyDescriptors() 的问题,而这个只是个get方法,所以需要看 Introspector.getBeanInfo(Class<?> beanClass) 方法

在这里插入图片描述

5、往下debug,发现返回的beanInfo通过了new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo()方法

在这里插入图片描述

6、再往下看发现是getTargetPropertyInfo()返回的writeMethodRef为空

在这里插入图片描述

7、进入getTargetPropertyInfo()方法

    private PropertyDescriptor[] getTargetPropertyInfo() {

        // Check if the bean has its own BeanInfo that will provide
        // explicit information.
        PropertyDescriptor[] explicitProperties = null;
        if (explicitBeanInfo != null) {
            explicitProperties = getPropertyDescriptors(this.explicitBeanInfo);
        }

        if (explicitProperties == null && superBeanInfo != null) {
            // We have no explicit BeanInfo properties.  Check with our parent.
            addPropertyDescriptors(getPropertyDescriptors(this.superBeanInfo));
        }

        for (int i = 0; i < additionalBeanInfo.length; i++) {
            addPropertyDescriptors(additionalBeanInfo[i].getPropertyDescriptors());
        }

        if (explicitProperties != null) {
            // Add the explicit BeanInfo data to our results.
            addPropertyDescriptors(explicitProperties);

        } else {

            // Apply some reflection to the current class.

            // First get an array of all the public methods at this level
            Method methodList[] = getPublicDeclaredMethods(beanClass);

            // Now analyze each method.
            for (int i = 0; i < methodList.length; i++) {
                Method method = methodList[i];
                if (method == null) {
                    continue;
                }
                // skip static methods.
                int mods = method.getModifiers();
                if (Modifier.isStatic(mods)) {
                    continue;
                }
                String name = method.getName();
                Class<?>[] argTypes = method.getParameterTypes();
                Class<?> resultType = method.getReturnType();
                int argCount = argTypes.length;
                PropertyDescriptor pd = null;

                if (name.length() <= 3 && !name.startsWith(IS_PREFIX)) {
                    // Optimization. Don't bother with invalid propertyNames.
                    continue;
                }

                try {

                    if (argCount == 0) {
                        if (name.startsWith(GET_PREFIX)) {
                            // Simple getter
                            pd = new PropertyDescriptor(this.beanClass, name.substring(3), method, null);
                        } else if (resultType == boolean.class && name.startsWith(IS_PREFIX)) {
                            // Boolean getter
                            pd = new PropertyDescriptor(this.beanClass, name.substring(2), method, null);
                        }
                    } else if (argCount == 1) {
                        if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) {
                            pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null);
                        } else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) {
                            // Simple setter
                            pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method);
                            if (throwsException(method, PropertyVetoException.class)) {
                                pd.setConstrained(true);
                            }
                        }
                    } else if (argCount == 2) {
                            if (void.class.equals(resultType) && int.class.equals(argTypes[0]) && name.startsWith(SET_PREFIX)) {
                            pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, null, method);
                            if (throwsException(method, PropertyVetoException.class)) {
                                pd.setConstrained(true);
                            }
                        }
                    }
                } catch (IntrospectionException ex) {
                    // This happens if a PropertyDescriptor or IndexedPropertyDescriptor
                    // constructor fins that the method violates details of the deisgn
                    // pattern, e.g. by having an empty name, or a getter returning
                    // void , or whatever.
                    pd = null;
                }

                if (pd != null) {
                    // If this class or one of its base classes is a PropertyChange
                    // source, then we assume that any properties we discover are "bound".
                    if (propertyChangeSource) {
                        pd.setBound(true);
                    }
                    addPropertyDescriptor(pd);
                }
            }
        }
        processPropertyDescriptors();

        // Allocate and populate the result array.
        PropertyDescriptor result[] =
                properties.values().toArray(new PropertyDescriptor[properties.size()]);

        // Set the default index.
        if (defaultPropertyName != null) {
            for (int i = 0; i < result.length; i++) {
                if (defaultPropertyName.equals(result[i].getName())) {
                    defaultPropertyIndex = i;
                }
            }
        }

        return result;
    }

8、主要看看这两段代码,第一段为获取方法的入参和出参,第二段判断只有入参的参数为一个,返回值是void,且方法名以set作为前缀的,才会被当做writeMethod,即写入方法。

像BeanCopier依赖Introspector的writeMethod对目标类赋值的工具,在转换使用了@Accessors(chain = true)注解的类时,在获取属性描述PropertyDescriptor就不会返回这个属性的writeMethod属性,就相当于该类的属性没有“写入方法”,这就造成了拷贝对象过程中出现空指针问题。

         String name = method.getName();
                Class<?>[] argTypes = method.getParameterTypes();
                Class<?> resultType = method.getReturnType();
    
 else if (argCount == 1) {
                        if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) {
                            pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null);
                        } else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) {
                            // Simple setter
                            pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method);
                            if (throwsException(method, PropertyVetoException.class)) {
                                pd.setConstrained(true);
                            }
                        }
                    }

三、解决方法

1、去掉@Accessors(chain = true)注解

2、使用其他的反序列化手段,比如FastJson

Dto dto = JSON.toJavaObject(json, Dto.class)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我认不到你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值