You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring SPEL使用Elvis运算符编译时抛出ArrayIndexOutOfBoundsException原因排查

为什么使用SpEL Elvis操作符的测试用例会抛出ArrayIndexOutOfBoundsException?

我来帮你拆解这个问题——你的测试用例抛出ArrayIndexOutOfBoundsException是Spring SpEL即时编译器处理Elvis操作符(?:)时的一个已知bug,下面给你详细分析原因和解决办法:

问题复现场景

先看你的测试代码核心逻辑:开启了SpEL的即时编译模式,使用Elvis操作符从Map中取值,第一次执行正常,第二次移除第一个key后触发编译时抛出异常:

@Test 
public void spel_elvis_operator_test() {
    String time1 = "time1";
    String time2 = "time2";
    String spelExpression = String.format("get('%s') ?: get('%s')", time1, time2);
    Expression expression = new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null))
            .parseExpression(spelExpression);
    Map<String, Object> data = new HashMap<>();
    data.put(time1, 1000L);
    data.put(time2, 2000L);
    // 第一次执行——解释执行表达式
    assertThat(expression.getValue(new StandardEvaluationContext(data), Long.class)).isEqualTo(1000L);
    data.remove(time1);
    // 第二次执行——尝试编译表达式
    assertThat(expression.getValue(new StandardEvaluationContext(data), Long.class)).isEqualTo(2000L);
}

抛出的异常堆栈:

java.lang.ArrayIndexOutOfBoundsException: -1
at org.springframework.asm.Frame.merge(Frame.java:1501)
at org.springframework.asm.Frame.merge(Frame.java:1478)
at org.springframework.asm.MethodWriter.visitMaxs(MethodWriter.java:1516)
at org.springframework.expression.spel.standard.SpelCompiler.createExpressionClass(SpelCompiler.java:176)
at org.springframework.expression.spel.standard.SpelCompiler.compile(SpelCompiler.java:105)
at org.springframework.expression.spel.standard.SpelExpression.compileExpression(SpelExpression.java:467)
at org.springframework.expression.spel.standard.SpelExpression.checkCompile(SpelExpression.java:437)
at org.springframework.expr...

根本原因

这个问题的核心是Spring SpEL即时编译器的帧合并bug:

  • 第一次执行时,SpEL处于解释模式,会动态处理值的获取和类型适配,完全没问题;
  • 第二次执行时,SpEL会尝试将表达式编译为字节码以优化性能,但在编译Elvis操作符的两个分支时,ASM(Spring用来生成字节码的库)的Frame.merge方法无法正确合并两个分支的栈帧状态,导致出现索引越界的异常。

这个bug通常出现在Elvis操作符的两个分支返回类型在编译阶段无法被明确推断为同一类型的场景,即使实际运行时类型是一致的。

解决方案

这里有三个可行的解决办法:

1. 禁用SpEL编译模式

把SpelCompilerMode.IMMEDIATE改为SpelCompilerMode.OFF,强制使用解释执行模式,直接绕过编译阶段的bug:

Expression expression = new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.OFF, null))
        .parseExpression(spelExpression);

2. 显式指定分支的返回类型

通过显式类型转换,让SpEL编译器能明确推断两个分支的返回类型一致,避免帧合并错误:

String spelExpression = String.format("T(Long).valueOf(get('%s')) ?: T(Long).valueOf(get('%s'))", time1, time2);

3. 升级Spring Framework版本

这个bug已经在Spring Framework的后续版本中被修复(比如5.3.20+、6.0+),如果你的项目允许升级,直接升级到最新稳定版本就能彻底解决问题。

内容的提问来源于stack exchange,提问作者elBastarde

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:48:45