diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index 3a03cfd9a1a..b8f5f92d055 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,10 +27,11 @@ import java.text.MessageFormat; *
When a message is formatted, it will have this kind of form, capturing the prefix * and the error kind: * - *
EL1004E: Type cannot be found 'String'+ *
EL1005E: Type cannot be found 'String'* * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public enum SpelMessage { @@ -255,7 +256,11 @@ public enum SpelMessage { /** @since 4.3.17 */ FLAWED_PATTERN(Kind.ERROR, 1073, - "Failed to efficiently evaluate pattern ''{0}'': consider redesigning it"); + "Failed to efficiently evaluate pattern ''{0}'': consider redesigning it"), + + /** @since 5.3.16 */ + EXCEPTION_COMPILING_EXPRESSION(Kind.ERROR, 1074, + "An exception occurred while compiling an expression"); private final Kind kind; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java index f2a225952a0..d94c18ea5c7 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -110,7 +110,8 @@ public final class SpelCompiler implements Opcodes { return ReflectionUtils.accessibleConstructor(clazz).newInstance(); } catch (Throwable ex) { - throw new IllegalStateException("Failed to instantiate CompiledExpression", ex); + throw new IllegalStateException("Failed to instantiate CompiledExpression for expression: " + + expression.toStringAST(), ex); } } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java index 86ed383c389..660fb23ddc1 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,6 +44,7 @@ import org.springframework.util.Assert; * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class SpelExpression implements Expression { @@ -522,17 +523,34 @@ public class SpelExpression implements Expression { // Compiled by another thread before this thread got into the sync block return true; } - SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader()); - compiledAst = compiler.compile(this.ast); - if (compiledAst != null) { - // Successfully compiled - this.compiledAst = compiledAst; - return true; + try { + SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader()); + compiledAst = compiler.compile(this.ast); + if (compiledAst != null) { + // Successfully compiled + this.compiledAst = compiledAst; + return true; + } + else { + // Failed to compile + this.failedAttempts.incrementAndGet(); + return false; + } } - else { + catch (Exception ex) { // Failed to compile this.failedAttempts.incrementAndGet(); - return false; + + // If running in mixed mode, revert to interpreted + if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { + this.compiledAst = null; + this.interpretedCount.set(0); + return false; + } + else { + // Running in SpelCompilerMode.immediate mode - propagate exception to caller + throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_COMPILING_EXPRESSION); + } } } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java index 5046f58189a..a3ee9cb0835 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java @@ -76,6 +76,21 @@ class SpelCompilerTests { assertThat(expression.getValue(context)).asInstanceOf(BOOLEAN).isTrue(); } + @Test // gh-28043 + void changingRegisteredVariableTypeDoesNotResultInFailureInMixedMode() { + SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.MIXED, null); + SpelExpressionParser parser = new SpelExpressionParser(config); + Expression sharedExpression = parser.parseExpression("#bean.value"); + StandardEvaluationContext context = new StandardEvaluationContext(); + + Object[] beans = new Object[] {new Bean1(), new Bean2(), new Bean3(), new Bean4()}; + + IntStream.rangeClosed(1, 1_000_000).parallel().forEach(count -> { + context.setVariable("bean", beans[count % 4]); + assertThat(sharedExpression.getValue(context)).asString().startsWith("1"); + }); + } + static class OrderedComponent implements Ordered { @@ -121,4 +136,28 @@ class SpelCompilerTests { boolean hasSomeProperty(); } + public static class Bean1 { + public String getValue() { + return "11"; + } + } + + public static class Bean2 { + public Integer getValue() { + return 111; + } + } + + public static class Bean3 { + public Float getValue() { + return 1.23f; + } + } + + public static class Bean4 { + public Character getValue() { + return '1'; + } + } + }