双剑破万法-递归加反射完成接口数据修改

2 阅读12分钟

前言

时隔一月再来一篇文章,春节玩了八天左右,没有碰学习,算是彻底放松了自己,和家人一起在电视上投屏看了很多的电影,很爽。回到公司的前两天开始学习直到现在,开启了很多新姿势,直播学习(没人看),建了一个小的微信学习群(8个人),让大家陪我一起学习。我自己还玩起了大众点评,写美食点评,还有小红书记录生活,还是很充实的。本篇名字很夸张,其实就是个切面的代码片,思路说不上多么精妙,重要的还是逻辑代码处理吧,写得还算尽善尽美,希望给遇到相同需求的读者们一个思路。

设计思路

经典一句话需求

我们团队最近在负责部门的SC系统重构,页面全部权限配置化,样式搞得也很华丽呼哨。同时因为今年公司搞国际化,所以有中英文切换的需求,本来是前端做好页面的国际化就行,但是页面标题加内容都是后端返回的,所以需要在后端做通用的中英文切换,并且要根据权限隐藏部分返回的数据信息。需求就是一句话的问题,那么接下来就是程序的设计思路。

如何设计流程和数据表

通用处理+针对接口,这两个特性几乎定向指定了切面这个优秀的传统解决手法。再结合接口的两种业务情况读和写,制订了如上的流程设计。

select sm.menu_code        menuCode,--菜单编码
               si.interface_code   interfaceCode,--接口编码
               sfpd.field_key      fieldKey,--接口字段key
               sfpd.field_key_en   fieldKeyEn,--接口字段对于英文值的key
               sfpd.field_type     fieldType,--字段类型,支持number、StringObject
               sfpd.field_length   fieldLength,--字段长度
               sfpd.field_required fieldRequired,--字段是否允许为null
               sra.edit_flag       editFlag,--该字段是否可编辑
               sra.view_flag       viewFlag--该字段是否可查看
        from sys_role_auth sra
                 join sys_role sr on sra.role_id = sr.id and sr.is_delete = 0
                 join sys_menu sm on sm.id = sra.menu_id and sm.is_delete = 0
                 join sys_interface si on sra.interface_id = si.id and si.is_delete = 0
                 join sys_field_pool sfp on sfp.id = sra.field_pool_id and sfp.is_delete = 0
                 join sys_tag_role str on str.role_id = sra.role_id and str.is_delete = 0
                 join sys_tag st on st.id = str.tag_id and st.is_delete = 0 and st.tag_name like '%员工%'
                 join sys_field_pool_detail sfpd on sfpd.pool_id = sfp.id and sfpd.is_delete = 0
        where sra.auth_type = 1

表设计的很仓促,这里也不详细列出来了,就是经典的RBAC模型,用这个获取字段权限的SQL简单描述下表的设计思路。在设计的时候是考虑将接口字段作为权限的最小单位,权限控制的路径是用户->角色->菜单->接口->字段。权限控制除了校验是否可以编辑和查看,还增加了对于类型、长度和是否为NULL的判断,这个其实意义不大,常规来说引入hibernate-validator使用@Valid注解最方便。

这里中英文因为涉及到具体的值的变化,而我不想引入翻译,因此我让开发同事在接口中文值zhVal和英文值enVal对应两个字段zhKey和enKey,默认展示中文zhKey对应的值。如果当前人员属于英文,那就将英文值对于的enKey的enVal覆盖中文zhKey的zhVal,然后还是返回中文zhKey。前端也只用渲染zhKey,因为英文值在后端会替换。

难点破解

@Data
public class IndexVO {
    /**
     * 自我评价
     */
    private List<IndicatorDisplayVO> selfEvaluation;
    /**
     * 我的SC
     */
    private List<MyScVO> myScList;
    /**
     * 团队SC
     */
    private TeamScVO teamSc;
}

@Data
public class TeamScVO {
    /**
     * 方案年度
     */
    private Integer scYear;
    /**
     * 方案名称
     */
    private String planName;
    private String planNameUs;
    /**
     * 等级列表
     */
    private List<LevelVO> levelList;

    @Data
    public static class LevelVO {
        /**
         * SC结果
         */
        private String scValue;
        /**
         * 人像列表
         */
        private List<String> phoneList;
    }
}

通用的问题处理了,接下来就是困难之处了,如何处理如上数据结构的返回值,将之做替换和隐藏。

