采坑-fastjson序列化调用isXxx方法报错NPE

目录

  • 引出问题
  • 分析原因
  • 解决方法

内容

NPE问题的发生

发生条件
1、在一个POJO类上定义了方法名为isXxx这样的public方法;
2、系统统一拦截器对POJO对象进行JSON格式的日志打印;
3、JSON.toJSON方法的调用采用的是fastJson默认配置(序列化时会同时处理类中的fields、getXxx、isXxx);
4、isXxx方法内部未做判空,抛出NPE;

问题分析

乍一看,都怪isXxx方法内部没做判空,导致NPE的产生;
认真思考一下,还是因为开发同学没意识到,在fastjson做序列化时会会把isXxx方法和getXxx方法同时做调用,做序列化处理;getXxx方法肯定不会抛出NPE异常对吧,这是每个开发同学共知的;那如果我们也把isXxx方法做相同保证,就没问题了;

问题解决

1、先看fastjson的源码—fastjson-1.2.78.jar

com.alibaba.fastjson.util.TypeUtils

1、决策一个Class序列化时,应该有哪些属性


    public static SerializeBeanInfo buildBeanInfo(Class<?> beanType //
            , Map<String,String> aliasMap //
            , PropertyNamingStrategy propertyNamingStrategy //
            , boolean fieldBased //
    ){
        JSONType jsonType = TypeUtils.getAnnotation(beanType,JSONType.class);
        String[] orders = null;
        final int features;
        String typeName = null, typeKey = null;
        if(jsonType != null){
            orders = jsonType.orders();

            typeName = jsonType.typeName();
            if(typeName.length() == 0){
                typeName = null;
            }

            PropertyNamingStrategy jsonTypeNaming = jsonType.naming();
            if (jsonTypeNaming != PropertyNamingStrategy.NeverUseThisValueExceptDefaultValue) {
                propertyNamingStrategy = jsonTypeNaming;
            }

            features = SerializerFeature.of(jsonType.serialzeFeatures());
            for(Class<?> supperClass = beanType.getSuperclass()
                ; supperClass != null && supperClass != Object.class
                    ; supperClass = supperClass.getSuperclass()){
                JSONType superJsonType = TypeUtils.getAnnotation(supperClass,JSONType.class);
                if(superJsonType == null){
                    break;
                }
                typeKey = superJsonType.typeKey();
                if(typeKey.length() != 0){
                    break;
                }
            }

            for(Class<?> interfaceClass : beanType.getInterfaces()){
                JSONType superJsonType = TypeUtils.getAnnotation(interfaceClass,JSONType.class);
                if(superJsonType != null){
                    typeKey = superJsonType.typeKey();
                    if(typeKey.length() != 0){
                        break;
                    }
                }
            }

            if(typeKey != null && typeKey.length() == 0){
                typeKey = null;
            }
        } else{
            features = 0;
        }
        // fieldName,field ,先生成fieldName的快照,减少之后的findField的轮询
        Map<String,Field> fieldCacheMap = new HashMap<String,Field>();
        ParserConfig.parserAllFieldToCache(beanType, fieldCacheMap);
        List<FieldInfo> fieldInfoList = fieldBased
                ? computeGettersWithFieldBase(beanType, aliasMap, false, propertyNamingStrategy) //
                : computeGetters(beanType, jsonType, aliasMap, fieldCacheMap, false, propertyNamingStrategy);
        FieldInfo[] fields = new FieldInfo[fieldInfoList.size()];
        fieldInfoList.toArray(fields);
        FieldInfo[] sortedFields;
        List<FieldInfo> sortedFieldList;
        if(orders != null && orders.length != 0){
            sortedFieldList = fieldBased
                    ? computeGettersWithFieldBase(beanType, aliasMap, true, propertyNamingStrategy) //
                    : computeGetters(beanType, jsonType, aliasMap, fieldCacheMap, true, propertyNamingStrategy);
        } else{
            sortedFieldList = new ArrayList<FieldInfo>(fieldInfoList);
            Collections.sort(sortedFieldList);
        }
        sortedFields = new FieldInfo[sortedFieldList.size()];
        sortedFieldList.toArray(sortedFields);
        if(Arrays.equals(sortedFields, fields)){
            sortedFields = fields;
        }
        return new SerializeBeanInfo(beanType, jsonType, typeName, typeKey, features, fields, sortedFields);
    }

