再项目开发中,需要构建组织的树形,菜单的树形,字典树形。感觉相似的代码写了一堆,就想着有没有办法,写个通用的方法去处理下?
学习了《SpringEL详解》,用springEl处理下。
构建树形:
菜单实体:
@Data
@ToString
public class Menu {private Integer id;private String name;private Integer parentId;private Integer sortId;private String url;private List<Menu> subMenus;public Menu(Integer id, String name, String url, Integer parentId) {this.id = id;this.name = name;this.url = url;this.parentId = parentId;this.subMenus = new ArrayList<>();}public Menu(Integer id, String name, Integer parentId) {this.id = id;this.name = name;this.parentId = parentId;this.subMenus = new ArrayList<>();}public Menu(Integer id, String name, Integer parentId, List<Menu> subMenus) {this.id = id;this.name = name;this.parentId = parentId;this.subMenus = subMenus;}
}
递归的方式:
// 容易出现 大量无效的循环public static List<Menu> recursiveBuildTree(List<Menu> menus, int parentId) {List<Menu> result = new ArrayList<>();for (Menu menu : menus) {if (menu.getParentId().equals(parentId)) {// 递归查找子菜单menu.setSubMenus(recursiveBuildTree(menus, menu.getId()));result.add(menu);}}return result;}
递归的方式,还要主要限制层级和效率的问题
map的方式:
public static List<Menu> buildTree(List<Menu> list, Integer matchParentId) {List<Menu> res = new ArrayList<>();// 创建一个Map,以id为键Map<Integer, List<Menu>> parentMenuMap = new HashMap<>();Integer parentId;for (Menu menu : list) {parentId = menu.getParentId();parentMenuMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(menu);}// 遍历列表,将每个节点添加到其父节点的children列表中for (Menu menu : list) {menu.setSubMenus(parentMenuMap.getOrDefault(menu.getId(), new ArrayList<>()));// 从根节点开始构建树,假设根节点的parentId为0if (matchParentId.equals(menu.getParentId())) {res.add(menu);}}return res;}
测试:
public static void main(String[] args) {List<Menu> listMapTree = initMenus();List<Menu> result = MapBuildTree.buildTree(listMapTree, -1);System.out.println(JSON.toJSONString(result));} public static List<Menu> initMenus(){List<Menu> datas = new ArrayList<>();Menu root = new Menu(1, "root", "/", -1);Menu leaf2 = new Menu(2, "leaf1", "/", 1);Menu leaf3 = new Menu(3, "leaf2", "/", 1);Menu leaf4 = new Menu(4, "leaf11", "/api/v1/leaf11.html", 2);Menu leaf5 = new Menu(5, "leaf12", "/api/v1/leaf12.html", 2);datas.add(root);datas.add(leaf2);datas.add(leaf3);datas.add(leaf4);datas.add(leaf5);return datas;}
通用的方式
递归的方式:
过程:
1,获取到父级id 用于匹配
2,设置子项
3,获取id,用于递归
el 例子- 获取值:
public static void main(String[] args) {Map<String, Object> myMap = new HashMap<>();myMap.put("name", "yan");myMap.put("id", 1222);myMap.put("position", "E");ExpressionParser parser = new SpelExpressionParser();EvaluationContext context = new StandardEvaluationContext();// 设置变量context.setVariable("myMap", myMap);String value1 = parser.parseExpression("#myMap['name']").getValue(context, String.class);System.out.println(value1);Integer value2 = parser.parseExpression("#myMap['id']").getValue(context, Integer.class);System.out.println(value2);String value3 = parser.parseExpression("#myMap['position']").getValue(context, String.class);System.out.println(value3);}
el 例子-设置值:
public static void main(String[] args) {List<Map<String, Object>> listMapTree = initMenuMap();ExpressionParser parser = new SpelExpressionParser();EvaluationContext context = new StandardEvaluationContext();String variableName ="myMap";Map<String, Object> mapList = new HashMap<>();mapList.put("id", 22);mapList.put("parentId", -1);mapList.put("children", new ArrayList<>());// 设置变量// 设置上下文中的变量context.setVariable(variableName, mapList);parser.parseExpression("#"+variableName+"['children']").setValue(context, listMapTree);System.out.println(JSON.toJSONString(mapList));}public static List<Map<String,Object>> initMenuMap(){return changeFormat(initData());}private static List<Map<String,Object>> changeFormat(String areaInfo){JSONArray areaArr = JSONArray.parseArray(areaInfo);return ListUtils.emptyIfNull(areaArr).stream().map(e -> (JSONObject) e).map(e -> (Map<String, Object>)JSONObject.parseObject( e.toJSONString())).collect(Collectors.toList());}public static String initData(){return "[{\"id\":1,\"name\":\"root\",\"parentId\":-1,\"subMenus\":[],\"url\":\"/\"},{\"id\":2,\"name\":\"leaf1\",\"parentId\":1,\"subMenus\":[],\"url\":\"/\"},{\"id\":3,\"name\":\"leaf2\",\"parentId\":1,\"subMenus\":[],\"url\":\"/\"},{\"id\":4,\"name\":\"leaf11\",\"parentId\":2,\"subMenus\":[],\"url\":\"/api/v1/leaf11.html\"},{\"id\":5,\"name\":\"leaf12\",\"parentId\":2,\"subMenus\":[],\"url\":\"/api/v1/leaf12.html\"}]";}
el 递归处理:
public static<T, E> List<T> buildTree(List<T> trees, String parentIdName,String idName, String childrenName, E matchParentId) {String variableName ="myMap";String parentIdVariName = "#" + variableName + "['" + parentIdName + "']";String childrenVariName = "#"+variableName+"['"+childrenName+"']";String idVariName = "#" + variableName + "['" + idName + "']";return buildTrees(trees, parentIdVariName, idVariName, childrenVariName, matchParentId, variableName);}public static<T, E> List<T> buildTrees(List<T> trees, String parentIdVariName,String idVariName, String childrenVariName, E matchParentId, String variableName ) {ExpressionParser parser = new SpelExpressionParser();EvaluationContext context = new StandardEvaluationContext();List<T> result = new ArrayList<>();for (T node : trees) {//添加到下级数据中context.setVariable(variableName, node);E parentId = (E) parser.parseExpression(parentIdVariName).getValue(context);System.out.println(parentId);if(parentId == matchParentId){E id = (E) parser.parseExpression(idVariName).getValue(context);parser.parseExpression(childrenVariName).setValue(context, buildTrees(trees,parentIdVariName, idVariName, childrenVariName, id, variableName));result.add(node);}}return result;}
测试:
public static void main(String[] args) {List<Menu> listMapTree = initMenus();List<Menu> result2 = RecurElBuildTree.buildTree(listMapTree, "parentId", "id", "subMenus", -1);System.out.println(JSON.toJSONString(result2));}
map的方式:
过程:
1,获取到父级id 用于生成map对象
2,获取id,设置子项
3,再获取父节id,根据表达式判断是否是父级
4,表达式可能多个点话,内容进行替换
el-表达式:
public static void main(String[] args) {ExpressionParser parser = new SpelExpressionParser();EvaluationContext context = new StandardEvaluationContext();String variableName ="myMap";Map<String, Object> mapList = new HashMap<>();mapList.put("id", 22);mapList.put("parentId", -1);mapList.put("children", new ArrayList<>());// 设置变量// 设置上下文中的变量context.setVariable(variableName, mapList);Integer parentId = parser.parseExpression("#"+variableName+"['parentId']").getValue(context, Integer.class);System.out.println(parentId);Boolean equal = parser.parseExpression(parentId +" == -1 || " + parentId+" == null").getValue(context, Boolean.class);System.out.println("express: " + equal);}
map处理:
import cn.hutool.core.util.StrUtil;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class TreeElUtil {public static<T, E> List<T> buildTreee(List<T> trees, String parentIdName,String idName, String childrenName, String parentIdExr) {ExpressionParser parser = new SpelExpressionParser();EvaluationContext context = new StandardEvaluationContext();String variableName ="myMap";// 创建一个Map,以id为键Map<E, List<T>> parentMenuMap = new HashMap<>();String parentIdVariName = "#" + variableName + "['" + parentIdName + "']";for (T node : trees) {context.setVariable(variableName, node);E parentId = (E) parser.parseExpression(parentIdVariName).getValue(context);parentMenuMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(node);}List<T> result = new ArrayList<>();String childrenVariName = "#"+variableName+"['"+childrenName+"']";String idVariName = "#" + variableName + "['" + idName + "']";for (T node : trees) {//添加到下级数据中context.setVariable(variableName, node);E id = (E) parser.parseExpression(idVariName).getValue(context);
// parser.parseExpression(childrenName).setValue(node, parentMenuMap.getOrDefault(id, new ArrayList<>()));parser.parseExpression(childrenVariName).setValue(context, parentMenuMap.getOrDefault(id, new ArrayList<>()));//如里是根节点,加入结构E parentId = (E) parser.parseExpression(parentIdVariName).getValue(context);// 这边的String format = formatResult(parentIdExr, parentId);Boolean parent = parser.parseExpression(format).getValue(context, Boolean.class);if (parent != null && parent) {result.add(node);}}return result;}private static String formatResult(String str, Object... param){String regExp = "\\{}";Pattern pattern = Pattern.compile(regExp);Matcher matcher = pattern.matcher(str);while(matcher.find()){str = StrUtil.format(str, param);;}return str;}}
测试:
map数据:
public static void main(String[] args) {List<Map<String, Object>> listMapTree = initOrgMaps();String parentExr= "{} == -1 || {} == null";List<Map<String, Object>> result = TreeElUtil.buildTreee(listMapTree, "parentOrgId", "orgId", "subOrgs", parentExr);System.out.println(JSON.toJSONString(result));}public static List<Map<String, Object>> initOrgMaps(){List<Map<String, Object>> datas = new ArrayList<>();Map<String, Object> root = generateMap(1, "主部门", -1);Map<String, Object> leaf2 = generateMap(2, "业务部门", 1);Map<String, Object> leaf3 = generateMap(3, "开发部门", 1);Map<String, Object> leaf4 = generateMap(4, "业务部门11", 2);Map<String, Object> leaf5 = generateMap(5, "业务部门12", 2);datas.add(root);datas.add(leaf2);datas.add(leaf3);datas.add(leaf4);datas.add(leaf5);return datas;}public static Map<String, Object> generateMap(Integer orgId, String name, Integer parentOrgId){Map<String, Object> map = new HashMap<>();map.put("orgId", orgId);map.put("parentOrgId", parentOrgId);map.put("name", name);map.put("subOrgs", new ArrayList<>());return map;}
bean格式数据:
org:
@Data
@ToString
public class Org {private Integer orgId;private String name;private Integer parentOrgId;private Integer sortId;private List<Org> subOrgs;public Org(Integer orgId, String name, Integer parentOrgId) {this.orgId = orgId;this.parentOrgId = parentOrgId;this.name = name;this.subOrgs = new ArrayList<>();}
}
测试:
public static void main(String[] args) {List<Org> listMapTree = initOrgs();String parentExr= "{} == -1 || {} == null";List<Org> result = TreeElUtil.buildTreee(listMapTree, "parentOrgId", "orgId", "subOrgs", parentExr);System.out.println(JSON.toJSONString(result));}public static List<Org> initOrgs(){List<Org> datas = new ArrayList<>();Org root = new Org(1, "主部门", -1);Org leaf2 = new Org(2, "业务部门", 1);Org leaf3 = new Org(3, "开发部门", 1);Org leaf4 = new Org(4, "业务部门11", 2);Org leaf5 = new Org(5, "业务部门12", 2);datas.add(root);datas.add(leaf2);datas.add(leaf3);datas.add(leaf4);datas.add(leaf5);return datas;}
总结:
如果相似的代码出现多次,就可以考虑做一个通用的处理。SpringEL的功能很强大,不过这个用java8的特性去处理,看起来更直观。
学习了SpringEl,就想办法多应用,遇到不懂的再学习,这样才会学得更深,才容易举一反三。
关联文章:《java8 构建通用树》