知序
返回
保存草稿
发布
H1
H2
H3
1
2
3
Excalidraw 图
AI 写作
AI 优化
目录
输入内容后显示目录
Markdown
# 08 - 异常处理 > 这篇解决什么问题:你的 Java 程序调 JS 脚本,脚本跑着跑着报错了——但报出来的是一个你没见过的 `PolyglotException`,错误消息看不懂、拿不到 JS 里那个 `Error` 对象、也拿不回 Java 自己抛出的异常。更糟的是,你调 `e.getCause()` 想挖底层,结果拿到 `null`。跨语言边界上"到底是谁抛的错、怎么把它恢复成我认识的类型",就是这篇的主战场。 > 读完你会知道:为什么跨语言的一切异常都被统一包装成 `PolyglotException`、`isHostException()` 如何一秒分清"错在 JS 还是错在 Java"、`getGuestObject()` / `asHostException()` 各该在什么时候取什么、为什么千万别用 `getCause()` 去挖宿主异常,以及四类典型报错场景的完整处理套路。 --- ## 1. 问题的本质:跨语言边界的异常为什么"特别" ### 1.1 在一个语言里 catch,跟跨语言 catch 完全不是一回事 先想清楚一个前提:异常是**有语言边界**的。在纯 Java 里,`try/catch` 只管 Java 的栈;在纯 JS 里,`try/catch` 只管 JS 的栈。可 GraalJS 是**两种语言跑在同一个 JVM 进程**里,异常可能要跨过这条边界: ``` JS 这一侧 JVM 这条边界 Java 这一侧 ┌──────────────────────┐ ┌─────────────────────┐ ┌──────────────────────────┐ │ JS 运行时错误 │ │ │ │ │ │ JS 主动 throw new │───▶│ PolyglotException │◀───│ 你唯一能 catch 到的异常 │ │ Error(...) │ │ (唯一收口) │ │ │ │ JS 调 Java 方法,Java │ │ │ │ │ │ 内部又抛异常 │────▶ 异常从这边钻进 JS │ │ │ └──────────────────────┘ └─────────────────────┘ └──────────────────────────┘ ``` 关键结论:**不管错误发生在哪里、是哪一边的栈,Java 侧能拿到的异常类型只有一种——`PolyglotException`。** 它是一道"统一收口",把千奇百怪的错误都装进一个盒子里,然后通过盒子上的几个方法,让你再把它拆回原本的面目。 > 一句话:**跨语言边界,所有异常都被压平成一个 `PolyglotException`——你面对的不是"几种异常",而是一个"可拆箱的盒子"。** ### 1.2 它到底能拆出哪些信息 `PolyglotException` 不是简单地丢一行错误消息,而是一个带有一整套判读方法的对象。核心在下面几个: | 方法 | 作用 | 典型返回 | |---|---|---| | `getMessage()` | 人类可读的错误描述 | `notDefinedVar is not defined` | | `isHostException()` | 是不是"宿主(Java)抛的异常穿透过来的" | `boolean` | | `asHostException()` | 取回 Java 原始的 `Throwable` | `IllegalStateException` 等 | | `getGuestObject()` | 取回 JS 侧被抛出的那个 guest 对象 | `Value`(如 `Error`) | | `isSyntaxError()` | 是不是语法错误(思考题会用) | `boolean` | | `getGuestStackTrace()` | guest 侧的原始调用栈 | 栈帧数组 | **核心判读线只有一根:`isHostException()`。** 它是 true 还是 false,决定了你接下来该走哪条取数据的路: ``` isHostException() == true → 原始异常在 asHostException() 里(Java 那边抛的) isHostException() == false → guest 异常,看 getMessage() / getGuestObject() ``` > 一句话:**`isHostException()` 是拆箱说明书的总纲——翻它一下,就知道该取"Java 的原始异常"还是"JS 的 guest 错误"。** 下图把这套"一切异常压平进一个盒子、再按需拆回"的机制画出来:  *图:异常统一包装:PolyglotException* --- ## 2. 四种场景,一条解码链:逐段拆给你看 先给一张"场景速查表"定坐标,再逐个深挖。注意这四类都是同一套 `try { … } catch (PolyglotException e)` 包裹,区别只在"异常内容怎么取"。 | 场景 | 表现 | 关键方法 | |---|---|---| | JS 运行时错误 | `eval`/`execute` 抛 `PolyglotException` | `getMessage()` | | JS 主动 `throw` | 同上,异常里带一个 guest 对象 | `getGuestObject()` 取回 `Error` | | 调用 JS 函数参数错误 | 缺参 → JS 内部 `undefined.length` 报错 | `getMessage()` | | Java 异常穿透 | JS 调 Java 方法,Java 抛异常,穿过 JS 回到 Java | `isHostException()` + `asHostException()` | **运行结果(实测演示):** ``` [JS 运行时错误] 消息: <错误描述,如 notDefinedVar is not defined> isHostException: false [JS 主动抛出] 消息: <order rejected: insufficient stock 等> guestObject: Error: order rejected: insufficient stock [调用 JS 函数参数错误] 消息: <undefined.length 相关错误> [Java 异常穿透] isHostException: true 原始异常类型: IllegalStateException 原始异常消息: database connection lost ``` ### 2.1 场景一:JS 运行时错误——最常见的那类 ```java try { context.eval("js", "notDefinedVar.someMethod()"); } catch (PolyglotException e) { System.out.println("[JS 运行时错误]"); System.out.println(" 消息: " + e.getMessage()); System.out.println(" isHostException: " + e.isHostException()); } ``` `notDefinedVar` 压根没定义就 `.someMethod()`,JS 引擎在执行阶段抛运行时错误。它**不是** Java 这边抛的,所以 `isHostException()` 是 `false`,错误描述从 `getMessage()` 拿。 这里要补一个认知:**这条异常走到你面前时,它经历了"JS 引擎解释 → 类型检查失败 → 抛 guest 异常 → Polyglot 层包一层 → 冒到 Java"**。你看到的 `getMessage()` 是 Polyglot 层提炼过的原始 JS 错误描述,底层的类型信息(`ReferenceError`、`TypeError` 之类)需要通过 guest 路径进一步探,但绝大多数场景只要消息就够了。 > 一句话:**运行时错误的特征是 `isHostException()==false`,只读 `getMessage()` 即可——它拿到的是 JS 自己的错误描述。** ### 2.2 场景二:JS 主动 throw——把丢出来的对象抓回来 ```java try { context.eval("js", "throw new Error('order rejected: insufficient stock')"); } catch (PolyglotException e) { System.out.println("[JS 主动抛出]"); System.out.println(" 消息: " + e.getMessage()); System.out.println(" guestObject: " + e.getGuestObject()); } ``` 和场景一的差别:JS 是**主动** `throw new Error(...)`,被抛出的那个 `Error` 对象被完整保存在 `getGuestObject()` 里。它是什么?一个 `Value`——也就是一个跨语言包装的 guest 对象。你不光能 `print` 它,还能进一步像操作任何 `Value` 一样操作它: ```java Object thrown = e.getGuestObject(); // 取回被抛的 JS 对象 // 若它是对象,可 getMember("message") 精确取消息字段 ``` 这给了 Java 侧一个"与错误本体交互"的能力,而不只是读一行字符串。业务上常拿它做结构化错误处理:下游判断 `guest.getMessage()` 判断错误码,而不是去 parse 整串消息。 > 一句话:**JS 主动 throw 的对象就不是一行消息了,而是 `getGuestObject()` 里的一个可操作的 `Value`。** ### 2.3 场景三:JS 函数缺参——异常从你预期的"调用"侧钻出来 ```java Value fn = context.eval("js", "x => x.length > 0"); fn.execute(); // 缺少参数 -> JS 端 undefined.length 报错 ``` 这条最容易踩:你的 Java 代码**没有主动抛错**,JS 函数也没写 `throw`,但你还是收到了 `PolyglotException`。原因在 JS 的动态性——参数 `x` 没传,函数体内 `x` 是 `undefined`,`undefined.length` 本身就是一次类型错误。它内部照样冒成 guest 异常,`getMessage()` 描述它。 这四类场景你完全可以只记一条主线:**只要 JS 执行出错,统一冒成 `PolyglotException`,先用 `isHostException()` 分流,再决定读消息还是读对象。** 缺参只是"触发错误的地方"不同,收口方式完全一样。 > 一句话:**缺参这种"调用侧失误"也会冒成 `PolyglotException`——记住一条主线就够:先分流、再读取。** ### 2.4 场景四:Java 异常穿透——最需要留意的一类 前面三类的异常"出身"都是 JS。第四类相反:**异常是从你的 Java 方法里抛出来的,经过 JS 再穿回 Java**。典型链路如下(假设已开启宿主访问): ```java try (Context hostCtx = Context.newBuilder("js") .allowHostAccess(HostAccess.ALL) .allowHostClassLookup(s -> true) .build()) { try { hostCtx.eval("js", "Java.type('com.example.BusinessService').boom()"); } catch (PolyglotException e) { System.out.println("[Java 异常穿透]"); System.out.println(" isHostException: " + e.isHostException()); Throwable host = e.asHostException(); System.out.println(" 原始异常类型: " + host.getClass().getSimpleName()); System.out.println(" 原始异常消息: " + host.getMessage()); } } ``` 链路拆开看是这样: ``` JS: Java.type('com.example.BusinessService').boom() │ ▼ 调 Java 静态方法 boom() Java: boom() 抛 IllegalStateException("database connection lost") │ ▼ 异常穿过 JVM 边界挤进 JS JS: 那个调用点立刻失败,guest 侧拿到的异常"挂着一个宿主异常" │ ▼ 冒回 Java Java: catch(PolyglotException) → isHostException()==true ``` 此时两个判别信号都指向"宿主侧": - `isHostException()` 为 `true`:原始异常来自宿主(Java 方法) - **务必用 `e.asHostException()` 取回原来的 `Throwable`**——类型是 `IllegalStateException`,消息是 `database connection lost` 对应的 Java 方法就是个普通抛异常的方法: ```java public static void boom() { throw new IllegalStateException("database connection lost"); } ``` **这一类的价值**:Java 侧不仅能知道"调 JS 出错了",还能**恢复成自己能 `catch` 的原始异常语义**——继续按它原本的类型(`IllegalStateException`、自定义业务异常)做分支处理,而不是所有东西都被压成一句含糊的 Polyglot 消息。 > 一句话:**Java 异常穿透 = `isHostException()==true` + `asHostException()` 取回原始 `Throwable`,让你失而复得地类型安全地处理它。** --- ## 3. 两个最容易用错的关键方法:它俩各管哪一头 ### 3.1 `getGuestObject()` vs `asHostException()`——判据只有一个 先把最容易混淆的一对讲透彻: | 方法 | 适用场景 | 返回 | 对应 `isHostException()` | |---|---|---|---| | `getGuestObject()` | JS 主动 throw 的对象(guest 异常) | `Value`(如 `Error`) | `false` | | `asHostException()` | JS 调 Java 时 Java 方法抛的原始异常 | `Throwable`(如 `IllegalStateException`) | `true` | 记忆锚点一句话:**"guest" 和 "host" 各归各——`getGuestObject` 拿 JS 抛的对象,`asHostException` 拿 Java 抛的异常;谁被 `isHostException()` 判定,就找谁要。** ### 3.2 为什么万万别用 `getCause()` 去挖 Java 原始异常 这是本系列实测踩到的一个隐蔽坑(后文踩坑篇第 7 条会再展开)。很多人拿到 `PolyglotException` 后,习惯性地想"挖 cause 链"找底层,于是: ```java Throwable root = e.getCause(); // ❌ 宿主异常时经常拿到 null ``` 问题在于:**宿主(Java)原始异常不一定挂在 `PolyglotException` 的 cause 链上。** `getCause()` 是给"由 Java 异常经标准 cause 链向外传播"用的;而跨语言的宿主异常走的是 Polyglot 专门的语义通道,官方为此提供了语义明确且必定可用的 `asHostException()`。 ```java if (e.isHostException()) { Throwable host = e.asHostException(); // ✅ 正确取法 } ``` > 一句话:**`getCause()` 在宿主异常路径上可能返回 `null`——别省事,认准 `asHostException()`。** --- ## 4. 一处要留意的:怎么知道"这到底算不算语法错误" 异常判定的最后一个维度是"错误发生在什么时候"。`isSyntaxError()` 可以区分「源码根本没编过(语法阶段)」和「运行时才爆」这两类:编译期语法错、类型错返回 true,运行期错误返回 false。这让 Java 侧能对"错误发生时机"做不一样的处理(比如语法错直接判为配置问题,运行错才去找运行时原因)。详见思考题第 1 问。 > 一句话:**错误分"没编过去"和"跑挂了"两类,`isSyntaxError()` 帮你把前者判别出来。** --- ## 5. 本篇小结 ```text ┌──────────────────────────────────────────────────────┐ │ 08 异常处理 要带走的话 │ ├──────────────────────────────────────────────────────┤ │ 1. 跨语言一切异常统一收口成 PolyglotException │ │ 2. 判读只看一根线:isHostException() │ │ 3. true → asHostException() 拿 Java 原始异常 │ │ 4. false → getMessage() / getGuestObject() 拿 JS 侧 │ │ 5. 千万别用 getCause() 挖宿主异常(可能为 null) │ │ 6. 语法错可被 isSyntaxError() 区分出来 │ └──────────────────────────────────────────────────────┘ │ v 你已经能把任何跨语言异常"拆回原本的样子" ──→ 下一步:把不可信脚本关进沙箱执行 ``` > 一句话收束:**不 bind 具体错误,先分流:`isHostException` 一判,`asHostException`/`getGuestObject` 一对,异常就原形毕露。** ## 思考题(自测) 1. 捕获 `PolyglotException` 后,如何判断它是语法错误还是运行时错误?(提示:`PolyglotException.isSyntaxError()`)那"少传一个参数"导致的 `undefined.length` 属于哪一类? 2. 场景四里,如果 JS 端自己在 `try/catch` 里把 Java 异常吞掉了,Java 侧还能 catch 到吗?异常传播的边界到底在哪里? 3. 生产环境里,把一条来自 JS 规则引擎的英文异常消息,映射成用户能看懂的友好业务提示,你会在哪一层做这个翻译?为什么不在 `catch` 处逐条 if/else?
预览
发布文章
添加摘要让读者更快了解文章内容
取消
确认发布
AI 写作助手
输入一个主题,AI 将为你生成一篇 Markdown 博客文章
AI 正在写作中...
取消
开始生成
AI 优化助手
描述你的修改需求,AI 将在原文基础上进行补充或微调
AI 正在优化中...
取消
开始优化
画布
从已有图里选
✕
图名
保存后生成 /images/excalidraw/<图名>.svg(只能用字母、数字、短横线、下划线、点)
取消
保存并插入