2、仅仅基于Class中的filed做序列化

    public static List<FieldInfo> computeGettersWithFieldBase(
            Class<?> clazz, //
            Map<String,String> aliasMap, //
            boolean sorted, //
            PropertyNamingStrategy propertyNamingStrategy){
        Map<String,FieldInfo> fieldInfoMap = new LinkedHashMap<String,FieldInfo>();
        for(Class<?> currentClass = clazz; currentClass != null; currentClass = currentClass.getSuperclass()){
            Field[] fields = currentClass.getDeclaredFields();
            computeFields(currentClass, aliasMap, propertyNamingStrategy, fieldInfoMap, fields);
        }
        return getFieldInfos(clazz, sorted, fieldInfoMap);
    }

    private static List<FieldInfo> getFieldInfos(Class<?> clazz, boolean sorted, Map<String,FieldInfo> fieldInfoMap){
        List<FieldInfo> fieldInfoList = new ArrayList<FieldInfo>();
        String[] orders = null;
        JSONType annotation = TypeUtils.getAnnotation(clazz,JSONType.class);
        if(annotation != null){
            orders = annotation.orders();
        }
        if(orders != null && orders.length > 0){
            LinkedHashMap<String,FieldInfo> map = new LinkedHashMap<String,FieldInfo>(fieldInfoMap.size());
            for(FieldInfo field : fieldInfoMap.values()){
                map.put(field.name, field);
            }
            for(String item : orders){
                FieldInfo field = map.get(item);
                if(field != null){
                    fieldInfoList.add(field);
                    map.remove(item);
                }
            }
            fieldInfoList.addAll(map.values());
        } else{
            fieldInfoList.addAll(fieldInfoMap.values());
            if(sorted){
                Collections.sort(fieldInfoList);
            }
        }
        return fieldInfoList;
    }