完整切面代码

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruijie.framework.base.RequestContext;
import com.ruijie.framework.business.exception.BusinessException;
import com.ruijie.framework.common.RemoteResult;
import com.ruijie.scapi.annotation.AuthJudge;
import com.ruijie.scapi.pojo.dto.auth.UserAuthDTO;
import com.ruijie.scapi.pojo.vo.index.IndexVO;
import com.ruijie.scapi.service.IJoinCommentUserService;
import com.ruijie.scapi.service.ISysRoleAuthService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Slf4j
@Component
@Aspect
public class FieldAuthFilterAop {

    private final ISysRoleAuthService roleAuthService;
    private final IJoinCommentUserService commentUserService;

    public FieldAuthFilterAop(ISysRoleAuthService roleAuthService, IJoinCommentUserService commentUserService) {
        this.roleAuthService = roleAuthService;
        this.commentUserService = commentUserService;
    }

    /**
     * 定义切面范围
     */
    @Pointcut("@annotation(com.ruijie.scapi.annotation.AuthJudge)")
    public void Pointcut() {
    }

    @Around("Pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String userId = RequestContext.getCurrentContext().getUserId();
        Object[] objects = joinPoint.getArgs();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            throw new BusinessException("无法获取请求信息");
        } else {
            //转换后获取HttpServletRequest对象
            HttpServletRequest request = attributes.getRequest();
            AuthJudge authJudge = methodSignature.getMethod().getAnnotation(AuthJudge.class);
            boolean query = authJudge.isQuery();
            if (!query) {
                //写操作...
            } else {
                Object obj = joinPoint.proceed(objects);
                if (obj instanceof RemoteResult) {
                    RemoteResult result = (RemoteResult) obj;
                    Object data = ((RemoteResult<?>) obj).getData();
                    String userLanguage = commentUserService.getUserLanguage(userId);
                    if (StringUtils.isBlank(userLanguage)) {
                        throw new BusinessException("当前用户没有引入参评人员配置");
                    } else {
                        List<UserAuthDTO> userAuthList = roleAuthService.getUserAuth(userId);
                        List<UserAuthDTO> employeeAuthList = roleAuthService.listEmployeeAuth();
                        userAuthList.addAll(employeeAuthList);
                        List<String> fieldKeyList = userAuthList.stream()
                                .filter(auth -> auth.getViewFlag() == 1
                                        && request.getServletPath().equals(auth.getInterfaceCode()))
                                .map(UserAuthDTO::getFieldKey).distinct().collect(Collectors.toList());
                        Map<String, String> authMap = userAuthList.stream()
                                .filter(auth -> auth.getViewFlag() == 1 && request.getServletPath().equals(auth.getInterfaceCode()))
                                .collect(Collectors.toMap(UserAuthDTO::getFieldKey, val ->
                                        StringUtils.isNotBlank(val.getFieldKeyEn()) ? val.getFieldKeyEn() : "", (a, b) -> a));
                        if (data instanceof IPage) {
                            IPage page = (IPage) data;
                            List records = page.getRecords();
                            List<Map<String, Object>> disguiseList = new ArrayList<>();
                            JSONArray resListInvert = JSON.parseArray(JSON.toJSONString(records,
                                    SerializerFeature.WRITE_MAP_NULL_FEATURES, SerializerFeature.WriteDateUseDateFormat));
                            for (Object o : resListInvert) {
                                Map<String, Object> disguiseMap = handleTranslation(userLanguage, fieldKeyList, authMap, o, null);
                                disguiseList.add(disguiseMap);
                            }
                            IPage disguisePage = new Page();
                            disguisePage.setCurrent(page.getCurrent());
                            disguisePage.setSize(page.getSize());
                            disguisePage.setTotal(page.getTotal());
                            disguisePage.setPages(page.getPages());
                            disguisePage.setRecords(disguiseList);
                            result.setData(disguisePage);
                        } else if (data instanceof List || data instanceof Set) {
                            List<Map<String, Object>> disguiseList = new ArrayList<>();
                            JSONArray resListInvert = JSON.parseArray(JSON.toJSONString(data,
                                    SerializerFeature.WRITE_MAP_NULL_FEATURES, SerializerFeature.WriteDateUseDateFormat));
                            for (Object o : resListInvert) {
                                Map<String, Object> disguiseMap = handleTranslation(userLanguage, fieldKeyList, authMap, o, null);
                                disguiseList.add(disguiseMap);
                            }
                            result.setData(disguiseList);
                        } else {
                            Map<String, Object> disguiseMap = handleTranslation(userLanguage, fieldKeyList, authMap, data, null);
                            result.setData(disguiseMap);
                        }
                    }
                    return result;
                } else {
                    throw new BusinessException("请使用标准返回值RemoteResult");
                }
            }
        }
    }

    private Map<String, Object> handleTranslation(String userLanguage, List<String> fieldKeyList,
                                                  Map<String, String> authMap, Object obj, Map<String, String> objClassMap) {
        Map<String, Object> disguiseMap = new HashMap<>();
        if (objClassMap == null) {
            objClassMap = findObjectClass(obj);
        }
        JSONObject resInvert = null;
        if (obj instanceof JSONObject) {
            resInvert = (JSONObject) obj;
        } else {
            resInvert = JSON.parseObject(JSON.toJSONString(obj,
                    SerializerFeature.WRITE_MAP_NULL_FEATURES, SerializerFeature.WriteDateUseDateFormat));
        }
        Map<String, String> finalObjClassMap = objClassMap;
        if (resInvert == null) {
            return disguiseMap;
        } else {
            JSONObject finalResInvert = resInvert;
            resInvert.forEach((key, value) -> {
                if (finalObjClassMap.get(key) != null && finalObjClassMap.get(key).contains("ruijie")) {
                    Map<String, Object> map = handleTranslation(userLanguage, fieldKeyList, authMap, value, finalObjClassMap);
                    disguiseMap.put(key, map);
                } else if (value instanceof List || value instanceof Set) {
                    if ("true".equals(finalObjClassMap.get(key + "-generics"))) {
                        disguiseMap.put(key, value);
                    } else {
                        List<Map<String, Object>> disguiseList = new ArrayList<>();
                        JSONArray resListInvert = JSON.parseArray(JSON.toJSONString(value,
                                SerializerFeature.WRITE_MAP_NULL_FEATURES, SerializerFeature.WriteDateUseDateFormat));
                        for (Object o : resListInvert) {
                            Map<String, Object> sheerMap = handleTranslation(userLanguage, fieldKeyList, authMap, o, finalObjClassMap);
                            disguiseList.add(sheerMap);
                        }
                        disguiseMap.put(key, disguiseList);
                    }
                } else {
                    if (fieldKeyList.contains(key)) {
                        if (!userLanguage.equals("中文")) {
                            Object valEn = finalResInvert.get(authMap.get(key));
                            value = Objects.isNull(valEn) ? value : valEn;
                        }
                        if (value instanceof Long) {
                            value = String.valueOf(value);
                        }
                        disguiseMap.put(key, value);
                    }
                }
            });
        }
        return disguiseMap;
    }

    private Map<String, String> findObjectClass(Object obj) {
        Map<String, String> fieldClassMap = new HashMap<>();
        if (obj != null) {
            inspectFields(obj.getClass(), fieldClassMap);
        }
        return fieldClassMap;
    }

    private void inspectFields(Class<?> objClass, Map<String, String> fieldClassMap) {
        Field[] fields = objClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Class<?> fieldClass = field.getType();
            String fieldClassName = fieldClass.getName();
            if (fieldClass.equals(List.class) || fieldClass.equals(Set.class)) {
                //获取字段的类型信息,包括泛型信息 java.util.List<com.ruijie.scapi.pojo.vo.index.IndicatorDisplayVO>
                Type genericType = field.getGenericType();
                //检查泛型类型是否是参数化类型
                if (genericType instanceof ParameterizedType) {
                    //将泛型类型强制转换为 ParameterizedType,以便进一步操作获取泛型的实际类型参数
                    ParameterizedType parameterizedType = (ParameterizedType) genericType;
                    //获取泛型类型中的实际类型参数
                    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                    if (actualTypeArguments.length > 0 && actualTypeArguments[0] instanceof Class) {
                        //将实际类型参数转换为 Class<?> 对象,表示泛型中的类类型。
                        Class<?> listType = (Class<?>) actualTypeArguments[0];
                        fieldClassMap.put(fieldName, fieldClassName);
                        //保留泛型信息
                        boolean generics = false;
                        try {
                            Class<?> primitiveClass = listType.getField("TYPE").get(null).getClass();
                            generics = primitiveClass.isPrimitive();
                        } catch (Exception e) {
                            log.warn("尝试获取其对应的原始类型失败");
                        }
                        if (!generics) {
                            generics = listType.equals(String.class);
                        }
                        fieldClassMap.put(fieldName + "-generics", generics ? "true" : "false");
                        if (listType.getName().contains("ruijie")) {
                            inspectFields(listType, fieldClassMap);
                        }
                    }
                }
            } else {
                //TODO 这里应确保字段名不重复
                fieldClassMap.put(fieldName, fieldClassName);
                //只处理业务类,标识为全限定类名中包含ruijie
                if (fieldClass.getName().contains("ruijie")) {
                    inspectFields(fieldClass, fieldClassMap);
                }
            }
        }
    }
}

