1
1
/*
2
- * Copyright 2002-2018 the original author or authors.
2
+ * Copyright 2002-2020 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
16
16
17
17
package org .springframework .expression .spel .standard ;
18
18
19
+ import java .util .concurrent .atomic .AtomicInteger ;
20
+
19
21
import org .springframework .core .convert .TypeDescriptor ;
20
22
import org .springframework .expression .EvaluationContext ;
21
23
import org .springframework .expression .EvaluationException ;
@@ -65,15 +67,15 @@ public class SpelExpression implements Expression {
65
67
66
68
// Holds the compiled form of the expression (if it has been compiled)
67
69
@ Nullable
68
- private CompiledExpression compiledAst ;
70
+ private volatile CompiledExpression compiledAst ;
69
71
70
72
// Count of many times as the expression been interpreted - can trigger compilation
71
73
// when certain limit reached
72
- private volatile int interpretedCount = 0 ;
74
+ private final AtomicInteger interpretedCount = new AtomicInteger ( 0 ) ;
73
75
74
76
// The number of times compilation was attempted and failed - enables us to eventually
75
77
// 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 ) ;
77
79
78
80
79
81
/**
@@ -116,16 +118,17 @@ public String getExpressionString() {
116
118
@ Override
117
119
@ Nullable
118
120
public Object getValue () throws EvaluationException {
119
- if (this .compiledAst != null ) {
121
+ CompiledExpression compiledAst = this .compiledAst ;
122
+ if (compiledAst != null ) {
120
123
try {
121
124
EvaluationContext context = getEvaluationContext ();
122
- return this . compiledAst .getValue (context .getRootObject ().getValue (), context );
125
+ return compiledAst .getValue (context .getRootObject ().getValue (), context );
123
126
}
124
127
catch (Throwable ex ) {
125
128
// If running in mixed mode, revert to interpreted
126
129
if (this .configuration .getCompilerMode () == SpelCompilerMode .MIXED ) {
127
- this .interpretedCount = 0 ;
128
130
this .compiledAst = null ;
131
+ this .interpretedCount .set (0 );
129
132
}
130
133
else {
131
134
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -144,10 +147,11 @@ public Object getValue() throws EvaluationException {
144
147
@ Override
145
148
@ Nullable
146
149
public <T > T getValue (@ Nullable Class <T > expectedResultType ) throws EvaluationException {
147
- if (this .compiledAst != null ) {
150
+ CompiledExpression compiledAst = this .compiledAst ;
151
+ if (compiledAst != null ) {
148
152
try {
149
153
EvaluationContext context = getEvaluationContext ();
150
- Object result = this . compiledAst .getValue (context .getRootObject ().getValue (), context );
154
+ Object result = compiledAst .getValue (context .getRootObject ().getValue (), context );
151
155
if (expectedResultType == null ) {
152
156
return (T ) result ;
153
157
}
@@ -159,8 +163,8 @@ public <T> T getValue(@Nullable Class<T> expectedResultType) throws EvaluationEx
159
163
catch (Throwable ex ) {
160
164
// If running in mixed mode, revert to interpreted
161
165
if (this .configuration .getCompilerMode () == SpelCompilerMode .MIXED ) {
162
- this .interpretedCount = 0 ;
163
166
this .compiledAst = null ;
167
+ this .interpretedCount .set (0 );
164
168
}
165
169
else {
166
170
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -179,15 +183,16 @@ public <T> T getValue(@Nullable Class<T> expectedResultType) throws EvaluationEx
179
183
@ Override
180
184
@ Nullable
181
185
public Object getValue (Object rootObject ) throws EvaluationException {
182
- if (this .compiledAst != null ) {
186
+ CompiledExpression compiledAst = this .compiledAst ;
187
+ if (compiledAst != null ) {
183
188
try {
184
- return this . compiledAst .getValue (rootObject , getEvaluationContext ());
189
+ return compiledAst .getValue (rootObject , getEvaluationContext ());
185
190
}
186
191
catch (Throwable ex ) {
187
192
// If running in mixed mode, revert to interpreted
188
193
if (this .configuration .getCompilerMode () == SpelCompilerMode .MIXED ) {
189
- this .interpretedCount = 0 ;
190
194
this .compiledAst = null ;
195
+ this .interpretedCount .set (0 );
191
196
}
192
197
else {
193
198
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -207,9 +212,10 @@ public Object getValue(Object rootObject) throws EvaluationException {
207
212
@ Override
208
213
@ Nullable
209
214
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 ) {
211
217
try {
212
- Object result = this . compiledAst .getValue (rootObject , getEvaluationContext ());
218
+ Object result = compiledAst .getValue (rootObject , getEvaluationContext ());
213
219
if (expectedResultType == null ) {
214
220
return (T )result ;
215
221
}
@@ -221,8 +227,8 @@ public <T> T getValue(Object rootObject, @Nullable Class<T> expectedResultType)
221
227
catch (Throwable ex ) {
222
228
// If running in mixed mode, revert to interpreted
223
229
if (this .configuration .getCompilerMode () == SpelCompilerMode .MIXED ) {
224
- this .interpretedCount = 0 ;
225
230
this .compiledAst = null ;
231
+ this .interpretedCount .set (0 );
226
232
}
227
233
else {
228
234
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -244,15 +250,16 @@ public <T> T getValue(Object rootObject, @Nullable Class<T> expectedResultType)
244
250
public Object getValue (EvaluationContext context ) throws EvaluationException {
245
251
Assert .notNull (context , "EvaluationContext is required" );
246
252
247
- if (this .compiledAst != null ) {
253
+ CompiledExpression compiledAst = this .compiledAst ;
254
+ if (compiledAst != null ) {
248
255
try {
249
- return this . compiledAst .getValue (context .getRootObject ().getValue (), context );
256
+ return compiledAst .getValue (context .getRootObject ().getValue (), context );
250
257
}
251
258
catch (Throwable ex ) {
252
259
// If running in mixed mode, revert to interpreted
253
260
if (this .configuration .getCompilerMode () == SpelCompilerMode .MIXED ) {
254
- this .interpretedCount = 0 ;
255
261
this .compiledAst = null ;
262
+ this .interpretedCount .set (0 );
256
263
}
257
264
else {
258
265
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -273,9 +280,10 @@ public Object getValue(EvaluationContext context) throws EvaluationException {
273
280
public <T > T getValue (EvaluationContext context , @ Nullable Class <T > expectedResultType ) throws EvaluationException {
274
281
Assert .notNull (context , "EvaluationContext is required" );
275
282
276
- if (this .compiledAst != null ) {
283
+ CompiledExpression compiledAst = this .compiledAst ;
284
+ if (compiledAst != null ) {
277
285
try {
278
- Object result = this . compiledAst .getValue (context .getRootObject ().getValue (), context );
286
+ Object result = compiledAst .getValue (context .getRootObject ().getValue (), context );
279
287
if (expectedResultType != null ) {
280
288
return ExpressionUtils .convertTypedValue (context , new TypedValue (result ), expectedResultType );
281
289
}
@@ -286,8 +294,8 @@ public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResu
286
294
catch (Throwable ex ) {
287
295
// If running in mixed mode, revert to interpreted
288
296
if (this .configuration .getCompilerMode () == SpelCompilerMode .MIXED ) {
289
- this .interpretedCount = 0 ;
290
297
this .compiledAst = null ;
298
+ this .interpretedCount .set (0 );
291
299
}
292
300
else {
293
301
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -307,15 +315,16 @@ public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResu
307
315
public Object getValue (EvaluationContext context , Object rootObject ) throws EvaluationException {
308
316
Assert .notNull (context , "EvaluationContext is required" );
309
317
310
- if (this .compiledAst != null ) {
318
+ CompiledExpression compiledAst = this .compiledAst ;
319
+ if (compiledAst != null ) {
311
320
try {
312
- return this . compiledAst .getValue (rootObject , context );
321
+ return compiledAst .getValue (rootObject , context );
313
322
}
314
323
catch (Throwable ex ) {
315
324
// If running in mixed mode, revert to interpreted
316
325
if (this .configuration .getCompilerMode () == SpelCompilerMode .MIXED ) {
317
- this .interpretedCount = 0 ;
318
326
this .compiledAst = null ;
327
+ this .interpretedCount .set (0 );
319
328
}
320
329
else {
321
330
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -338,9 +347,10 @@ public <T> T getValue(EvaluationContext context, Object rootObject, @Nullable Cl
338
347
339
348
Assert .notNull (context , "EvaluationContext is required" );
340
349
341
- if (this .compiledAst != null ) {
350
+ CompiledExpression compiledAst = this .compiledAst ;
351
+ if (compiledAst != null ) {
342
352
try {
343
- Object result = this . compiledAst .getValue (rootObject , context );
353
+ Object result = compiledAst .getValue (rootObject , context );
344
354
if (expectedResultType != null ) {
345
355
return ExpressionUtils .convertTypedValue (context , new TypedValue (result ), expectedResultType );
346
356
}
@@ -351,8 +361,8 @@ public <T> T getValue(EvaluationContext context, Object rootObject, @Nullable Cl
351
361
catch (Throwable ex ) {
352
362
// If running in mixed mode, revert to interpreted
353
363
if (this .configuration .getCompilerMode () == SpelCompilerMode .MIXED ) {
354
- this .interpretedCount = 0 ;
355
364
this .compiledAst = null ;
365
+ this .interpretedCount .set (0 );
356
366
}
357
367
else {
358
368
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@@ -473,48 +483,58 @@ public void setValue(EvaluationContext context, Object rootObject, @Nullable Obj
473
483
* @param expressionState the expression state used to determine compilation mode
474
484
*/
475
485
private void checkCompile (ExpressionState expressionState ) {
476
- this .interpretedCount ++ ;
486
+ this .interpretedCount . incrementAndGet () ;
477
487
SpelCompilerMode compilerMode = expressionState .getConfiguration ().getCompilerMode ();
478
488
if (compilerMode != SpelCompilerMode .OFF ) {
479
489
if (compilerMode == SpelCompilerMode .IMMEDIATE ) {
480
- if (this .interpretedCount > 1 ) {
490
+ if (this .interpretedCount . get () > 1 ) {
481
491
compileExpression ();
482
492
}
483
493
}
484
494
else {
485
495
// compilerMode = SpelCompilerMode.MIXED
486
- if (this .interpretedCount > INTERPRETED_COUNT_THRESHOLD ) {
496
+ if (this .interpretedCount . get () > INTERPRETED_COUNT_THRESHOLD ) {
487
497
compileExpression ();
488
498
}
489
499
}
490
500
}
491
501
}
492
502
493
-
494
503
/**
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
498
508
*/
499
509
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 ) {
501
516
// Don't try again
502
517
return false ;
503
518
}
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 ;
515
536
}
516
537
}
517
- return (this .compiledAst != null );
518
538
}
519
539
520
540
/**
@@ -524,8 +544,8 @@ public boolean compileExpression() {
524
544
*/
525
545
public void revertToInterpreted () {
526
546
this .compiledAst = null ;
527
- this .interpretedCount = 0 ;
528
- this .failedAttempts = 0 ;
547
+ this .interpretedCount . set ( 0 ) ;
548
+ this .failedAttempts . set ( 0 ) ;
529
549
}
530
550
531
551
/**
0 commit comments