Skip to content

Commit 2c0a505

Browse files
committed
Thread-safe compiled expression evaluation in SpelExpression
Closes gh-24265
1 parent 0583b33 commit 2c0a505

File tree

1 file changed

+70
-50
lines changed
  • spring-expression/src/main/java/org/springframework/expression/spel/standard

1 file changed

+70
-50
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.expression.spel.standard;
1818

19+
import java.util.concurrent.atomic.AtomicInteger;
20+
1921
import org.springframework.core.convert.TypeDescriptor;
2022
import org.springframework.expression.EvaluationContext;
2123
import org.springframework.expression.EvaluationException;
@@ -65,15 +67,15 @@ public class SpelExpression implements Expression {
6567

6668
// Holds the compiled form of the expression (if it has been compiled)
6769
@Nullable
68-
private CompiledExpression compiledAst;
70+
private volatile CompiledExpression compiledAst;
6971

7072
// Count of many times as the expression been interpreted - can trigger compilation
7173
// when certain limit reached
72-
private volatile int interpretedCount = 0;
74+
private final AtomicInteger interpretedCount = new AtomicInteger(0);
7375

7476
// The number of times compilation was attempted and failed - enables us to eventually
7577
// give up trying to compile it when it just doesn't seem to be possible.
76-
private volatile int failedAttempts = 0;
78+
private final AtomicInteger failedAttempts = new AtomicInteger(0);
7779

7880

7981
/**
@@ -116,16 +118,17 @@ public String getExpressionString() {
116118
@Override
117119
@Nullable
118120
public Object getValue() throws EvaluationException {
119-
if (this.compiledAst != null) {
121+
CompiledExpression compiledAst = this.compiledAst;
122+
if (compiledAst != null) {
120123
try {
121124
EvaluationContext context = getEvaluationContext();
122-
return this.compiledAst.getValue(context.getRootObject().getValue(), context);
125+
return compiledAst.getValue(context.getRootObject().getValue(), context);
123126
}
124127
catch (Throwable ex) {
125128
// If running in mixed mode, revert to interpreted
126129
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
127-
this.interpretedCount = 0;
128130
this.compiledAst = null;
131+
this.interpretedCount.set(0);
129132
}
130133
else {
131134
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -144,10 +147,11 @@ public Object getValue() throws EvaluationException {
144147
@Override
145148
@Nullable
146149
public <T> T getValue(@Nullable Class<T> expectedResultType) throws EvaluationException {
147-
if (this.compiledAst != null) {
150+
CompiledExpression compiledAst = this.compiledAst;
151+
if (compiledAst != null) {
148152
try {
149153
EvaluationContext context = getEvaluationContext();
150-
Object result = this.compiledAst.getValue(context.getRootObject().getValue(), context);
154+
Object result = compiledAst.getValue(context.getRootObject().getValue(), context);
151155
if (expectedResultType == null) {
152156
return (T) result;
153157
}
@@ -159,8 +163,8 @@ public <T> T getValue(@Nullable Class<T> expectedResultType) throws EvaluationEx
159163
catch (Throwable ex) {
160164
// If running in mixed mode, revert to interpreted
161165
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
162-
this.interpretedCount = 0;
163166
this.compiledAst = null;
167+
this.interpretedCount.set(0);
164168
}
165169
else {
166170
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -179,15 +183,16 @@ public <T> T getValue(@Nullable Class<T> expectedResultType) throws EvaluationEx
179183
@Override
180184
@Nullable
181185
public Object getValue(Object rootObject) throws EvaluationException {
182-
if (this.compiledAst != null) {
186+
CompiledExpression compiledAst = this.compiledAst;
187+
if (compiledAst != null) {
183188
try {
184-
return this.compiledAst.getValue(rootObject, getEvaluationContext());
189+
return compiledAst.getValue(rootObject, getEvaluationContext());
185190
}
186191
catch (Throwable ex) {
187192
// If running in mixed mode, revert to interpreted
188193
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
189-
this.interpretedCount = 0;
190194
this.compiledAst = null;
195+
this.interpretedCount.set(0);
191196
}
192197
else {
193198
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -207,9 +212,10 @@ public Object getValue(Object rootObject) throws EvaluationException {
207212
@Override
208213
@Nullable
209214
public <T> T getValue(Object rootObject, @Nullable Class<T> expectedResultType) throws EvaluationException {
210-
if (this.compiledAst != null) {
215+
CompiledExpression compiledAst = this.compiledAst;
216+
if (compiledAst != null) {
211217
try {
212-
Object result = this.compiledAst.getValue(rootObject, getEvaluationContext());
218+
Object result = compiledAst.getValue(rootObject, getEvaluationContext());
213219
if (expectedResultType == null) {
214220
return (T)result;
215221
}
@@ -221,8 +227,8 @@ public <T> T getValue(Object rootObject, @Nullable Class<T> expectedResultType)
221227
catch (Throwable ex) {
222228
// If running in mixed mode, revert to interpreted
223229
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
224-
this.interpretedCount = 0;
225230
this.compiledAst = null;
231+
this.interpretedCount.set(0);
226232
}
227233
else {
228234
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -244,15 +250,16 @@ public <T> T getValue(Object rootObject, @Nullable Class<T> expectedResultType)
244250
public Object getValue(EvaluationContext context) throws EvaluationException {
245251
Assert.notNull(context, "EvaluationContext is required");
246252

247-
if (this.compiledAst != null) {
253+
CompiledExpression compiledAst = this.compiledAst;
254+
if (compiledAst != null) {
248255
try {
249-
return this.compiledAst.getValue(context.getRootObject().getValue(), context);
256+
return compiledAst.getValue(context.getRootObject().getValue(), context);
250257
}
251258
catch (Throwable ex) {
252259
// If running in mixed mode, revert to interpreted
253260
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
254-
this.interpretedCount = 0;
255261
this.compiledAst = null;
262+
this.interpretedCount.set(0);
256263
}
257264
else {
258265
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -273,9 +280,10 @@ public Object getValue(EvaluationContext context) throws EvaluationException {
273280
public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResultType) throws EvaluationException {
274281
Assert.notNull(context, "EvaluationContext is required");
275282

276-
if (this.compiledAst != null) {
283+
CompiledExpression compiledAst = this.compiledAst;
284+
if (compiledAst != null) {
277285
try {
278-
Object result = this.compiledAst.getValue(context.getRootObject().getValue(), context);
286+
Object result = compiledAst.getValue(context.getRootObject().getValue(), context);
279287
if (expectedResultType != null) {
280288
return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType);
281289
}
@@ -286,8 +294,8 @@ public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResu
286294
catch (Throwable ex) {
287295
// If running in mixed mode, revert to interpreted
288296
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
289-
this.interpretedCount = 0;
290297
this.compiledAst = null;
298+
this.interpretedCount.set(0);
291299
}
292300
else {
293301
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -307,15 +315,16 @@ public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResu
307315
public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
308316
Assert.notNull(context, "EvaluationContext is required");
309317

310-
if (this.compiledAst != null) {
318+
CompiledExpression compiledAst = this.compiledAst;
319+
if (compiledAst != null) {
311320
try {
312-
return this.compiledAst.getValue(rootObject, context);
321+
return compiledAst.getValue(rootObject, context);
313322
}
314323
catch (Throwable ex) {
315324
// If running in mixed mode, revert to interpreted
316325
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
317-
this.interpretedCount = 0;
318326
this.compiledAst = null;
327+
this.interpretedCount.set(0);
319328
}
320329
else {
321330
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -338,9 +347,10 @@ public <T> T getValue(EvaluationContext context, Object rootObject, @Nullable Cl
338347

339348
Assert.notNull(context, "EvaluationContext is required");
340349

341-
if (this.compiledAst != null) {
350+
CompiledExpression compiledAst = this.compiledAst;
351+
if (compiledAst != null) {
342352
try {
343-
Object result = this.compiledAst.getValue(rootObject, context);
353+
Object result = compiledAst.getValue(rootObject, context);
344354
if (expectedResultType != null) {
345355
return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType);
346356
}
@@ -351,8 +361,8 @@ public <T> T getValue(EvaluationContext context, Object rootObject, @Nullable Cl
351361
catch (Throwable ex) {
352362
// If running in mixed mode, revert to interpreted
353363
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
354-
this.interpretedCount = 0;
355364
this.compiledAst = null;
365+
this.interpretedCount.set(0);
356366
}
357367
else {
358368
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -473,48 +483,58 @@ public void setValue(EvaluationContext context, Object rootObject, @Nullable Obj
473483
* @param expressionState the expression state used to determine compilation mode
474484
*/
475485
private void checkCompile(ExpressionState expressionState) {
476-
this.interpretedCount++;
486+
this.interpretedCount.incrementAndGet();
477487
SpelCompilerMode compilerMode = expressionState.getConfiguration().getCompilerMode();
478488
if (compilerMode != SpelCompilerMode.OFF) {
479489
if (compilerMode == SpelCompilerMode.IMMEDIATE) {
480-
if (this.interpretedCount > 1) {
490+
if (this.interpretedCount.get() > 1) {
481491
compileExpression();
482492
}
483493
}
484494
else {
485495
// compilerMode = SpelCompilerMode.MIXED
486-
if (this.interpretedCount > INTERPRETED_COUNT_THRESHOLD) {
496+
if (this.interpretedCount.get() > INTERPRETED_COUNT_THRESHOLD) {
487497
compileExpression();
488498
}
489499
}
490500
}
491501
}
492502

493-
494503
/**
495-
* Perform expression compilation. This will only succeed once exit descriptors for all nodes have
496-
* been determined. If the compilation fails and has failed more than 100 times the expression is
497-
* no longer considered suitable for compilation.
504+
* Perform expression compilation. This will only succeed once exit descriptors for
505+
* all nodes have been determined. If the compilation fails and has failed more than
506+
* 100 times the expression is no longer considered suitable for compilation.
507+
* @return whether this expression has been successfully compiled
498508
*/
499509
public boolean compileExpression() {
500-
if (this.failedAttempts > FAILED_ATTEMPTS_THRESHOLD) {
510+
CompiledExpression compiledAst = this.compiledAst;
511+
if (compiledAst != null) {
512+
// Previously compiled
513+
return true;
514+
}
515+
if (this.failedAttempts.get() > FAILED_ATTEMPTS_THRESHOLD) {
501516
// Don't try again
502517
return false;
503518
}
504-
if (this.compiledAst == null) {
505-
synchronized (this.expression) {
506-
// Possibly compiled by another thread before this thread got into the sync block
507-
if (this.compiledAst != null) {
508-
return true;
509-
}
510-
SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader());
511-
this.compiledAst = compiler.compile(this.ast);
512-
if (this.compiledAst == null) {
513-
this.failedAttempts++;
514-
}
519+
520+
synchronized (this) {
521+
if (this.compiledAst != null) {
522+
// Compiled by another thread before this thread got into the sync block
523+
return true;
524+
}
525+
SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader());
526+
compiledAst = compiler.compile(this.ast);
527+
if (compiledAst != null) {
528+
// Successfully compiled
529+
this.compiledAst = compiledAst;
530+
return true;
531+
}
532+
else {
533+
// Failed to compile
534+
this.failedAttempts.incrementAndGet();
535+
return false;
515536
}
516537
}
517-
return (this.compiledAst != null);
518538
}
519539

520540
/**
@@ -524,8 +544,8 @@ public boolean compileExpression() {
524544
*/
525545
public void revertToInterpreted() {
526546
this.compiledAst = null;
527-
this.interpretedCount = 0;
528-
this.failedAttempts = 0;
547+
this.interpretedCount.set(0);
548+
this.failedAttempts.set(0);
529549
}
530550

531551
/**

0 commit comments

Comments
 (0)