完整代码如上,可以直接复制放在本地测试,接下来会逐步拆解分析。

读代码讲解

写操作和读代码类似,但是读涉及到数据替换更加复杂,所以讲解以读的代码为主。这里囊括了常见的三种数据返回类型,IPage分页结果、List/Set和普通Object。

Object obj = joinPoint.proceed(objects);
if (obj instanceof RemoteResult) {
    RemoteResult result = (RemoteResult) obj;
    Object data = ((RemoteResult<?>) obj).getData();
    //-----------业务代码1,获取当前用户的语言,用以中英文切换
    String userLanguage = commentUserService.getUserLanguage(userId);
    if (StringUtils.isBlank(userLanguage)) {
        throw new BusinessException("当前用户没有引入参评人员配置");
        //----------业务代码1
    } else {
        //----------业务代码2,获取用户的字段权限,顺道List转Map,提升后续查询性能
        List<UserAuthDTO> userAuthList = roleAuthService.getUserAuth(userId);
        List<UserAuthDTO> employeeAuthList = roleAuthService.listEmployeeAuth();
        userAuthList.addAll(employeeAuthList);
        List<String> fieldKeyList = userAuthList.stream()
                .filter(auth -> auth.getViewFlag() == 1
                        && request.getServletPath().equals(auth.getInterfaceCode()))
                .map(UserAuthDTO::getFieldKey).distinct().collect(Collectors.toList());
        Map<String, String> authMap = userAuthList.stream()
                .filter(auth -> auth.getViewFlag() == 1 && request.getServletPath().equals(auth.getInterfaceCode()))
                .collect(Collectors.toMap(UserAuthDTO::getFieldKey, val ->
                        StringUtils.isNotBlank(val.getFieldKeyEn()) ? val.getFieldKeyEn() : "", (a, b) -> a));
        //----------业务代码2
        if (data instanceof IPage) {
            //mybatis-plus的分页类
            IPage page = (IPage) data;
            List records = page.getRecords();
            List<Map<String, Object>> disguiseList = new ArrayList<>();
            JSONArray resListInvert = JSON.parseArray(JSON.toJSONString(records,
                    SerializerFeature.WRITE_MAP_NULL_FEATURES, SerializerFeature.WriteDateUseDateFormat));
            for (Object o : resListInvert) {
                //将List中的每一个对象Object都转换成对应的Map<String, Object>
                Map<String, Object> disguiseMap = handleTranslation(userLanguage, fieldKeyList, authMap, o, null);
                disguiseList.add(disguiseMap);
            }
            //重新组装数据并返回
            IPage disguisePage = new Page();
            disguisePage.setCurrent(page.getCurrent());
            disguisePage.setSize(page.getSize());
            disguisePage.setTotal(page.getTotal());
            disguisePage.setPages(page.getPages());
            disguisePage.setRecords(disguiseList);
            result.setData(disguisePage);
        } else if (data instanceof List || data instanceof Set) {
            List<Map<String, Object>> disguiseList = new ArrayList<>();
            JSONArray resListInvert = JSON.parseArray(JSON.toJSONString(data,
                    SerializerFeature.WRITE_MAP_NULL_FEATURES, SerializerFeature.WriteDateUseDateFormat));
            for (Object o : resListInvert) {
                Map<String, Object> disguiseMap = handleTranslation(userLanguage, fieldKeyList, authMap, o, null);
                disguiseList.add(disguiseMap);
            }
            result.setData(disguiseList);
        } else {
            Map<String, Object> disguiseMap = handleTranslation(userLanguage, fieldKeyList, authMap, data, null);
            result.setData(disguiseMap);
        }
    }
    return result;
} else {
    throw new BusinessException("请使用标准返回值RemoteResult");
}

