问题背景:业务需求要求,配置树状菜单权限,每个菜单下面有(子菜单树,子按钮数据,子标签树,子数据权限树),正常通过递归list数据可以把子集放到subList里面。
进一步需求:通过每个节点下面的subList,根据类型拆分为
private List<SysResourcesPojoNew> subs;
private List<SysResourcesPojoNew> menusList;
private List<SysResourcesPojoNew> buttonList;
private List<SysResourcesPojoNew> labList;
private List<SysResourcesPojoNew> dataList;
实现操作
1.list 转 Tree
package cn.gewut.base.common.utils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;
/**
* <p>
* list 转 tree
* </p>
*
* @author yin
* @since 2023-07-18
*/
public class ListToTreeUtil {
private String PARENT_FILED_NAME = "parentId" ;
private String CHILD_FILED_NAME = "childList" ;
private String ID_FILED_NAME = "id" ;
public <T> List<T> transferListToTreeR(List<T> tList, String idFiledName, String parenIdFiledName, String childFiledName) throws NoSuchFieldException {
if (CollectionUtils.isEmpty(tList)) {
return tList;
}
if (!ObjectUtils.isEmpty(idFiledName)) {
this.ID_FILED_NAME = idFiledName;
}
if (!ObjectUtils.isEmpty(parenIdFiledName)) {
this.PARENT_FILED_NAME = parenIdFiledName;
}
if (!ObjectUtils.isEmpty(childFiledName)) {
this.CHILD_FILED_NAME = childFiledName;
}
return transferListToTree(tList, ID_FILED_NAME, PARENT_FILED_NAME, CHILD_FILED_NAME);
}
/**
* 列表转树形结构
*
* @param tList 转换前的列表
* @param <T> 范型 T 需要保证转换的类中对应关系 Long类型的 parentId 与指向父级的 id 保证类型中包含 List<T> 的 childList 属性
* @return 转换后的列表
*/
public <T> List<T> transferListToTree(List<T> tList) throws NoSuchFieldException {
if (CollectionUtils.isEmpty(tList)) {
return tList;
}
return transferListToTree(tList, ID_FILED_NAME, PARENT_FILED_NAME, CHILD_FILED_NAME);
}
public <T> List<T> transferListToTree(List<T> tList, String idFieldName, String pidFieldName, String childListFieldName) throws NoSuchFieldException {
if (CollectionUtils.isEmpty(tList)) {
return tList;
}
if (StringUtils.isEmpty(idFieldName) || StringUtils.isEmpty(pidFieldName) || StringUtils.isEmpty(childListFieldName)) {
throw new NoSuchFieldException("属性名称缺失");
}
return tList.stream().filter(item -> {
try {
// 获取父级节点
Field field = item.getClass().getDeclaredField(pidFieldName);
field.setAccessible(Boolean.TRUE);
Long pid = (Long) field.get(item);
return null == pid;
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException("树形结构转换失败");
}
}).map(item -> {
try {
//处理最高级树
Field childField = item.getClass().getDeclaredField(childListFieldName);
childField.setAccessible(Boolean.TRUE);
childField.set(item, getChildList(item, tList, idFieldName, pidFieldName, childListFieldName));
return item;
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("树形结构转换失败");
}
}).collect(Collectors.toList());
}
private <T> List<T> getChildList(T t, List<T> tList, String idFieldName, String pidFieldName, String childListFieldName) throws NoSuchFieldException, IllegalAccessException {
Field parentField = t.getClass().getDeclaredField(idFieldName);
parentField.setAccessible(Boolean.TRUE);
Long id = (Long) parentField.get(t);
return tList.stream().filter(item -> {
try {
// 获取该节点下的所有子节点
Field field = item.getClass().getDeclaredField(pidFieldName);
field.setAccessible(Boolean.TRUE);
Long pid = (Long) field.get(item)==null?0:(Long) field.get(item);
return pid.equals(id);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("树形结构转换失败");
}
}).peek(item -> {
try {
Field childField = item.getClass().getDeclaredField(childListFieldName);
childField.setAccessible(Boolean.TRUE);
childField.set(item, getChildList(item, tList, idFieldName, pidFieldName, childListFieldName));
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("树形结构转换失败");
}
}).collect(Collectors.toList());
}
}
2.重写实体类多个get方法,拆分子树
```java
private List<SysResourcesPojoNew> subs;
private List<SysResourcesPojoNew> menusList;
private List<SysResourcesPojoNew> buttonList;
private List<SysResourcesPojoNew> labList;
private List<SysResourcesPojoNew> dataList;
public List<SysResourcesPojoNew> getMenusList() {
List<SysResourcesPojoNew> list = new ArrayList<>();
try {
if (!ObjectUtils.isEmpty(getSubs())) {
for (SysResourcesPojoNew sub : getSubs()) {
if ("1".equals(sub.getRestype())) {
list.add(sub);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
if(list.size()==0){
return null;
}
return list;
}
public List<SysResourcesPojoNew> getLabList() {
List<SysResourcesPojoNew> list = new ArrayList<>();
try {
if (!ObjectUtils.isEmpty(getSubs())) {
for (SysResourcesPojoNew sub : getSubs()) {
if ("3".equals(sub.getRestype())) {
list.add(sub);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (list.size() == 0) {
return null;
}
return list;
}
3.Jackson序列化实体过程,返回到前端 ,执行get方法。
背后原理
Jackson序列化实体过程
(参考 https://blog.csdn.net/My__God/article/details/109435313)
1.查找实体类型对应的Srializer,如果尚未创建则先创建一个再返回
Serializer创建过程:
POJOPropertiesCollector
protected void collectAll()
{
LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>();
// First: gather basic data
_addFields(props);
_addMethods(props);
// 25-Jan-2016, tatu: Avoid introspecting (constructor-)creators for non-static
// inner classes, see [databind#1502]
if (!_classDef.isNonStaticInnerClass()) {
_addCreators(props);
}
_addInjectables(props);
// Remove ignored properties, first; this MUST precede annotation merging
// since logic relies on knowing exactly which accessor has which annotation
_removeUnwantedProperties(props);
// and then remove unneeded accessors (wrt read-only, read-write)
_removeUnwantedAccessor(props);
// Rename remaining properties
_renameProperties(props);
// then merge annotations, to simplify further processing
// 26-Sep-2017, tatu: Before 2.9.2 was done earlier but that prevented some of
// annotations from getting properly merged
for (POJOPropertyBuilder property : props.values()) {
property.mergeAnnotations(_forSerialization);
}
// And use custom naming strategy, if applicable...
PropertyNamingStrategy naming = _findNamingStrategy();
if (naming != null) {
_renameUsing(props, naming);
}
/* Sort by visibility (explicit over implicit); drop all but first
* of member type (getter, setter etc) if there is visibility
* difference
*/
for (POJOPropertyBuilder property : props.values()) {
property.trimByVisibility();
}
/* and, if required, apply wrapper name: note, MUST be done after
* annotations are merged.
*/
if (_config.isEnabled(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME)) {
_renameWithWrappers(props);
}
// well, almost last: there's still ordering...
_sortProperties(props);
_properties = props;
_collected = true;
}
protected void collectAll() {
LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>(); //props是字段数组,
_addFields(props); // 根据object的字段往 props 数组里赋值, 这个时候是不区分private or public的, 同样也不区分@JsonIgnore注解
_addMethods(props); // 根据object的getter方法往 props 数组里赋值
if (!_classDef.isNonStaticInnerClass()) { //是否静态内部类字段(独有的配置, 在此没有深入研究)
_addCreators(props);
}
_removeUnwantedProperties(props); //见文知意: 移除unWant的属性, 此时会移除掉@JsonIgnore标注的字段
//(@JsonIgnore的作用域是field, Getter, Setter), 即便在序列化时的setter方法上标注,也会去除
_removeUnwantedAccessor(props);
_renameProperties(props); //属性重命名: (包括json注解 + getter方法覆盖)
......
_sortProperties(props); //属性排序
_properties = props; //赋值给_properties, _properties会被后面用到
_collected = true;
}
Serializer主要为一个Property定义列表,其中描述了Propery序列化定义,Property定义列表生成过程为:
1)扫描所有的属性,添加到Property列表中;
2)扫描所有的get方法,根据属性名在property列表中寻找对应的Property定义,如果有对应属性,则将get方法设置为该property的get方法,没有对应属性则根据get方法创建一个property定义;
3)遍历property列表,将标记为ignore的属性从property定义中删除;
4)遍历property列表,将标记为ignore的方法从property定义中删除;
5)遍历property列表,将rename后的属性名添加到property列表中;
2.用创建的Serializer生成对应的json串到指定的writer中
如果实体中指定了策略为@JsonInclude(JsonInclude.Include.NON_DEFAULT),则属性值为默认值的属性将会被忽略,其中属性的默认值获取方式为,调用实体的无参构造函数创建一个实例,调用属性对应的get方法,将其返回值作为属性的默认值,如