
网页调试Java代码
网页调试Java代码
背景
低代码平台中的后端流主要处理数据、执行业务逻辑和与数据库或其他后端服务进行交互的一系列步骤。
但是在页面上,看不到后端执行步骤,所以提出这一想法,在网页debug Java代码。
过程
实现调试之前已知逻辑流是一个类,逻辑流中的节点是类中的每个方法,而且每个逻辑流在被调用的时候都会有一个属于自己的上下文环境 ActionContext
,这个 ActionContext
中包含当前逻辑流调用时候的所有参数信息,那么想实现在页面调试Java代码就简单了,在执行每个方法前,将线程暂停,并将 ActionContext
中的信息返回页面,页面发送指令到后端,进行对应的操作即可。
实现
- 使用
WebSocket
来前后端交互 - 使用锁将线程暂停
最刚开始尝试过使用
wait
或者用线程的notify
,因为文章整理跟当时做功能已经有段时间了,已经忘记当时为什么这几种方案有什么问题了,只记得当时这几种方案都有问题。。。
后面使用的是LockSupport
,关于此工具的使用可以看
-
首先在每个类中生成属于每个线程自己的
debugActionId
-
其次生成代码的时候在每个节点方法开始的第一句,插入一条记录当前方法和当前线程的信息。
DebugUtil.park(debugActionId.get(), "Start", 3056069844134422L);
-
然后在页面调用过程中,如果当前节点ID在调试列表中,使用工具类
LockSupport.park()
暂停线程,并把当前逻辑流的参数发送给页面 -
页面通过
WebSocket
将操作类型发送给后端,后端继续相应的操作。
整体操作比较简单,复杂的是功能实现以后得调试,跟契合当前平台的返回数据格式,因为上下文环境中返回的数据格式并不是页面上的格式,所以需要拼装成页面上熟悉的数据格式。
下面附上工具代码
Click here for code - DebugUtil
publi class DebugUtil {
// 存储debug的信息
private static final Map<Long, DebugContext> debugContextMap = new ConcurrentHashMap<>();
// 如果是逻辑流互相调用 记录调用逻辑流服务节点的id
private static final Map<Long, Map<String, Object>> nextNodeMap = new ConcurrentHashMap<>();
// 断点列表
private static final Set<Long> breakPointList = new CopyOnWriteArraySet<>();
// 记录下一步的所有的节点id
private static final Set<Long> nextStepNodeIdSet = new CopyOnWriteArraySet<>();
// 记录流的debugActionId
private static final List<Long> debugActionIdList = new CopyOnWriteArrayList<>();
private static WebSocketServer webSocketServer;
private static final Map<Long, DebugThread> threadMap = new ConcurrentHashMap<>();
//private static Thread thread;
// 是否忽略断点 用给终止调试用 true: 忽略所有断点
private static boolean ignore = false;
// 标记运行到这里的节点id
private static Long runToNodeId;
@Resource
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
DebugUtil.webSocketServer = applicationContext.getBean(WebSocketServer.class);
}
public static void park(Long debugActionId, String nodeName, Long nodeId) {
if (longIsNotEmpty(debugActionId) && debugActionIdList.contains(debugActionId) && debugContextMap.get(debugActionId) != null) {
DebugContext debugContext = debugContextMap.get(debugActionId);
debugContext.setCurrentNodeName(nodeName);
debugContext.setCurrentNodeId(nodeId);
// 如果有标记的节点id 证明是"运行到这里 或者 步出" 那需要判断是否匹配当前的节点 忽略其他断点
if ((longIsNotEmpty(runToNodeId))) {
if (nodeId.equals(runToNodeId)) {
runToNodeId = 0L;
doPark(debugActionId);
}
} else {
// 是正常的debug
// 如果没有忽略断点而且 节点集合 包含当前节点 停止
if (!ignore && (breakPointList.contains(nodeId) || nextStepNodeIdSet.contains(nodeId))) {
doPark(debugActionId);
}
}
if ("End".equalsIgnoreCase(nodeName)) {
handleEnd(debugActionId);
threadMap.remove(debugActionId);
}
}
}
private static void doPark(Long debugActionId) {
sendMsg(debugActionId);
nextStepNodeIdSet.clear();
LockSupport.park();
}
/**
* 断点放开
*/
public static void unPark(Long debugActionId) {
if (!threadMap.containsKey(debugActionId) && !longIsNotEmpty(runToNodeId)) {
Map<String, Object> errMsgMap = new LinkedHashMap<>();
errMsgMap.put("result", "error");
errMsgMap.put("msg", "调试线程出现异常, 请中断重试");
webSocketServer.sendMessage(errMsgMap);
handleClose();
}
if (!CollectionUtils.isEmpty(debugActionIdList)) {
if (longIsNotEmpty(debugActionId)) {
DebugContext debugContext = debugContextMap.get(debugActionId);
// 如果这次放开断点放开的是结束节点 从Util里删除这个流的数据
DebugThread thread = threadMap.get(debugActionId);
if (debugContext != null && thread != null) {
if (debugContext.getCurrentNodeName().startsWith("End")) {
handleEnd(debugActionId);
threadMap.remove(debugActionId);
}
}
try {
LockSupport.unpark(thread.getThread());
} catch (NullPointerException exception) {
exception.printStackTrace();
}
}
}
}
/**
* 断点放开 全部
*/
public static void unPark() {
if (!CollectionUtils.isEmpty(threadMap)) {
for (DebugThread thread : threadMap.values()) {
LockSupport.unpark(thread.getThread());
}
}
}
/**
* 终止调试
*/
public static void stopDebug() {
ignore = true;
unPark();
}
/**
* 添加开启debug的线程
*/
public static void addDebugThread(Long actionId, DebugThread thread, ActionContext context) {
Long debugActionId = thread.getCId();
String actionName = thread.getName();
System.out.printf("添加debug线程, debugActionId: %s, actionName: %s%n", debugActionId, actionName);
// actionNameMap 和 debugContextMap 不为空 证明这是被调用的逻辑流 不需要覆盖线程信息
//if (CollectionUtils.isEmpty(debugActionIdList) && CollectionUtils.isEmpty(debugContextMap)) {
//DebugUtil.thread = thread;
threadMap.put(debugActionId, thread);
//}
debugActionIdList.add(debugActionId);
debugContextMap.putIfAbsent(debugActionId, new DebugContext(actionId, actionName, thread.getParentId(), context));
}
/**
* 添加开启断点的节点id
*/
public static void addBreakPoint(Long nodeId) {
System.out.printf("添加断点, nodeId: %s%n", nodeId);
breakPointList.add(nodeId);
}
public static void addBreakPoint(List<Long> nodeIdList) {
System.out.printf("添加断点(列表) nodeIdList: %s%n", nodeIdList.stream().map(String::valueOf).collect(Collectors.joining(",")));
breakPointList.clear();
breakPointList.addAll(nodeIdList);
}
/**
* 移除断点
*/
public static void removeBreakPoint(Long nodeId) {
System.out.printf("删除断点, nodeId: %s%n", nodeId);
breakPointList.remove(nodeId);
}
/**
* 运行到这里
*/
public static void runToThis(Long debugActionId, Long nodeId) {
runToNodeId = nodeId;
unPark(debugActionId);
}
public static void addNextStepNodeId(Long debugActionId, List<Long> nodeIdList) {
nextStepNodeIdSet.addAll(nodeIdList);
unPark(debugActionId);
}
/**
* 一个流走完后 清空debug信息
*/
public static void handleEnd(Long debugActionId) {
if (debugContextMap.containsKey(debugActionId) && debugActionIdList.contains(debugActionId)) {
DebugContext debugContext = debugContextMap.get(debugActionId);
Long actionId = debugContext.getActionId();
String actionName = debugContext.getActionName();
log.info(" #END# debugAction: {}, action:{}:{}, 结束, 清空消息", debugActionId, actionId, actionName);
debugActionIdList.remove(debugActionId);
debugContextMap.remove(debugActionId);
threadMap.remove(debugActionId);
nextStepNodeIdSet.remove(debugActionId);
if (CollectionUtils.isEmpty(debugActionIdList)) {
log.info("当前无断点 全部调试结束");
ignore = false;
}
sendMsg(debugActionId);
}
}
/**
* 调试过程中更改参数值
*/
public static void changeParam(Long debugActionId, Map<String, Object> paramMap) {
paramMap.forEach((key, value) -> debugContextMap.get(debugActionId).getContext().putValue(key, value));
sendMsg(debugActionId);
}
public static void markNextNodeId(Long debugActionId, Long currentServerId, Long nextNodeId) {
Map<String, Object> innerNodeMap = new HashMap<>();
innerNodeMap.put("currentServerId", currentServerId);
innerNodeMap.put("nextNodeId", nextNodeId);
nextNodeMap.put(debugActionId, innerNodeMap);
}
public static void handleException() {
log.info("出现异常, 调试结束");
if (!CollectionUtils.isEmpty(debugActionIdList)) {
log.info("清空debug信息 发送消息");
handleClose();
sendMsg(null);
}
}
private static void sendMsg(Long debugActionId) {
Map<String, Object> resultMap = new LinkedHashMap<>();
List<Map<String, Object>> debugActionList = new ArrayList<>();
if (longIsNotEmpty(debugActionId)) {
resultMap.put("currentDebugActionId", debugActionId);
}
debugActionIdList.forEach(currentDebugActionId -> {
Map<String, Object> varMap = new LinkedHashMap<>();
DebugContext context = debugContextMap.get(currentDebugActionId);
Map<String, Object> variablesMap = mapCopy(context.getContext().getVariables());
variablesMap.remove("Entities");
handleLogicList(variablesMap);
varMap.put("currentNodeId", context.getCurrentNodeId());
varMap.put("currentNodeName", context.getCurrentNodeName());
varMap.put("debugActionId", currentDebugActionId);
varMap.put("actionId", context.getActionId());
varMap.put("actionName", context.getActionName());
varMap.put("variables", variablesMap);
varMap.put("currentServerId", 0L);
varMap.put("nextNodeId", 0L);
Long parentId = context.getParentId();
// 获取爸爸的信息
if (longIsNotEmpty(parentId)) {
if (nextNodeMap.containsKey(parentId)) {
varMap.putAll(nextNodeMap.get(parentId));
}
}
varMap.putIfAbsent("parentId", longIsNotEmpty(parentId) ? parentId : 0L);
debugActionList.add(varMap);
});
resultMap.put("actionList", debugActionList);
webSocketServer.sendMessage(resultMap);
}
/**
* 处理WebSocket结束 一般debug结束会调用
*/
public static void handleClose() {
ignore = true;
unPark();
runToNodeId = null;
//thread = null;
//ignore = false;
debugContextMap.clear();
debugActionIdList.clear();
breakPointList.clear();
nextStepNodeIdSet.clear();
log.info("debug completed");
}
private static boolean longIsNotEmpty(Long value) {
return value != null && value != 0L;
}
private static void handleLogicList(Map<String, Object> variablesMap) {
variablesMap.forEach((key, value) -> {
if (value instanceof Map) {
handleLogicList((Map<String, Object>) value);
}
if (value != null && "outPutModel".equalsIgnoreCase(value.getClass().getSimpleName())) {
Map<String, Object> fieldMap = new HashMap<>();
variablesMap.put(key, fieldMap);
Field[] fields = value.getClass().getDeclaredFields();
for (Field field : fields) {
try {
String fieldName = field.getName();
field.setAccessible(true);
Object fieldValue = field.get(value);
if (Map.class.equals(field.getType())) {
Map map = (Map) fieldValue;
handleLogicList(map);
fieldMap.putAll(map);
} else if (LogicList.class.equals(field.getType())) {
LogicList logicList = (LogicList) fieldValue;
fieldMap.put(fieldName, generateLogicListMap(logicList));
} else {
fieldMap.put(fieldName, fieldValue);
}
} catch (Exception exception) {
throw new RuntimeException("获取对象的属性异常:" + exception.getMessage());
}
}
}
if (value instanceof LogicList) {
LogicList<?> list = (LogicList<?>) value;
variablesMap.put(key, generateLogicListMap(list));
}
});
}
@NotNull
private static Map<String, Object> generateLogicListMap(LogicList<?> list) {
Map<String, Object> listMap = new HashMap<>();
listMap.put("Current", list.getCurrent());
listMap.put("List", list);
listMap.put("Empty", list.getEmpty());
listMap.put("Length", list.getLength());
listMap.put("CurrentRowNumber", list.getCurrentRowNumber() == -1 ? 0 : list.getCurrentRowNumber());
return listMap;
}
private static Map<String, Object> mapCopy(Map<String, Object> variablesMap) {
Map<String, Object> resultMap = new HashMap<>();
variablesMap.forEach((key, value) -> {
if (value instanceof Map) {
resultMap.put(key, mapCopy((Map) ((HashMap) value).clone()));
} else if (value instanceof LogicList) {
LogicList cloneList = (LogicList) ((LogicList) value).clone();
cloneList.forEach(item -> {
if (item instanceof Map) {
int index = cloneList.indexOf(item);
cloneList.set(index, mapCopy((Map) ((HashMap) item).clone()));
}
});
resultMap.put(key, cloneList);
} else {
resultMap.put(key, value);
}
});
return resultMap;
}
}
Click here for code - WebSocket
public class WebSocketServer {
private static Session session;
@Autowired
private ObjectMapper objectMapper;
@OnOpen
public void onOpen(Session session) {
WebSocketServer.session = session;
log.info("ws connect");
}
/**
* 连接关闭
* 调用的方法
*/
@OnClose
public void onClose() {
DebugUtil.handleClose();
session = null;
log.info("ws disconnect");
}
public void sendMessage(Object obj) {
if (session != null && session.isOpen()) {
try {
session.getBasicRemote().sendText(objectMapper.writeValueAsString(obj));
log.info("send message success, msg: " + objectMapper.writeValueAsString(obj));
} catch (Exception ex) {
log.error("there has an exception when closing the ws", ex);
}
} else {
log.info("ws connection lost");
}
}
@OnMessage
public void onMessage(String msg) throws JsonProcessingException {
if (!StringUtils.isEmpty(msg)) {
DebugParam debugParam = new ObjectMapper().readValue(msg, DebugParam.class);
Long debugActionId = debugParam.getDebugActionId();
switch (debugParam.getOperationEnum()) {
case CONTINUE:
DebugUtil.unPark(debugActionId);
break;
case PARAM:
DebugUtil.changeParam(debugActionId, debugParam.getParamMap());
break;
case ADD_BREAK_POINT:
DebugUtil.addBreakPoint(debugParam.getNodeId());
break;
case REMOVE_BREAK_POINT:
DebugUtil.removeBreakPoint(debugParam.getNodeId());
break;
case STOP:
DebugUtil.stopDebug();
break;
case RUN_TO:
DebugUtil.runToThis(debugActionId, debugParam.getNodeId());
break;
case NEXT_STEP:
DebugUtil.addNextStepNodeId(debugActionId, debugParam.getNodeIdList());
break;
default:
throw new IllegalArgumentException("未知的操作类型" + debugParam.getOperationEnum());
}
}
}
@OnError
public void onError(Throwable t) {
//什么都不想打印都去掉就好了
log.info("出现未知错误 ");
t.printStackTrace();
}
}
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 Henry's Lib
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果