为什么校验obj instanceof RemoteResult?

因为我司通用返回值的类强制要求是RemoteResult,所以这一层校验就是过滤非法返回值用的。

为什么使用SerializerFeature.WRITE_MAP_NULL_FEATURES, SerializerFeature.WriteDateUseDateFormat?

fastjson默认会隐藏值为null的key,这就很坑了,因为List返回的Object里的字段A,有的有值有的没值,转换之后的结果就会发现有的Object因为A没值结果连A这个字段都没有了。

转换后输出实际上是一个将原来的Object转换成Map<String, Object>,这里并没有对Date做特殊处理。默认fastjson会把时间转换成long时间戳,这里为了避免二次操作,直接按照日期格式输出,这样我转换时就不用额外处理了。

/**
 * @param userLanguage 用户语言-中文 OR 英文
 * @param fieldKeyList 用户权限字段集合
 * @param authMap 中英文key映射Map
 * @param obj 当前对象
 * @param objClassMap key为字段名称,value为Class的Map,比如String abc,那么key为abc,value为java.lang.String
 */
private Map<String, Object> handleTranslation(String userLanguage, List<String> fieldKeyList,
                                                  Map<String, String> authMap, Object obj, Map<String, String> objClassMap) {
    Map<String, Object> disguiseMap = new HashMap<>();
    if (objClassMap == null) {
        //获取递归+反射后得到的key为对象,value为Class的Map,方便后续做判断
        objClassMap = findObjectClass(obj);
    }
    JSONObject resInvert = null;
    if (obj instanceof JSONObject) {
        //被转换过,直接使用
        resInvert = (JSONObject) obj;
    } else {
        //将当前对象转换成JSONObject类型,方便后续处理
        resInvert = JSON.parseObject(JSON.toJSONString(obj,
                SerializerFeature.WRITE_MAP_NULL_FEATURES, SerializerFeature.WriteDateUseDateFormat));
    }
    Map<String, String> finalObjClassMap = objClassMap;
    if (resInvert == null) {
        //为null说明就是个null,那么直接返回空map
        return disguiseMap;
    } else {
        JSONObject finalResInvert = resInvert;
        //遍历JSONObject这个Map集合,去判断key是否需要再次递归深入
        resInvert.forEach((key, value) -> {
            if (finalObjClassMap.get(key) != null && finalObjClassMap.get(key).contains("gongsi")) {
                //这里判断get(key).contains("gongsi")是因为我们自定义的类的路径,也就是代码包名中含有gongsi这个标识就说明是我自定义的类不是基本数据类型,那就要继续递归深入
                Map<String, Object> map = handleTranslation(userLanguage, fieldKeyList, authMap, value, finalObjClassMap);
                disguiseMap.put(key, map);
            } else if (value instanceof List || value instanceof Set) {
                //这里是对集合等含泛型类型的类的特殊处理,后续讲解,为true时说明泛型中是基本类型,那么直接取值即可
                if ("true".equals(finalObjClassMap.get(key + "-generics"))) {
                    disguiseMap.put(key, value);
                } else {
                    //false说明泛型中并非基本类型,转换成JSONArray循环继续递归深入
                    List<Map<String, Object>> disguiseList = new ArrayList<>();
                    JSONArray resListInvert = JSON.parseArray(JSON.toJSONString(value,
                            SerializerFeature.WRITE_MAP_NULL_FEATURES, SerializerFeature.WriteDateUseDateFormat));
                    for (Object o : resListInvert) {
                        Map<String, Object> sheerMap = handleTranslation(userLanguage, fieldKeyList, authMap, o, finalObjClassMap);
                        disguiseList.add(sheerMap);
                    }
                    disguiseMap.put(key, disguiseList);
                }
            } else {
                if (fieldKeyList.contains(key)) {
                    //这里判断fieldKeyList字段权限集合中是否包含当前key,包含则塞进结果集中
                    if (!userLanguage.equals("中文")) {
                        //判断中英文,如果是中文就不变,如果是英文,就从authMap获取英文对应的enKey,再拿出enVal覆盖原值
                        Object valEn = finalResInvert.get(authMap.get(key));
                        value = Objects.isNull(valEn) ? value : valEn;
                    }
                    //对Long类型数据做字符串转换,避免前端精度丢失
                    if (value instanceof Long) {
                        value = String.valueOf(value);
                    }
                    disguiseMap.put(key, value);
                }
            }
        });
    }
    return disguiseMap;
}