3、除了属性,还要考虑方法
这里就有把isXxx这样的方法的返回值,也作为序列化的一部分输出;


    public static List<FieldInfo> computeGetters(Class<?> clazz, //
                                                 JSONType jsonType, //
                                                 Map<String,String> aliasMap, //
                                                 Map<String,Field> fieldCacheMap, //
                                                 boolean sorted, //
                                                 PropertyNamingStrategy propertyNamingStrategy //
    ){
        Map<String,FieldInfo> fieldInfoMap = new LinkedHashMap<String,FieldInfo>();
        boolean kotlin = TypeUtils.isKotlin(clazz);
        // for kotlin
        Constructor[] constructors = null;
        Annotation[][] paramAnnotationArrays = null;
        String[] paramNames = null;
        short[] paramNameMapping = null;
        Method[] methods = clazz.getMethods();
        Arrays.sort(methods, new MethodInheritanceComparator());
        for(Method method : methods){
            String methodName = method.getName();
            int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
            String label = null;
            if(Modifier.isStatic(method.getModifiers())){
                continue;
            }

            Class<?> returnType = method.getReturnType();
            if(returnType.equals(Void.TYPE)){
                continue;
            }

            if(method.getParameterTypes().length != 0){
                continue;
            }

            if(returnType == ClassLoader.class
                    || returnType == InputStream.class
                    || returnType == Reader.class){
                continue;
            }

            if(methodName.equals("getMetaClass")
                    && returnType.getName().equals("groovy.lang.MetaClass")){
                continue;
            }
            if(methodName.equals("getSuppressed")
                    && method.getDeclaringClass() == Throwable.class){
                continue;
            }

            if(kotlin && isKotlinIgnore(clazz, methodName)){
                continue;
            }
            /**
             *  如果在属性或者方法上存在JSONField注解,并且定制了name属性,不以类上的propertyNamingStrategy设置为准,以此字段的JSONField的name定制为准。
             */
            Boolean fieldAnnotationAndNameExists = false;
            JSONField annotation = TypeUtils.getAnnotation(method, JSONField.class);
            if(annotation == null){
                annotation = getSuperMethodAnnotation(clazz, method);
            }
            if(annotation == null && kotlin){
                if(constructors == null){
                    constructors = clazz.getDeclaredConstructors();
                    Constructor creatorConstructor = TypeUtils.getKotlinConstructor(constructors);
                    if(creatorConstructor != null){
                        paramAnnotationArrays = TypeUtils.getParameterAnnotations(creatorConstructor);
                        paramNames = TypeUtils.getKoltinConstructorParameters(clazz);
                        if(paramNames != null){
                            String[] paramNames_sorted = new String[paramNames.length];
                            System.arraycopy(paramNames, 0, paramNames_sorted, 0, paramNames.length);

                            Arrays.sort(paramNames_sorted);
                            paramNameMapping = new short[paramNames.length];
                            for(short p = 0; p < paramNames.length; p++){
                                int index = Arrays.binarySearch(paramNames_sorted, paramNames[p]);
                                paramNameMapping[index] = p;
                            }
                            paramNames = paramNames_sorted;
                        }
                    }
                }
                if(paramNames != null && paramNameMapping != null && methodName.startsWith("get")){
                    String propertyName = decapitalize(methodName.substring(3));
                    int p = Arrays.binarySearch(paramNames, propertyName);
                    if (p < 0) {
                        for (int i = 0; i < paramNames.length; i++) {
                            if (propertyName.equalsIgnoreCase(paramNames[i])) {
                                p = i;
                                break;
                            }
                        }
                    }
                    if(p >= 0){
                        short index = paramNameMapping[p];
                        Annotation[] paramAnnotations = paramAnnotationArrays[index];
                        if(paramAnnotations != null){
                            for(Annotation paramAnnotation : paramAnnotations){
                                if(paramAnnotation instanceof JSONField){
                                    annotation = (JSONField) paramAnnotation;
                                    break;
                                }
                            }
                        }
                        if(annotation == null){
                            Field field = ParserConfig.getFieldFromCache(propertyName, fieldCacheMap);
                            if(field != null){
                                annotation = TypeUtils.getAnnotation(field, JSONField.class);
                            }
                        }
                    }
                }
            }
            if(annotation != null){
                if(!annotation.serialize()){
                    continue;
                }
                ordinal = annotation.ordinal();
                serialzeFeatures = SerializerFeature.of(annotation.serialzeFeatures());
                parserFeatures = Feature.of(annotation.parseFeatures());
                if(annotation.name().length() != 0){
                    String propertyName = annotation.name();
                    if(aliasMap != null){
                        propertyName = aliasMap.get(propertyName);
                        if(propertyName == null){
                            continue;
                        }
                    }
                    FieldInfo fieldInfo = new FieldInfo(propertyName, method, null, clazz, null, ordinal,
                            serialzeFeatures, parserFeatures, annotation, null, label);
                    fieldInfoMap.put(propertyName, fieldInfo);
                    continue;
                }
                if(annotation.label().length() != 0){
                    label = annotation.label();
                }
            }
            if(methodName.startsWith("get")){
                if(methodName.length() < 4){
                    continue;
                }
                if(methodName.equals("getClass")){
                    continue;
                }
                if(methodName.equals("getDeclaringClass") && clazz.isEnum()){
                    continue;
                }
                char c3 = methodName.charAt(3);
                String propertyName;
                Field field = null;
                if(Character.isUpperCase(c3) //
                        || c3 > 512 // for unicode method name
                        ){
                    if(compatibleWithJavaBean){
                        propertyName = decapitalize(methodName.substring(3));
                    } else{
                        propertyName = TypeUtils.getPropertyNameByMethodName(methodName);
                    }
                    propertyName = getPropertyNameByCompatibleFieldName(fieldCacheMap, methodName, propertyName, 3);
                } else if(c3 == '_'){
                    propertyName = methodName.substring(3);
                    field = fieldCacheMap.get(propertyName);
                    if (field == null) {
                        String temp = propertyName;
                        propertyName = methodName.substring(4);
                        field = ParserConfig.getFieldFromCache(propertyName, fieldCacheMap);
                        if (field == null) {
                            propertyName = temp; //减少修改代码带来的影响
                        }
                    }
                } else if(c3 == 'f'){
                    propertyName = methodName.substring(3);
                } else if(methodName.length() >= 5 && Character.isUpperCase(methodName.charAt(4))){
                    propertyName = decapitalize(methodName.substring(3));
                } else{
                    propertyName = methodName.substring(3);
                    field = ParserConfig.getFieldFromCache(propertyName, fieldCacheMap);
                    if (field == null) {
                        continue;
                    }
                }
                boolean ignore = isJSONTypeIgnore(clazz, propertyName);
                if(ignore){
                    continue;
                }

                if (field == null) {
                    // 假如bean的field很多的情况一下,轮询时将大大降低效率
                    field = ParserConfig.getFieldFromCache(propertyName, fieldCacheMap);
                }

                if(field == null && propertyName.length() > 1){
                    char ch = propertyName.charAt(1);
                    if(ch >= 'A' && ch <= 'Z'){
                        String javaBeanCompatiblePropertyName = decapitalize(methodName.substring(3));
                        field = ParserConfig.getFieldFromCache(javaBeanCompatiblePropertyName, fieldCacheMap);
                    }
                }
                JSONField fieldAnnotation = null;
                if(field != null){
                    fieldAnnotation = TypeUtils.getAnnotation(field, JSONField.class);
                    if(fieldAnnotation != null){
                        if(!fieldAnnotation.serialize()){
                            continue;
                        }
                        ordinal = fieldAnnotation.ordinal();
                        serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                        parserFeatures = Feature.of(fieldAnnotation.parseFeatures());
                        if(fieldAnnotation.name().length() != 0){
                            fieldAnnotationAndNameExists = true;
                            propertyName = fieldAnnotation.name();
                            if(aliasMap != null){
                                propertyName = aliasMap.get(propertyName);
                                if(propertyName == null){
                                    continue;
                                }
                            }
                        }
                        if(fieldAnnotation.label().length() != 0){
                            label = fieldAnnotation.label();
                        }
                    }
                }
                if(aliasMap != null){
                    propertyName = aliasMap.get(propertyName);
                    if(propertyName == null){
                        continue;
                    }
                }
                if(propertyNamingStrategy != null && !fieldAnnotationAndNameExists){
                    propertyName = propertyNamingStrategy.translate(propertyName);
                }
                FieldInfo fieldInfo = new FieldInfo(propertyName, method, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
                        annotation, fieldAnnotation, label);
                fieldInfoMap.put(propertyName, fieldInfo);
            }
            if(methodName.startsWith("is")){
                if(methodName.length() < 3){
                    continue;
                }
                if(returnType != Boolean.TYPE
                        && returnType != Boolean.class){
                    continue;
                }
                char c2 = methodName.charAt(2);
                String propertyName;
                Field field = null;
                if(Character.isUpperCase(c2)){
                    if(compatibleWithJavaBean){
                        propertyName = decapitalize(methodName.substring(2));
                    } else{
                        propertyName = Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
                    }
                    propertyName = getPropertyNameByCompatibleFieldName(fieldCacheMap, methodName, propertyName, 2);
                } else if(c2 == '_'){
                    propertyName = methodName.substring(3);
                    field = fieldCacheMap.get(propertyName);
                    if (field == null) {
                        String temp = propertyName;
                        propertyName = methodName.substring(2);
                        field = ParserConfig.getFieldFromCache(propertyName, fieldCacheMap);
                        if (field == null) {
                            propertyName = temp;
                        }
                    }
                } else if(c2 == 'f'){
                    propertyName = methodName.substring(2);
                } else{
                    propertyName = methodName.substring(2);
                    field = ParserConfig.getFieldFromCache(propertyName, fieldCacheMap);
                    if (field == null) {
                        continue;
                    }
                }
                boolean ignore = isJSONTypeIgnore(clazz, propertyName);
                if(ignore){
                    continue;
                }

                if(field == null) {
                    field = ParserConfig.getFieldFromCache(propertyName, fieldCacheMap);
                }

                if(field == null){
                    field = ParserConfig.getFieldFromCache(methodName, fieldCacheMap);
                }
                JSONField fieldAnnotation = null;
                if(field != null){
                    fieldAnnotation = TypeUtils.getAnnotation(field, JSONField.class);
                    if(fieldAnnotation != null){
                        if(!fieldAnnotation.serialize()){
                            continue;
                        }
                        ordinal = fieldAnnotation.ordinal();
                        serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                        parserFeatures = Feature.of(fieldAnnotation.parseFeatures());
                        if(fieldAnnotation.name().length() != 0){
                            propertyName = fieldAnnotation.name();
                            if(aliasMap != null){
                                propertyName = aliasMap.get(propertyName);
                                if(propertyName == null){
                                    continue;
                                }
                            }
                        }
                        if(fieldAnnotation.label().length() != 0){
                            label = fieldAnnotation.label();
                        }
                    }
                }
                if(aliasMap != null){
                    propertyName = aliasMap.get(propertyName);
                    if(propertyName == null){
                        continue;
                    }
                }
                if(propertyNamingStrategy != null){
                    propertyName = propertyNamingStrategy.translate(propertyName);
                }
                //优先选择get
                if(fieldInfoMap.containsKey(propertyName)){
                    continue;
                }
                FieldInfo fieldInfo = new FieldInfo(propertyName, method, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
                        annotation, fieldAnnotation, label);
                fieldInfoMap.put(propertyName, fieldInfo);
            }
        }
        Field[] fields = clazz.getFields();
        computeFields(clazz, aliasMap, propertyNamingStrategy, fieldInfoMap, fields);
        return getFieldInfos(clazz, sorted, fieldInfoMap);
    }

