Skip to content

Commit 6acb091

Browse files
committed
Upgrade SpelCompiler bytecode level to 1.8 and optimize for concurrent access
Closes gh-26033
1 parent 99ed01e commit 6acb091

File tree

1 file changed

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

1 file changed

+50
-28
lines changed

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

Lines changed: 50 additions & 28 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.
@@ -63,27 +63,29 @@
6363
* <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}.
6464
*
6565
* @author Andy Clement
66+
* @author Juergen Hoeller
6667
* @since 4.1
6768
*/
6869
public final class SpelCompiler implements Opcodes {
6970

70-
private static final Log logger = LogFactory.getLog(SpelCompiler.class);
71+
private static final int CLASSES_DEFINED_LIMIT = 10;
7172

72-
private static final int CLASSES_DEFINED_LIMIT = 100;
73+
private static final Log logger = LogFactory.getLog(SpelCompiler.class);
7374

7475
// A compiler is created for each classloader, it manages a child class loader of that
7576
// classloader and the child is used to load the compiled expressions.
7677
private static final Map<ClassLoader, SpelCompiler> compilers = new ConcurrentReferenceHashMap<>();
7778

79+
7880
// The child ClassLoader used to load the compiled expression classes
79-
private ChildClassLoader ccl;
81+
private volatile ChildClassLoader childClassLoader;
8082

8183
// Counter suffix for generated classes within this SpelCompiler instance
8284
private final AtomicInteger suffixId = new AtomicInteger(1);
8385

8486

8587
private SpelCompiler(@Nullable ClassLoader classloader) {
86-
this.ccl = new ChildClassLoader(classloader);
88+
this.childClassLoader = new ChildClassLoader(classloader);
8789
}
8890

8991

@@ -135,7 +137,7 @@ private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl e
135137
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
136138
String className = "spel/Ex" + getNextSuffix();
137139
ClassWriter cw = new ExpressionClassWriter();
138-
cw.visit(V1_5, ACC_PUBLIC, className, null, "org/springframework/expression/spel/CompiledExpression", null);
140+
cw.visit(V1_8, ACC_PUBLIC, className, null, "org/springframework/expression/spel/CompiledExpression", null);
139141

140142
// Create default constructor
141143
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
@@ -150,7 +152,7 @@ private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl e
150152
// Create getValue() method
151153
mv = cw.visitMethod(ACC_PUBLIC, "getValue",
152154
"(Ljava/lang/Object;Lorg/springframework/expression/EvaluationContext;)Ljava/lang/Object;", null,
153-
new String[ ]{"org/springframework/expression/EvaluationException"});
155+
new String[] {"org/springframework/expression/EvaluationException"});
154156
mv.visitCode();
155157

156158
CodeFlow cf = new CodeFlow(className, cw);
@@ -187,7 +189,7 @@ private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl e
187189

188190
/**
189191
* Load a compiled expression class. Makes sure the classloaders aren't used too much
190-
* because they anchor compiled classes in memory and prevent GC. If you have expressions
192+
* because they anchor compiled classes in memory and prevent GC. If you have expressions
191193
* continually recompiling over time then by replacing the classloader periodically
192194
* at least some of the older variants can be garbage collected.
193195
* @param name the name of the class
@@ -196,12 +198,25 @@ private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl e
196198
*/
197199
@SuppressWarnings("unchecked")
198200
private Class<? extends CompiledExpression> loadClass(String name, byte[] bytes) {
199-
if (this.ccl.getClassesDefinedCount() > CLASSES_DEFINED_LIMIT) {
200-
this.ccl = new ChildClassLoader(this.ccl.getParent());
201+
ChildClassLoader ccl = this.childClassLoader;
202+
if (ccl.getClassesDefinedCount() >= CLASSES_DEFINED_LIMIT) {
203+
synchronized (this) {
204+
ChildClassLoader currentCcl = this.childClassLoader;
205+
if (ccl == currentCcl) {
206+
// Still the same ClassLoader that needs to be replaced...
207+
ccl = new ChildClassLoader(ccl.getParent());
208+
this.childClassLoader = ccl;
209+
}
210+
else {
211+
// Already replaced by some other thread, let's pick it up.
212+
ccl = currentCcl;
213+
}
214+
}
201215
}
202-
return (Class<? extends CompiledExpression>) this.ccl.defineClass(name, bytes);
216+
return (Class<? extends CompiledExpression>) ccl.defineClass(name, bytes);
203217
}
204218

219+
205220
/**
206221
* Factory method for compiler instances. The returned SpelCompiler will
207222
* attach a class loader as the child of the given class loader and this
@@ -211,21 +226,28 @@ private Class<? extends CompiledExpression> loadClass(String name, byte[] bytes)
211226
*/
212227
public static SpelCompiler getCompiler(@Nullable ClassLoader classLoader) {
213228
ClassLoader clToUse = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
214-
synchronized (compilers) {
215-
SpelCompiler compiler = compilers.get(clToUse);
216-
if (compiler == null) {
217-
compiler = new SpelCompiler(clToUse);
218-
compilers.put(clToUse, compiler);
229+
// Quick check for existing compiler without lock contention
230+
SpelCompiler compiler = compilers.get(clToUse);
231+
if (compiler == null) {
232+
// Full lock now since we're creating a child ClassLoader
233+
synchronized (compilers) {
234+
compiler = compilers.get(clToUse);
235+
if (compiler == null) {
236+
compiler = new SpelCompiler(clToUse);
237+
compilers.put(clToUse, compiler);
238+
}
219239
}
220-
return compiler;
221240
}
241+
return compiler;
222242
}
223243

224244
/**
225-
* Request that an attempt is made to compile the specified expression. It may fail if
226-
* components of the expression are not suitable for compilation or the data types
227-
* involved are not suitable for compilation. Used for testing.
228-
* @return true if the expression was successfully compiled
245+
* Request that an attempt is made to compile the specified expression.
246+
* It may fail if components of the expression are not suitable for compilation
247+
* or the data types involved are not suitable for compilation. Used for testing.
248+
* @param expression the expression to compile
249+
* @return {@code true} if the expression was successfully compiled,
250+
* {@code false} otherwise
229251
*/
230252
public static boolean compile(Expression expression) {
231253
return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression());
@@ -250,21 +272,21 @@ private static class ChildClassLoader extends URLClassLoader {
250272

251273
private static final URL[] NO_URLS = new URL[0];
252274

253-
private int classesDefinedCount = 0;
275+
private final AtomicInteger classesDefinedCount = new AtomicInteger(0);
254276

255277
public ChildClassLoader(@Nullable ClassLoader classLoader) {
256278
super(NO_URLS, classLoader);
257279
}
258280

259-
int getClassesDefinedCount() {
260-
return this.classesDefinedCount;
261-
}
262-
263281
public Class<?> defineClass(String name, byte[] bytes) {
264282
Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length);
265-
this.classesDefinedCount++;
283+
this.classesDefinedCount.incrementAndGet();
266284
return clazz;
267285
}
286+
287+
public int getClassesDefinedCount() {
288+
return this.classesDefinedCount.get();
289+
}
268290
}
269291

270292

@@ -276,7 +298,7 @@ public ExpressionClassWriter() {
276298

277299
@Override
278300
protected ClassLoader getClassLoader() {
279-
return ccl;
301+
return childClassLoader;
280302
}
281303
}
282304

0 commit comments

Comments
 (0)