从这个方法开始就用上了递归,因为返回结果类的层级是不一定的,因此必须要递归深入处理。这个就是真正处理返回结果集的方法,将一个Object转换成Map<String, Object>,模拟了Spring Boot处理返回值成为Json字符串的过程,在这个中间去添加了我的自定义操作。核心思路就是遍历当前对象的key对应的Class是否包含自定义对象,如果有自定义对象就将该对象递归处理,直到没有自定义对象为止。

那么哪些是不用处理的呢?比如String,int之类的基本数据类型,List之类泛型是基本数据类型,还有并非我自定义的对象,比如JSONObject之类的。我给需要处理的对象,都标记了特殊判断,他们都是包路径中含有gongsi这个标识的。其他需要处理的,再扩展特殊判断即可。

private Map<String, String> findObjectClass(Object obj) {
    Map<String, String> fieldClassMap = new HashMap<>();
    if (obj != null) {
        inspectFields(obj.getClass(), fieldClassMap);
    }
    return fieldClassMap;
}

private void inspectFields(Class<?> objClass, Map<String, String> fieldClassMap) {
    Field[] fields = objClass.getDeclaredFields();
    for (Field field : fields) {
        field.setAccessible(true);
        String fieldName = field.getName();
        Class<?> fieldClass = field.getType();
        String fieldClassName = fieldClass.getName();
        if (fieldClass.equals(List.class) || fieldClass.equals(Set.class)) {
            //获取字段的类型信息,包括泛型信息 java.util.List<com.ruijie.scapi.pojo.vo.index.IndicatorDisplayVO>
            Type genericType = field.getGenericType();
            //检查泛型类型是否是参数化类型
            if (genericType instanceof ParameterizedType) {
                //将泛型类型强制转换为 ParameterizedType,以便进一步操作获取泛型的实际类型参数
                ParameterizedType parameterizedType = (ParameterizedType) genericType;
                //获取泛型类型中的实际类型参数
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                if (actualTypeArguments.length > 0 && actualTypeArguments[0] instanceof Class) {
                    //将实际类型参数转换为 Class<?> 对象,表示泛型中的类类型。
                    Class<?> listType = (Class<?>) actualTypeArguments[0];
                    fieldClassMap.put(fieldName, fieldClassName);
                    //保留泛型信息
                    boolean generics = false;
                    try {
                        Class<?> primitiveClass = listType.getField("TYPE").get(null).getClass();
                        generics = primitiveClass.isPrimitive();
                    } catch (Exception e) {
                        log.warn("尝试获取其对应的原始类型失败");
                    }
                    if (!generics) {
                        //因为只能获取基本数据类型是不包含引用类型String的,所以这里要额外判断
                        generics = listType.equals(String.class);
                    }
                    fieldClassMap.put(fieldName + "-generics", generics ? "true" : "false");
                    if (listType.getName().contains("gongsi")) {
                        //如果是自定义类
                        inspectFields(listType, fieldClassMap);
                    }
                }
            }
        } else {
            //TODO 这里应确保字段名不重复
            fieldClassMap.put(fieldName, fieldClassName);
            //只处理业务类,标识为全限定类名中包含gongsi
            if (fieldClass.getName().contains("gongsi")) {
                inspectFields(fieldClass, fieldClassMap);
            }
        }
    }
}