2、解决问题

方案一: 形成共识,保证isXxx不会NPE

对于一个可能会被fastjson序列化输出的Class,其中的getXxx、isXxx方法,一定要判空,保证不会产生NPE;

方案二: fastjson的处理办法

这里还有两个办法,
1、 JSON.toJSONString时,只输出fields,而不根据getXxx、isXxx两类方法构造出的虚拟fields;

SerializeConfig globalInstance  = new SerializeConfig(true);
System.out.println(JSON.toJSON(family, globalInstance));

2、在Class上添加注解,排出一些属性
针对isBoy方法,就’boy’排出;

@JSONType(ignores = {"xxx"})

代扣参考

/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2021 All Rights Reserved.
 */
package org.hinsteny.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONType;
import com.alibaba.fastjson.serializer.SerializeConfig;

import java.io.Serializable;

/**
 * @author yatao.tyt
 * @version $Id: ToJsonStringOfFields.java, v 0.1 2021-12-13 5:51 PM yatao.tyt Exp$
 */
public class ToJsonStringOfFields {

    public static void main(String[] args) {
        Father father = new Father("MyFather", 30, true);
        Mother mother = new Mother();
        Family family = new Family("MyFamily", father, mother);

        // 默认常规操作--输出后发现Family中多了一个fatherInHome字段,这个就要看下是否符合预期了;
        System.out.println(JSON.toJSON(family));

        family = new Family("MyFamily", null, mother);
        // NPE报错,这里就有点不太能接受了,本想只是把一个对象按照JSON格式化输出,但是由于代码不规范加上数据原因抛出了NPE;
        //System.out.println(JSON.toJSON(family));
        //System.out.println(JSON.toJSONString(family));

        // 说下这个问题产生的原因
        /**
         * 1、团队同学实践DDD开发模式,在一个POJO类上定义了public的领域方法;
         * 2、JSON.toJSON方法的调用采用的是fastJson默认配置(序列化时会同时处理类中的fields、getXxx、isXxx);
         * 3、系统统一拦截器对数据领域对象进行JSON格式的日志打印;
         */

        // 如何解决这个问题呢?
        /**
         * 两个方面分析
         * 1、如果团队同学都用的DDD开发模式,那很容易产生isXxx这样的方法,那我们应该避免系统中对此类对象进行类似于拦截器日志打印JSON字符串输出这类操作;
         * 2、避免不了1的话,那就要把JSON这个工具搞明白,用清楚;
         */
        // 方案一: JSON.toJSONString时,只输出fields,而不根据getXxx、isXxx两类方法构造出的虚拟fields;
        SerializeConfig globalInstance  = new SerializeConfig(true);
        System.out.println(JSON.toJSON(family, globalInstance));
        // 方案二: 在要JSON输出的类上面显示声明要输出哪些属性,排除那些属性;
        /**
         * 在类上添加如下注解
         * <P>@JSONType(ignores = {"fatherInHome"})</P>
         */
        System.out.println(JSON.toJSON(family));

    }

