摘要:BProxy) 核心职责:为 JavaScript 提供数据库操作接口 public class DBProxy implements ProxyObject {
private final DataSource dataSource; private final ConnectionPoolManager poolManager; private final Context context; private final List<TransactionProxy> activeTransactions; @Override public Object getMember(String key) { switch (key) { case "query": return (ProxyExecutable) arguments -> query(argument事务代理 (TransactionProxy)
核心职责:管理数据库事务,支持自动清理 public class TransactionProxy implements ProxyObject, AutoCloseable {
private final Connection connection; private final Context context; private boolean completed = false; @Override public Object getMember(String key) { switch (key) { case "query": return (ProxyExecutable) arguments -> query(argument:
✅ 实现 AutoCloseable:支持自动清理 ✅ 自动回滚:未提交的事务自动回滚 ✅ 防止重复操作:completed 标志防止重复提交/回滚 ✅ 安全优先:默认回滚而不是提交
HTTP 代理 (HttpProxy) 核心职责:为 JavaScript 提供 HTTP 请求接口 public class HttpProxy implements ProxyObject {
private final Context context;
@Override public Object getMember(String key) { switch (key) { case "get": return (ProxyExecutable) arguments -> { String url = arguments[0].
摘要:本文探讨如何基于 Spring Boot 3 和 GraalVM JavaScript 构建现代企业级低代码接口平台。该架构通过 Spring Boot 3 提供高性能的微服务基础,结合 GraalVM 的轻量级 JavaScript 运行时,实现了动态业务逻辑的热部署与脚本化配置。平台采用声明式接口定义,低代码开发模式,同时通过 GraalVM 的 AOT 编译能力显著提升启动速度和运行效率。核心设计包括多数据源和实时脚本引擎,为企业提供灵活、高效且资源消耗低的快速接口开发解决方案。
Spring Boot 3
GraalVM JavaScript
GraalVM
JavaScript
Spring Boot 3 + GraalJS 技术组合的定位:
Spring Boot 3 + GraalJS
这是一个轻量级但高性能的技术组合,专门针对需要快速接口开发和动态脚本能力的企业场景。与传统的重量级低代码平台相比,这个组合更加聚焦、精简且高效。
解决的问题:
核心职责:管理 GraalVM Context,执行 JavaScript 代码,注入全局对象
@Component public class GraalVMScriptEngine { @Autowired private ConnectionPoolManager poolManager; /** * 执行脚本的核心方法 */ public ScriptExecutionResult execute( Script script, Map<String, Object> params, Integer timeoutSeconds, boolean enableConsoleLog, DataSourceService dataSourceService ) { Context context = null; List<String> consoleLogs = enableConsoleLog ? new ArrayList<>() : null; try { // 1. 创建安全的 GraalVM Context context = createSecureContext(); // 2. 获取 JavaScript 绑定 Value bindings = context.getBindings("js"); // 3. 注入 console 对象(可选) if (enableConsoleLog) { createConsoleObject(context, bindings, consoleLogs); } // 4. 注入 HTTP 对象 injectHttpObject(context, bindings); // 5. 注入数据库对象(懒加载) if (dataSourceService != null) { injectDatabaseObjectsLazy(context, bindings, dataSourceService); } // 6. 注入参数对象 injectParams(context, bindings, params); // 7. 执行脚本(带超时控制) ExecutorService executor = Executors.newSingleThreadExecutor(); Future<ScriptExecutionResult> future = executor.submit(() -> { long startTime = System.currentTimeMillis(); Value result = context.eval("js", script.getCode()); long executionTime = System.currentTimeMillis() - startTime; // 8. 清理未完成的事务 cleanupTransactions(context); return buildSuccessResult(script, result, executionTime, consoleLogs); }); // 9. 等待执行完成(带超时) int timeout = timeoutSeconds != null ? timeoutSeconds : 30; return future.get(timeout, TimeUnit.SECONDS); } catch (TimeoutException e) { return buildTimeoutResult(script, consoleLogs); } catch (Exception e) { return handleExecutionError(script, e, consoleLogs); } finally { if (context != null) { context.close(); } } } /** * 创建安全的 GraalVM Context(沙箱隔离) */ private Context createSecureContext() { return Context.newBuilder("js") .allowAllAccess(false) // 禁止所有访问 .allowIO(IOAccess.NONE) // 禁止 IO 操作 .allowHostAccess(HostAccess.newBuilder() .allowListAccess(true) // 允许访问 List .allowMapAccess(true) // 允许访问 Map .allowArrayAccess(true) // 允许访问数组 .allowPublicAccess(true) // 允许访问公共成员 .build()) .allowHostClassLookup(s -> false) // 禁止类查找 .allowCreateThread(false) // 禁止创建线程 .allowNativeAccess(false) // 禁止本地访问 .allowPolyglotAccess(PolyglotAccess.NONE) // 禁止多语言访问 .build(); } }
关键点:
核心职责:为 JavaScript 提供数据库操作接口
public class DBProxy implements ProxyObject { private final DataSource dataSource; private final ConnectionPoolManager poolManager; private final Context context; private final List<TransactionProxy> activeTransactions; @Override public Object getMember(String key) { switch (key) { case "query": return (ProxyExecutable) arguments -> query(arguments[0].asString(), arguments[1]); case "execute": return (ProxyExecutable) arguments -> execute(arguments[0].asString(), arguments[1]); case "executeAndGetKey": return (ProxyExecutable) arguments -> executeAndGetKey(arguments[0].asString(), arguments[1]); case "beginTransaction": return (ProxyExecutable) arguments -> beginTransaction(); default: return null; } } /** * 查询数据(SELECT) */ private Object query(String sql, Object params) { try (Connection conn = poolManager.getConnection(dataSource.getId()); PreparedStatement stmt = conn.prepareStatement(sql)) { // 设置参数 setParameters(stmt, params); // 执行查询 ResultSet rs = stmt.executeQuery(); // 转换结果为 JavaScript 数组 List<Map<String, Object>> results = convertResultSet(rs); return convertToJavaScriptArray(results); } catch (SQLException e) { throw new DatabaseOperationException("查询失败: " + e.getMessage(), e); } } /** * 执行更新(INSERT/UPDATE/DELETE) */ private int execute(String sql, Object params) { try (Connection conn = poolManager.getConnection(dataSource.getId()); PreparedStatement stmt = conn.prepareStatement(sql)) { setParameters(stmt, params); return stmt.executeUpdate(); } catch (SQLException e) { throw new DatabaseOperationException("执行失败: " + e.getMessage(), e); } } /** * 开启事务 */ private TransactionProxy beginTransaction() { try { Connection conn = poolManager.getConnection(dataSource.getId()); conn.setAutoCommit(false); TransactionProxy tx = new TransactionProxy(conn, context); activeTransactions.add(tx); // 跟踪事务 return tx; } catch (SQLException e) { throw new TransactionException("开启事务失败: " + e.getMessage(), e); } } }
核心职责:管理数据库事务,支持自动清理
public class TransactionProxy implements ProxyObject, AutoCloseable { private final Connection connection; private final Context context; private boolean completed = false; @Override public Object getMember(String key) { switch (key) { case "query": return (ProxyExecutable) arguments -> query(arguments[0].asString(), arguments[1]); case "execute": return (ProxyExecutable) arguments -> execute(arguments[0].asString(), arguments[1]); case "executeAndGetKey": return (ProxyExecutable) arguments -> executeAndGetKey(arguments[0].asString(), arguments[1]); case "commit": return (ProxyExecutable) arguments -> { commit(); return null; }; case "rollback": return (ProxyExecutable) arguments -> { rollback(); return null; }; default: return null; } } /** * 提交事务 */ public void commit() throws SQLException { if (!completed) { connection.commit(); completed = true; closeConnection(); } } /** * 回滚事务 */ public void rollback() throws SQLException { if (!completed) { connection.rollback(); completed = true; closeConnection(); } } /** * 自动清理(实现 AutoCloseable) * 如果事务未完成,自动回滚 */ @Override public void close() { try { if (!completed) { connection.rollback(); completed = true; } closeConnection(); } catch (SQLException e) { // 忽略清理异常 } } }
核心职责:为 JavaScript 提供 HTTP 请求接口
public class HttpProxy implements ProxyObject { private final Context context; @Override public Object getMember(String key) { switch (key) { case "get": return (ProxyExecutable) arguments -> { String url = arguments[0].asString(); Map<String, String> headers = arguments.length > 1 && !arguments[1].isNull() ? extractHeaders(arguments[1]) : new HashMap<>(); int timeout = arguments.length > 2 && !arguments[2].isNull() ? arguments[2].asInt() : 30000; return get(url, headers, timeout); }; case "post": return (ProxyExecutable) arguments -> { String url = arguments[0].asString(); Object body = arguments.length > 1 ? extractBody(arguments[1]) : null; Map<String, String> headers = arguments.length > 2 && !arguments[2].isNull() ? extractHeaders(arguments[2]) : new HashMap<>(); int timeout = arguments.length > 3 && !arguments[3].isNull() ? arguments[3].asInt() : 30000; return post(url, body, headers, timeout); }; default: return null; } } /** * 执行 GET 请求 */ private Object get(String url, Map<String, String> headers, int timeout) { HttpRequest request = HttpRequest.get(url); headers.forEach(request::header); request.timeout(timeout); HttpResponse response = request.execute(); return buildResponse(response); } /** * 执行 POST 请求 */ private Object post(String url, Object body, Map<String, String> headers, int timeout) { HttpRequest request = HttpRequest.post(url); headers.forEach(request::header); request.timeout(timeout); // 设置请求体 if (body instanceof Map) { ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(body); request.body(json); if (!headers.containsKey("Content-Type")) { request.header("Content-Type", "application/json"); } } else if (body != null) { request.body(body.toString()); } HttpResponse response = request.execute(); return buildResponse(response); } /** * 提取请求头(使用 JSON.stringify 转换) */ private Map<String, String> extractHeaders(Value value) { // 使用 JavaScript 的 JSON.stringify 转换为字符串 Value stringifyFunc = context.eval("js", "(function(obj) { return JSON.stringify(obj); })"); Value jsonString = stringifyFunc.execute(value); String json = jsonString.asString(); // 使用 Jackson 将 JSON 字符串转换为 Map ObjectMapper mapper = new ObjectMapper(); Map<String, Object> map = mapper.readValue(json, Map.class); // 转换为 String 类型的 Map Map<String, String> headers = new HashMap<>(); for (Map.Entry<String, Object> entry : map.entrySet()) { headers.put(entry.getKey(), String.valueOf(entry.getValue())); } return headers; } }
设计目标:按需加载数据源,减少资源占用
/** * 注入数据库对象到 JavaScript 上下文(懒加载方式) */ private void injectDatabaseObjectsLazy( Context context, Value bindings, DataSourceService dataSourceService ) { // 缓存已加载的 DBProxy Map<Long, DBProxy> dbProxyCache = new HashMap<>(); // 跟踪所有创建的事务 List<TransactionProxy> activeTransactions = new ArrayList<>(); // 注入 getDB(id) 函数 bindings.putMember("getDB", (ProxyExecutable) arguments -> { long dsId = extractDataSourceId(arguments[0]); // 检查缓存 DBProxy proxy = dbProxyCache.get(dsId); if (proxy != null) { return proxy; } // 懒加载:只在需要时才加载数据源 DataSource dataSource = dataSourceService.getDataSourceWithPassword(dsId); // 初始化连接池(如果还没有初始化) poolManager.initializePoolIfNeeded(dataSource); // 创建 DBProxy proxy = new DBProxy(dataSource, poolManager, context, activeTransactions); dbProxyCache.put(dsId, proxy); return proxy; }); // 注册清理钩子 context.getPolyglotBindings().putMember("__cleanupTransactions", (ProxyExecutable) arguments -> { for (TransactionProxy tx : activeTransactions) { try { tx.close(); // 自动回滚未完成的事务 } catch (Exception e) { // 忽略清理异常 } } activeTransactions.clear(); return null; } ); }
扩展点特性:
getDB(id)
如何扩展:
DataSource
ConnectionPoolManager
injectDatabaseObjectsLazy
设计目标:灵活注入全局对象到 JavaScript 环境
/** * 注入 HTTP 对象到 JavaScript 上下文 */ private void injectHttpObject(Context context, Value bindings) { HttpProxy httpProxy = new HttpProxy(context); bindings.putMember("http", httpProxy); } /** * 注入 console 对象到 JavaScript 上下文 */ private void createConsoleObject(Context context, Value bindings, List<String> consoleLogs) { // 创建 console 对象 String consoleScript = "(function() {" + " var console = {" + " log: function() {" + " var args = Array.prototype.slice.call(arguments);" + " var message = args.map(function(arg) {" + " if (typeof arg === 'object') {" + " try { return JSON.stringify(arg); }" + " catch(e) { return String(arg); }" + " }" + " return String(arg);" + " }).join(' ');" + " __logToJava(message);" + " }" + " };" + " return console;" + "})()"; // 创建 Java 回调函数 bindings.putMember("__logToJava", (ProxyExecutable) arguments -> { if (arguments.length > 0) { String message = arguments[0].asString(); consoleLogs.add(message); } return null; }); // 创建并设置 console 对象 Value consoleObject = context.eval("js", consoleScript); bindings.putMember("console", consoleObject); }
FileProxy
CacheProxy
ProxyObject
execute
bindings.putMember()
示例:添加文件操作对象
// 1. 创建 FileProxy public class FileProxy implements ProxyObject { @Override public Object getMember(String key) { switch (key) { case "read": return (ProxyExecutable) arguments -> readFile(arguments[0].asString()); case "write": return (ProxyExecutable) arguments -> writeFile(arguments[0].asString(), arguments[1].asString()); default: return null; } } } // 2. 注入到 JavaScript private void injectFileObject(Context context, Value bindings) { FileProxy fileProxy = new FileProxy(); bindings.putMember("file", fileProxy); } // 3. 在 execute 方法中调用 injectFileObject(context, bindings);
设计目标:灵活转换 Java 和 JavaScript 对象
/** * 注入参数对象(Java Map -> JavaScript Object) */ private void injectParams(Context context, Value bindings, Map<String, Object> params) { if (params != null && !params.isEmpty()) { // 使用 Jackson 将 Map 转换为 JSON 字符串 ObjectMapper mapper = new ObjectMapper(); String jsonParams = mapper.writeValueAsString(params); // 在 JavaScript 中解析 JSON Value paramsObject = context.eval("js", "(" + jsonParams + ")"); bindings.putMember("params", paramsObject); } else { // 创建空对象 Value emptyObject = context.eval("js", "({})"); bindings.putMember("params", emptyObject); } } /** * 转换 JavaScript 结果为 Java 对象 */ private Object convertToJavaObject(Value value) { if (value == null || value.isNull()) { return null; } if (value.isBoolean()) { return value.asBoolean(); } if (value.isNumber()) { if (value.fitsInInt()) { return value.asInt(); } else if (value.fitsInLong()) { return value.asLong(); } else { return value.asDouble(); } } if (value.isString()) { return value.asString(); } if (value.hasArrayElements()) { List<Object> list = new ArrayList<>(); long size = value.getArraySize(); for (long i = 0; i < size; i++) { list.add(convertToJavaObject(value.getArrayElement(i))); } return list; } if (value.hasMembers()) { Map<String, Object> map = new HashMap<>(); for (String key : value.getMemberKeys()) { map.put(key, convertToJavaObject(value.getMember(key))); } return map; } return value.toString(); }
convertToJavaObject
设计目标:灵活管理多个数据源的连接池
@Component public class ConnectionPoolManager { // 存储所有连接池 private final Map<Long, HikariDataSource> pools = new ConcurrentHashMap<>(); /** * 按需初始化连接池 */ public void initializePoolIfNeeded(DataSource dataSource) { if (!pools.containsKey(dataSource.getId())) { synchronized (this) { if (!pools.containsKey(dataSource.getId())) { initializePool(dataSource); } } } } /** * 初始化连接池 */ public void initializePool(DataSource dataSource) { HikariConfig config = new HikariConfig(); // 基本配置 config.setJdbcUrl(buildJdbcUrl(dataSource)); config.setUsername(dataSource.getUsername()); config.setPassword(PasswordEncryptionUtil.decrypt(dataSource.getPassword())); // 连接池配置 config.setMinimumIdle(dataSource.getMinPoolSize()); config.setMaximumPoolSize(dataSource.getMaxPoolSize()); config.setConnectionTimeout(dataSource.getConnectionTimeout()); config.setIdleTimeout(dataSource.getIdleTimeout()); // 创建连接池 HikariDataSource hikariDataSource = new HikariDataSource(config); pools.put(dataSource.getId(), hikariDataSource); } /** * 获取连接 */ public Connection getConnection(Long dataSourceId) throws SQLException { HikariDataSource pool = pools.get(dataSourceId); if (pool == null) { throw new DataSourceNotFoundException("数据源不存在: " + dataSourceId); } return pool.getConnection(); } /** * 关闭连接池 */ public void closePool(Long dataSourceId) { HikariDataSource pool = pools.remove(dataSourceId); if (pool != null) { pool.close(); } } }
应用场景:DBProxy、HttpProxy、TransactionProxy
// JavaScript 调用 db.query('SELECT * FROM users', []); // 实际执行 DBProxy.getMember("query") -> ProxyExecutable -> query(sql, params)
优势:
应用场景:ConnectionPoolManager
// 根据数据源配置创建连接池 public void initializePool(DataSource dataSource) { HikariConfig config = new HikariConfig(); // 配置连接池... HikariDataSource hikariDataSource = new HikariDataSource(config); pools.put(dataSource.getId(), hikariDataSource); }
应用场景:参数转换
// 不同类型使用不同的转换策略 if (value.isBoolean()) { return value.asBoolean(); } else if (value.isNumber()) { return convertNumber(value); } else if (value.isString()) { return value.asString(); }
应用场景:脚本执行流程
public ScriptExecutionResult execute(...) { // 1. 创建 Context context = createSecureContext(); // 2. 注入全局对象(可扩展) injectHttpObject(context, bindings); injectDatabaseObjectsLazy(context, bindings, dataSourceService); // 3. 执行脚本 Value result = context.eval("js", script.getCode()); // 4. 清理资源 cleanupTransactions(context); context.close(); }
添加新的全局对象:
添加新的数据源类型:
添加新的转换策略:
个人让AI写就好了,每个人写的也不同。
AI
快去上手吧
核心职责:管理数据库事务,支持自动清理 public class TransactionProxy implements ProxyObject, AutoCloseable {
✅ 实现 AutoCloseable:支持自动清理 ✅ 自动回滚:未提交的事务自动回滚 ✅ 防止重复操作:completed 标志防止重复提交/回滚 ✅ 安全优先:默认回滚而不是提交
HTTP 代理 (HttpProxy) 核心职责:为 JavaScript 提供 HTTP 请求接口 public class HttpProxy implements ProxyObject {
private final Context context;
@Override public Object getMember(String key) { switch (key) { case "get": return (ProxyExecutable) arguments -> { String url = arguments[0].
摘要:本文探讨如何基于
Spring Boot 3和GraalVM JavaScript构建现代企业级低代码接口平台。该架构通过Spring Boot 3提供高性能的微服务基础,结合GraalVM的轻量级JavaScript运行时,实现了动态业务逻辑的热部署与脚本化配置。平台采用声明式接口定义,低代码开发模式,同时通过 GraalVM 的 AOT 编译能力显著提升启动速度和运行效率。核心设计包括多数据源和实时脚本引擎,为企业提供灵活、高效且资源消耗低的快速接口开发解决方案。背景
一、核心技术背景
Spring Boot 3 + GraalJS技术组合的定位:这是一个轻量级但高性能的技术组合,专门针对需要快速接口开发和动态脚本能力的企业场景。与传统的重量级低代码平台相比,这个组合更加聚焦、精简且高效。
解决的问题:
系统架构图
核心业务代码
1. 脚本执行引擎 (GraalVMScriptEngine)
核心职责:管理 GraalVM Context,执行 JavaScript 代码,注入全局对象
@Component public class GraalVMScriptEngine { @Autowired private ConnectionPoolManager poolManager; /** * 执行脚本的核心方法 */ public ScriptExecutionResult execute( Script script, Map<String, Object> params, Integer timeoutSeconds, boolean enableConsoleLog, DataSourceService dataSourceService ) { Context context = null; List<String> consoleLogs = enableConsoleLog ? new ArrayList<>() : null; try { // 1. 创建安全的 GraalVM Context context = createSecureContext(); // 2. 获取 JavaScript 绑定 Value bindings = context.getBindings("js"); // 3. 注入 console 对象(可选) if (enableConsoleLog) { createConsoleObject(context, bindings, consoleLogs); } // 4. 注入 HTTP 对象 injectHttpObject(context, bindings); // 5. 注入数据库对象(懒加载) if (dataSourceService != null) { injectDatabaseObjectsLazy(context, bindings, dataSourceService); } // 6. 注入参数对象 injectParams(context, bindings, params); // 7. 执行脚本(带超时控制) ExecutorService executor = Executors.newSingleThreadExecutor(); Future<ScriptExecutionResult> future = executor.submit(() -> { long startTime = System.currentTimeMillis(); Value result = context.eval("js", script.getCode()); long executionTime = System.currentTimeMillis() - startTime; // 8. 清理未完成的事务 cleanupTransactions(context); return buildSuccessResult(script, result, executionTime, consoleLogs); }); // 9. 等待执行完成(带超时) int timeout = timeoutSeconds != null ? timeoutSeconds : 30; return future.get(timeout, TimeUnit.SECONDS); } catch (TimeoutException e) { return buildTimeoutResult(script, consoleLogs); } catch (Exception e) { return handleExecutionError(script, e, consoleLogs); } finally { if (context != null) { context.close(); } } } /** * 创建安全的 GraalVM Context(沙箱隔离) */ private Context createSecureContext() { return Context.newBuilder("js") .allowAllAccess(false) // 禁止所有访问 .allowIO(IOAccess.NONE) // 禁止 IO 操作 .allowHostAccess(HostAccess.newBuilder() .allowListAccess(true) // 允许访问 List .allowMapAccess(true) // 允许访问 Map .allowArrayAccess(true) // 允许访问数组 .allowPublicAccess(true) // 允许访问公共成员 .build()) .allowHostClassLookup(s -> false) // 禁止类查找 .allowCreateThread(false) // 禁止创建线程 .allowNativeAccess(false) // 禁止本地访问 .allowPolyglotAccess(PolyglotAccess.NONE) // 禁止多语言访问 .build(); } }关键点:
2. 数据库代理 (DBProxy)
核心职责:为 JavaScript 提供数据库操作接口
public class DBProxy implements ProxyObject { private final DataSource dataSource; private final ConnectionPoolManager poolManager; private final Context context; private final List<TransactionProxy> activeTransactions; @Override public Object getMember(String key) { switch (key) { case "query": return (ProxyExecutable) arguments -> query(arguments[0].asString(), arguments[1]); case "execute": return (ProxyExecutable) arguments -> execute(arguments[0].asString(), arguments[1]); case "executeAndGetKey": return (ProxyExecutable) arguments -> executeAndGetKey(arguments[0].asString(), arguments[1]); case "beginTransaction": return (ProxyExecutable) arguments -> beginTransaction(); default: return null; } } /** * 查询数据(SELECT) */ private Object query(String sql, Object params) { try (Connection conn = poolManager.getConnection(dataSource.getId()); PreparedStatement stmt = conn.prepareStatement(sql)) { // 设置参数 setParameters(stmt, params); // 执行查询 ResultSet rs = stmt.executeQuery(); // 转换结果为 JavaScript 数组 List<Map<String, Object>> results = convertResultSet(rs); return convertToJavaScriptArray(results); } catch (SQLException e) { throw new DatabaseOperationException("查询失败: " + e.getMessage(), e); } } /** * 执行更新(INSERT/UPDATE/DELETE) */ private int execute(String sql, Object params) { try (Connection conn = poolManager.getConnection(dataSource.getId()); PreparedStatement stmt = conn.prepareStatement(sql)) { setParameters(stmt, params); return stmt.executeUpdate(); } catch (SQLException e) { throw new DatabaseOperationException("执行失败: " + e.getMessage(), e); } } /** * 开启事务 */ private TransactionProxy beginTransaction() { try { Connection conn = poolManager.getConnection(dataSource.getId()); conn.setAutoCommit(false); TransactionProxy tx = new TransactionProxy(conn, context); activeTransactions.add(tx); // 跟踪事务 return tx; } catch (SQLException e) { throw new TransactionException("开启事务失败: " + e.getMessage(), e); } } }关键点:
3. 事务代理 (TransactionProxy)
核心职责:管理数据库事务,支持自动清理
public class TransactionProxy implements ProxyObject, AutoCloseable { private final Connection connection; private final Context context; private boolean completed = false; @Override public Object getMember(String key) { switch (key) { case "query": return (ProxyExecutable) arguments -> query(arguments[0].asString(), arguments[1]); case "execute": return (ProxyExecutable) arguments -> execute(arguments[0].asString(), arguments[1]); case "executeAndGetKey": return (ProxyExecutable) arguments -> executeAndGetKey(arguments[0].asString(), arguments[1]); case "commit": return (ProxyExecutable) arguments -> { commit(); return null; }; case "rollback": return (ProxyExecutable) arguments -> { rollback(); return null; }; default: return null; } } /** * 提交事务 */ public void commit() throws SQLException { if (!completed) { connection.commit(); completed = true; closeConnection(); } } /** * 回滚事务 */ public void rollback() throws SQLException { if (!completed) { connection.rollback(); completed = true; closeConnection(); } } /** * 自动清理(实现 AutoCloseable) * 如果事务未完成,自动回滚 */ @Override public void close() { try { if (!completed) { connection.rollback(); completed = true; } closeConnection(); } catch (SQLException e) { // 忽略清理异常 } } }关键点:
4. HTTP 代理 (HttpProxy)
核心职责:为 JavaScript 提供 HTTP 请求接口
public class HttpProxy implements ProxyObject { private final Context context; @Override public Object getMember(String key) { switch (key) { case "get": return (ProxyExecutable) arguments -> { String url = arguments[0].asString(); Map<String, String> headers = arguments.length > 1 && !arguments[1].isNull() ? extractHeaders(arguments[1]) : new HashMap<>(); int timeout = arguments.length > 2 && !arguments[2].isNull() ? arguments[2].asInt() : 30000; return get(url, headers, timeout); }; case "post": return (ProxyExecutable) arguments -> { String url = arguments[0].asString(); Object body = arguments.length > 1 ? extractBody(arguments[1]) : null; Map<String, String> headers = arguments.length > 2 && !arguments[2].isNull() ? extractHeaders(arguments[2]) : new HashMap<>(); int timeout = arguments.length > 3 && !arguments[3].isNull() ? arguments[3].asInt() : 30000; return post(url, body, headers, timeout); }; default: return null; } } /** * 执行 GET 请求 */ private Object get(String url, Map<String, String> headers, int timeout) { HttpRequest request = HttpRequest.get(url); headers.forEach(request::header); request.timeout(timeout); HttpResponse response = request.execute(); return buildResponse(response); } /** * 执行 POST 请求 */ private Object post(String url, Object body, Map<String, String> headers, int timeout) { HttpRequest request = HttpRequest.post(url); headers.forEach(request::header); request.timeout(timeout); // 设置请求体 if (body instanceof Map) { ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(body); request.body(json); if (!headers.containsKey("Content-Type")) { request.header("Content-Type", "application/json"); } } else if (body != null) { request.body(body.toString()); } HttpResponse response = request.execute(); return buildResponse(response); } /** * 提取请求头(使用 JSON.stringify 转换) */ private Map<String, String> extractHeaders(Value value) { // 使用 JavaScript 的 JSON.stringify 转换为字符串 Value stringifyFunc = context.eval("js", "(function(obj) { return JSON.stringify(obj); })"); Value jsonString = stringifyFunc.execute(value); String json = jsonString.asString(); // 使用 Jackson 将 JSON 字符串转换为 Map ObjectMapper mapper = new ObjectMapper(); Map<String, Object> map = mapper.readValue(json, Map.class); // 转换为 String 类型的 Map Map<String, String> headers = new HashMap<>(); for (Map.Entry<String, Object> entry : map.entrySet()) { headers.put(entry.getKey(), String.valueOf(entry.getValue())); } return headers; } }关键点:
扩展点实现
扩展点 1:懒加载数据源
设计目标:按需加载数据源,减少资源占用
/** * 注入数据库对象到 JavaScript 上下文(懒加载方式) */ private void injectDatabaseObjectsLazy( Context context, Value bindings, DataSourceService dataSourceService ) { // 缓存已加载的 DBProxy Map<Long, DBProxy> dbProxyCache = new HashMap<>(); // 跟踪所有创建的事务 List<TransactionProxy> activeTransactions = new ArrayList<>(); // 注入 getDB(id) 函数 bindings.putMember("getDB", (ProxyExecutable) arguments -> { long dsId = extractDataSourceId(arguments[0]); // 检查缓存 DBProxy proxy = dbProxyCache.get(dsId); if (proxy != null) { return proxy; } // 懒加载:只在需要时才加载数据源 DataSource dataSource = dataSourceService.getDataSourceWithPassword(dsId); // 初始化连接池(如果还没有初始化) poolManager.initializePoolIfNeeded(dataSource); // 创建 DBProxy proxy = new DBProxy(dataSource, poolManager, context, activeTransactions); dbProxyCache.put(dsId, proxy); return proxy; }); // 注册清理钩子 context.getPolyglotBindings().putMember("__cleanupTransactions", (ProxyExecutable) arguments -> { for (TransactionProxy tx : activeTransactions) { try { tx.close(); // 自动回滚未完成的事务 } catch (Exception e) { // 忽略清理异常 } } activeTransactions.clear(); return null; } ); }扩展点特性:
getDB(id)时才加载数据源如何扩展:
DataSource接口ConnectionPoolManagerinjectDatabaseObjectsLazy中添加预加载逻辑扩展点 2:注入全局对象
设计目标:灵活注入全局对象到 JavaScript 环境
/** * 注入 HTTP 对象到 JavaScript 上下文 */ private void injectHttpObject(Context context, Value bindings) { HttpProxy httpProxy = new HttpProxy(context); bindings.putMember("http", httpProxy); } /** * 注入 console 对象到 JavaScript 上下文 */ private void createConsoleObject(Context context, Value bindings, List<String> consoleLogs) { // 创建 console 对象 String consoleScript = "(function() {" + " var console = {" + " log: function() {" + " var args = Array.prototype.slice.call(arguments);" + " var message = args.map(function(arg) {" + " if (typeof arg === 'object') {" + " try { return JSON.stringify(arg); }" + " catch(e) { return String(arg); }" + " }" + " return String(arg);" + " }).join(' ');" + " __logToJava(message);" + " }" + " };" + " return console;" + "})()"; // 创建 Java 回调函数 bindings.putMember("__logToJava", (ProxyExecutable) arguments -> { if (arguments.length > 0) { String message = arguments[0].asString(); consoleLogs.add(message); } return null; }); // 创建并设置 console 对象 Value consoleObject = context.eval("js", consoleScript); bindings.putMember("console", consoleObject); }扩展点特性:
如何扩展:
FileProxy、CacheProxy)ProxyObject接口:定义 JavaScript 可调用的方法execute方法中注入:调用bindings.putMember()示例:添加文件操作对象
// 1. 创建 FileProxy public class FileProxy implements ProxyObject { @Override public Object getMember(String key) { switch (key) { case "read": return (ProxyExecutable) arguments -> readFile(arguments[0].asString()); case "write": return (ProxyExecutable) arguments -> writeFile(arguments[0].asString(), arguments[1].asString()); default: return null; } } } // 2. 注入到 JavaScript private void injectFileObject(Context context, Value bindings) { FileProxy fileProxy = new FileProxy(); bindings.putMember("file", fileProxy); } // 3. 在 execute 方法中调用 injectFileObject(context, bindings);扩展点 3:参数转换机制
设计目标:灵活转换 Java 和 JavaScript 对象
/** * 注入参数对象(Java Map -> JavaScript Object) */ private void injectParams(Context context, Value bindings, Map<String, Object> params) { if (params != null && !params.isEmpty()) { // 使用 Jackson 将 Map 转换为 JSON 字符串 ObjectMapper mapper = new ObjectMapper(); String jsonParams = mapper.writeValueAsString(params); // 在 JavaScript 中解析 JSON Value paramsObject = context.eval("js", "(" + jsonParams + ")"); bindings.putMember("params", paramsObject); } else { // 创建空对象 Value emptyObject = context.eval("js", "({})"); bindings.putMember("params", emptyObject); } } /** * 转换 JavaScript 结果为 Java 对象 */ private Object convertToJavaObject(Value value) { if (value == null || value.isNull()) { return null; } if (value.isBoolean()) { return value.asBoolean(); } if (value.isNumber()) { if (value.fitsInInt()) { return value.asInt(); } else if (value.fitsInLong()) { return value.asLong(); } else { return value.asDouble(); } } if (value.isString()) { return value.asString(); } if (value.hasArrayElements()) { List<Object> list = new ArrayList<>(); long size = value.getArraySize(); for (long i = 0; i < size; i++) { list.add(convertToJavaObject(value.getArrayElement(i))); } return list; } if (value.hasMembers()) { Map<String, Object> map = new HashMap<>(); for (String key : value.getMemberKeys()) { map.put(key, convertToJavaObject(value.getMember(key))); } return map; } return value.toString(); }扩展点特性:
如何扩展:
convertToJavaObject中添加新的类型判断扩展点 4:连接池管理
设计目标:灵活管理多个数据源的连接池
@Component public class ConnectionPoolManager { // 存储所有连接池 private final Map<Long, HikariDataSource> pools = new ConcurrentHashMap<>(); /** * 按需初始化连接池 */ public void initializePoolIfNeeded(DataSource dataSource) { if (!pools.containsKey(dataSource.getId())) { synchronized (this) { if (!pools.containsKey(dataSource.getId())) { initializePool(dataSource); } } } } /** * 初始化连接池 */ public void initializePool(DataSource dataSource) { HikariConfig config = new HikariConfig(); // 基本配置 config.setJdbcUrl(buildJdbcUrl(dataSource)); config.setUsername(dataSource.getUsername()); config.setPassword(PasswordEncryptionUtil.decrypt(dataSource.getPassword())); // 连接池配置 config.setMinimumIdle(dataSource.getMinPoolSize()); config.setMaximumPoolSize(dataSource.getMaxPoolSize()); config.setConnectionTimeout(dataSource.getConnectionTimeout()); config.setIdleTimeout(dataSource.getIdleTimeout()); // 创建连接池 HikariDataSource hikariDataSource = new HikariDataSource(config); pools.put(dataSource.getId(), hikariDataSource); } /** * 获取连接 */ public Connection getConnection(Long dataSourceId) throws SQLException { HikariDataSource pool = pools.get(dataSourceId); if (pool == null) { throw new DataSourceNotFoundException("数据源不存在: " + dataSourceId); } return pool.getConnection(); } /** * 关闭连接池 */ public void closePool(Long dataSourceId) { HikariDataSource pool = pools.remove(dataSourceId); if (pool != null) { pool.close(); } } }扩展点特性:
如何扩展:
关键设计模式
1. 代理模式 (Proxy Pattern)
应用场景:DBProxy、HttpProxy、TransactionProxy
// JavaScript 调用 db.query('SELECT * FROM users', []); // 实际执行 DBProxy.getMember("query") -> ProxyExecutable -> query(sql, params)优势:
2. 工厂模式 (Factory Pattern)
应用场景:ConnectionPoolManager
// 根据数据源配置创建连接池 public void initializePool(DataSource dataSource) { HikariConfig config = new HikariConfig(); // 配置连接池... HikariDataSource hikariDataSource = new HikariDataSource(config); pools.put(dataSource.getId(), hikariDataSource); }优势:
3. 策略模式 (Strategy Pattern)
应用场景:参数转换
// 不同类型使用不同的转换策略 if (value.isBoolean()) { return value.asBoolean(); } else if (value.isNumber()) { return convertNumber(value); } else if (value.isString()) { return value.asString(); }优势:
4. 模板方法模式 (Template Method Pattern)
应用场景:脚本执行流程
public ScriptExecutionResult execute(...) { // 1. 创建 Context context = createSecureContext(); // 2. 注入全局对象(可扩展) injectHttpObject(context, bindings); injectDatabaseObjectsLazy(context, bindings, dataSourceService); // 3. 执行脚本 Value result = context.eval("js", script.getCode()); // 4. 清理资源 cleanupTransactions(context); context.close(); }优势:
总结
核心业务代码特点
扩展点设计原则
如何添加新功能
添加新的全局对象:
ProxyObjectexecute方法中注入添加新的数据源类型:
DataSource接口ConnectionPoolManager添加新的转换策略:
convertToJavaObject中添加类型判断前端界面
结语