这里截了一张Debug的图,private List selfEvaluation;这个selfEvaluation在fieldClassMap中会存储两个值,"selfEvaluation":"java.util.List"和"selfEvaluation-generics":"false",用于handleTranslation中的判断。说白了findObjectClass这个方法就是通过递归和反射获取key为字段名称,value为Class的Map

写在最后

哎呀妈呀,博主失恋了来着,谈了半年结束了,年前的事儿吧。算是我没把握住,对象很优秀,也是我自己不够好吧,还是要努力改正自己的缺陷。说实在的,还是蛮后悔的,不过开弓没有回头箭,还是先打磨自己吧。博主最近也是蛮充实的,大众点评、小红书、直播学习、微信群,后面把公众号搞起来,对了,私信博主加微信啊,我需要在群里把学习的气氛炒起来,我也会分享我学习中的一些小知识点来着。

磕磕绊绊把这一篇写出来,博主就是不喜欢水文,毕竟写给自己看的,这一篇算是有点水了,有违我的本心。但是为了蹭蹭掘金的活动,同时保持下我更文的热度,也不是不能忍,要喷我的话,私信博主来微信鞭策我更新吧,蟹蟹大家。我是依旧乐观积极的云雨雪, 我要让这痛苦压抑的世界绽放幸福快乐之花,向美好的世界献上祝福!!!