    @JSONType(ignores = {"fatherInHome"})
    public static class Family implements Serializable {

        private static final long serialVersionUID = 7830552878571921405L;

        public Family(String name, Father father, Mother mother) {
            this.name = name;
            this.father = father;
            this.mother = mother;
        }

        private String name;

        private Father father;

        private Mother mother;

        public boolean isFatherInHome() {
            return father.isInHome();
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Father getFather() {
            return father;
        }

        public void setFather(Father father) {
            this.father = father;
        }

        public Mother getMother() {
            return mother;
        }

        public void setMother(Mother mother) {
            this.mother = mother;
        }

        @Override
        public String toString() {
            return "Family{" +
                    "name='" + name + '\'' +
                    ", father=" + father +
                    ", mother=" + mother +
                    '}';
        }
    }

    public static class Father implements Serializable {

        private static final long serialVersionUID = -3850777720843804181L;

        private String name;

        private int age;

        private boolean inHome;

        public Father(String name, int age, boolean inHome) {
            this.name = name;
            this.age = age;
            this.inHome = inHome;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public boolean isInHome() {
            return inHome;
        }

        public void setInHome(boolean inHome) {
            this.inHome = inHome;
        }
    }

    public static class Mother implements Serializable {

        private static final long serialVersionUID = -2758058771419796216L;

        private String name;

        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值