前言
时隔一月再来一篇文章,春节玩了八天左右,没有碰学习,算是彻底放松了自己,和家人一起在电视上投屏看了很多的电影,很爽。回到公司的前两天开始学习直到现在,开启了很多新姿势,直播学习(没人看),建了一个小的微信学习群(8个人),让大家陪我一起学习。我自己还玩起了大众点评,写美食点评,还有小红书记录生活,还是很充实的。本篇名字很夸张,其实就是个切面的代码片,思路说不上多么精妙,重要的还是逻辑代码处理吧,写得还算尽善尽美,希望给遇到相同需求的读者们一个思路。
设计思路
经典一句话需求
我们团队最近在负责部门的SC系统重构,页面全部权限配置化,样式搞得也很华丽呼哨。同时因为今年公司搞国际化,所以有中英文切换的需求,本来是前端做好页面的国际化就行,但是页面标题加内容都是后端返回的,所以需要在后端做通用的中英文切换,并且要根据权限隐藏部分返回的数据信息。需求就是一句话的问题,那么接下来就是程序的设计思路。
如何设计流程和数据表

通用处理+针对接口,这两个特性几乎定向指定了切面这个优秀的传统解决手法。再结合接口的两种业务情况读和写,制订了如上的流程设计。
vbnet
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、String、Object
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,因为英文值在后端会替换。
难点破解
swift
@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;
}
}
通用的问题处理了,接下来就是困难之处了,如何处理如上数据结构的返回值,将之做替换和隐藏。
完整切面代码
ini
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。
ini
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时间戳,这里为了避免二次操作,直接按照日期格式输出,这样我转换时就不用额外处理了。
typescript
/**
* @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这个标识的。其他需要处理的,再扩展特殊判断即可。
scss
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
写在最后
哎呀妈呀,博主失恋了来着,谈了半年结束了,年前的事儿吧。算是我没把握住,对象很优秀,也是我自己不够好吧,还是要努力改正自己的缺陷。说实在的,还是蛮后悔的,不过开弓没有回头箭,还是先打磨自己吧。博主最近也是蛮充实的,大众点评、小红书、直播学习、微信群,后面把公众号搞起来,对了,私信博主加微信啊,我需要在群里把学习的气氛炒起来,我也会分享我学习中的一些小知识点来着。
磕磕绊绊把这一篇写出来,博主就是不喜欢水文,毕竟写给自己看的,这一篇算是有点水了,有违我的本心。但是为了蹭蹭掘金的活动,同时保持下我更文的热度,也不是不能忍,要喷我的话,私信博主来微信鞭策我更新吧,蟹蟹大家。我是依旧乐观积极的云雨雪, 我要让这痛苦压抑的世界绽放幸福快乐之花,向美好的世界献上祝福!!!