Browse Source

Thread-safe compiled expression evaluation in SpelExpression

Closes gh-24265
pull/27217/head
Juergen Hoeller 5 years ago
parent
commit
d0e3e2acfc
  1. 120
      spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.expression.spel.standard;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
@ -65,15 +67,15 @@ public class SpelExpression implements Expression { @@ -65,15 +67,15 @@ public class SpelExpression implements Expression {
// Holds the compiled form of the expression (if it has been compiled)
@Nullable
private CompiledExpression compiledAst;
private volatile CompiledExpression compiledAst;
// Count of many times as the expression been interpreted - can trigger compilation
// when certain limit reached
private volatile int interpretedCount = 0;
private final AtomicInteger interpretedCount = new AtomicInteger(0);
// The number of times compilation was attempted and failed - enables us to eventually
// give up trying to compile it when it just doesn't seem to be possible.
private volatile int failedAttempts = 0;
private final AtomicInteger failedAttempts = new AtomicInteger(0);
/**
@ -116,16 +118,17 @@ public class SpelExpression implements Expression { @@ -116,16 +118,17 @@ public class SpelExpression implements Expression {
@Override
@Nullable
public Object getValue() throws EvaluationException {
if (this.compiledAst != null) {
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
try {
EvaluationContext context = getEvaluationContext();
return this.compiledAst.getValue(context.getRootObject().getValue(), context);
return compiledAst.getValue(context.getRootObject().getValue(), context);
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
this.interpretedCount.set(0);
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@ -144,10 +147,11 @@ public class SpelExpression implements Expression { @@ -144,10 +147,11 @@ public class SpelExpression implements Expression {
@Override
@Nullable
public <T> T getValue(@Nullable Class<T> expectedResultType) throws EvaluationException {
if (this.compiledAst != null) {
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
try {
EvaluationContext context = getEvaluationContext();
Object result = this.compiledAst.getValue(context.getRootObject().getValue(), context);
Object result = compiledAst.getValue(context.getRootObject().getValue(), context);
if (expectedResultType == null) {
return (T) result;
}
@ -159,8 +163,8 @@ public class SpelExpression implements Expression { @@ -159,8 +163,8 @@ public class SpelExpression implements Expression {
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
this.interpretedCount.set(0);
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@ -179,15 +183,16 @@ public class SpelExpression implements Expression { @@ -179,15 +183,16 @@ public class SpelExpression implements Expression {
@Override
@Nullable
public Object getValue(Object rootObject) throws EvaluationException {
if (this.compiledAst != null) {
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
try {
return this.compiledAst.getValue(rootObject, getEvaluationContext());
return compiledAst.getValue(rootObject, getEvaluationContext());
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
this.interpretedCount.set(0);
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@ -207,9 +212,10 @@ public class SpelExpression implements Expression { @@ -207,9 +212,10 @@ public class SpelExpression implements Expression {
@Override
@Nullable
public <T> T getValue(Object rootObject, @Nullable Class<T> expectedResultType) throws EvaluationException {
if (this.compiledAst != null) {
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
try {
Object result = this.compiledAst.getValue(rootObject, getEvaluationContext());
Object result = compiledAst.getValue(rootObject, getEvaluationContext());
if (expectedResultType == null) {
return (T)result;
}
@ -221,8 +227,8 @@ public class SpelExpression implements Expression { @@ -221,8 +227,8 @@ public class SpelExpression implements Expression {
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
this.interpretedCount.set(0);
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@ -244,15 +250,16 @@ public class SpelExpression implements Expression { @@ -244,15 +250,16 @@ public class SpelExpression implements Expression {
public Object getValue(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "EvaluationContext is required");
if (this.compiledAst != null) {
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
try {
return this.compiledAst.getValue(context.getRootObject().getValue(), context);
return compiledAst.getValue(context.getRootObject().getValue(), context);
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
this.interpretedCount.set(0);
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@ -273,9 +280,10 @@ public class SpelExpression implements Expression { @@ -273,9 +280,10 @@ public class SpelExpression implements Expression {
public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResultType) throws EvaluationException {
Assert.notNull(context, "EvaluationContext is required");
if (this.compiledAst != null) {
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
try {
Object result = this.compiledAst.getValue(context.getRootObject().getValue(), context);
Object result = compiledAst.getValue(context.getRootObject().getValue(), context);
if (expectedResultType != null) {
return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType);
}
@ -286,8 +294,8 @@ public class SpelExpression implements Expression { @@ -286,8 +294,8 @@ public class SpelExpression implements Expression {
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
this.interpretedCount.set(0);
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@ -307,15 +315,16 @@ public class SpelExpression implements Expression { @@ -307,15 +315,16 @@ public class SpelExpression implements Expression {
public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
Assert.notNull(context, "EvaluationContext is required");
if (this.compiledAst != null) {
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
try {
return this.compiledAst.getValue(rootObject, context);
return compiledAst.getValue(rootObject, context);
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
this.interpretedCount.set(0);
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@ -338,9 +347,10 @@ public class SpelExpression implements Expression { @@ -338,9 +347,10 @@ public class SpelExpression implements Expression {
Assert.notNull(context, "EvaluationContext is required");
if (this.compiledAst != null) {
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
try {
Object result = this.compiledAst.getValue(rootObject, context);
Object result = compiledAst.getValue(rootObject, context);
if (expectedResultType != null) {
return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType);
}
@ -351,8 +361,8 @@ public class SpelExpression implements Expression { @@ -351,8 +361,8 @@ public class SpelExpression implements Expression {
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
this.interpretedCount.set(0);
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
@ -473,48 +483,58 @@ public class SpelExpression implements Expression { @@ -473,48 +483,58 @@ public class SpelExpression implements Expression {
* @param expressionState the expression state used to determine compilation mode
*/
private void checkCompile(ExpressionState expressionState) {
this.interpretedCount++;
this.interpretedCount.incrementAndGet();
SpelCompilerMode compilerMode = expressionState.getConfiguration().getCompilerMode();
if (compilerMode != SpelCompilerMode.OFF) {
if (compilerMode == SpelCompilerMode.IMMEDIATE) {
if (this.interpretedCount > 1) {
if (this.interpretedCount.get() > 1) {
compileExpression();
}
}
else {
// compilerMode = SpelCompilerMode.MIXED
if (this.interpretedCount > INTERPRETED_COUNT_THRESHOLD) {
if (this.interpretedCount.get() > INTERPRETED_COUNT_THRESHOLD) {
compileExpression();
}
}
}
}
/**
* Perform expression compilation. This will only succeed once exit descriptors for all nodes have
* been determined. If the compilation fails and has failed more than 100 times the expression is
* no longer considered suitable for compilation.
* Perform expression compilation. This will only succeed once exit descriptors for
* all nodes have been determined. If the compilation fails and has failed more than
* 100 times the expression is no longer considered suitable for compilation.
* @return whether this expression has been successfully compiled
*/
public boolean compileExpression() {
if (this.failedAttempts > FAILED_ATTEMPTS_THRESHOLD) {
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
// Previously compiled
return true;
}
if (this.failedAttempts.get() > FAILED_ATTEMPTS_THRESHOLD) {
// Don't try again
return false;
}
if (this.compiledAst == null) {
synchronized (this.expression) {
// Possibly compiled by another thread before this thread got into the sync block
if (this.compiledAst != null) {
return true;
}
SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader());
this.compiledAst = compiler.compile(this.ast);
if (this.compiledAst == null) {
this.failedAttempts++;
}
synchronized (this) {
if (this.compiledAst != null) {
// 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;
}
else {
// Failed to compile
this.failedAttempts.incrementAndGet();
return false;
}
}
return (this.compiledAst != null);
}
/**
@ -524,8 +544,8 @@ public class SpelExpression implements Expression { @@ -524,8 +544,8 @@ public class SpelExpression implements Expression {
*/
public void revertToInterpreted() {
this.compiledAst = null;
this.interpretedCount = 0;
this.failedAttempts = 0;
this.interpretedCount.set(0);
this.failedAttempts.set(0);
}
/**

Loading…
Cancel
Save