Browse Source

Upgrade SpelCompiler bytecode level to 1.8 and optimize for concurrent access

Closes gh-26033
pull/26044/head
Juergen Hoeller 4 years ago
parent
commit
6acb091c4e
  1. 78
      spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java

78
spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.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.
@ -63,27 +63,29 @@ import org.springframework.util.StringUtils; @@ -63,27 +63,29 @@ import org.springframework.util.StringUtils;
* <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 4.1
*/
public final class SpelCompiler implements Opcodes {
private static final Log logger = LogFactory.getLog(SpelCompiler.class);
private static final int CLASSES_DEFINED_LIMIT = 10;
private static final int CLASSES_DEFINED_LIMIT = 100;
private static final Log logger = LogFactory.getLog(SpelCompiler.class);
// A compiler is created for each classloader, it manages a child class loader of that
// classloader and the child is used to load the compiled expressions.
private static final Map<ClassLoader, SpelCompiler> compilers = new ConcurrentReferenceHashMap<>();
// The child ClassLoader used to load the compiled expression classes
private ChildClassLoader ccl;
private volatile ChildClassLoader childClassLoader;
// Counter suffix for generated classes within this SpelCompiler instance
private final AtomicInteger suffixId = new AtomicInteger(1);
private SpelCompiler(@Nullable ClassLoader classloader) {
this.ccl = new ChildClassLoader(classloader);
this.childClassLoader = new ChildClassLoader(classloader);
}
@ -135,7 +137,7 @@ public final class SpelCompiler implements Opcodes { @@ -135,7 +137,7 @@ public final class SpelCompiler implements Opcodes {
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
String className = "spel/Ex" + getNextSuffix();
ClassWriter cw = new ExpressionClassWriter();
cw.visit(V1_5, ACC_PUBLIC, className, null, "org/springframework/expression/spel/CompiledExpression", null);
cw.visit(V1_8, ACC_PUBLIC, className, null, "org/springframework/expression/spel/CompiledExpression", null);
// Create default constructor
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
@ -150,7 +152,7 @@ public final class SpelCompiler implements Opcodes { @@ -150,7 +152,7 @@ public final class SpelCompiler implements Opcodes {
// Create getValue() method
mv = cw.visitMethod(ACC_PUBLIC, "getValue",
"(Ljava/lang/Object;Lorg/springframework/expression/EvaluationContext;)Ljava/lang/Object;", null,
new String[ ]{"org/springframework/expression/EvaluationException"});
new String[] {"org/springframework/expression/EvaluationException"});
mv.visitCode();
CodeFlow cf = new CodeFlow(className, cw);
@ -187,7 +189,7 @@ public final class SpelCompiler implements Opcodes { @@ -187,7 +189,7 @@ public final class SpelCompiler implements Opcodes {
/**
* Load a compiled expression class. Makes sure the classloaders aren't used too much
* because they anchor compiled classes in memory and prevent GC. If you have expressions
* because they anchor compiled classes in memory and prevent GC. If you have expressions
* continually recompiling over time then by replacing the classloader periodically
* at least some of the older variants can be garbage collected.
* @param name the name of the class
@ -196,12 +198,25 @@ public final class SpelCompiler implements Opcodes { @@ -196,12 +198,25 @@ public final class SpelCompiler implements Opcodes {
*/
@SuppressWarnings("unchecked")
private Class<? extends CompiledExpression> loadClass(String name, byte[] bytes) {
if (this.ccl.getClassesDefinedCount() > CLASSES_DEFINED_LIMIT) {
this.ccl = new ChildClassLoader(this.ccl.getParent());
ChildClassLoader ccl = this.childClassLoader;
if (ccl.getClassesDefinedCount() >= CLASSES_DEFINED_LIMIT) {
synchronized (this) {
ChildClassLoader currentCcl = this.childClassLoader;
if (ccl == currentCcl) {
// Still the same ClassLoader that needs to be replaced...
ccl = new ChildClassLoader(ccl.getParent());
this.childClassLoader = ccl;
}
else {
// Already replaced by some other thread, let's pick it up.
ccl = currentCcl;
}
}
}
return (Class<? extends CompiledExpression>) this.ccl.defineClass(name, bytes);
return (Class<? extends CompiledExpression>) ccl.defineClass(name, bytes);
}
/**
* Factory method for compiler instances. The returned SpelCompiler will
* attach a class loader as the child of the given class loader and this
@ -211,21 +226,28 @@ public final class SpelCompiler implements Opcodes { @@ -211,21 +226,28 @@ public final class SpelCompiler implements Opcodes {
*/
public static SpelCompiler getCompiler(@Nullable ClassLoader classLoader) {
ClassLoader clToUse = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
synchronized (compilers) {
SpelCompiler compiler = compilers.get(clToUse);
if (compiler == null) {
compiler = new SpelCompiler(clToUse);
compilers.put(clToUse, compiler);
// Quick check for existing compiler without lock contention
SpelCompiler compiler = compilers.get(clToUse);
if (compiler == null) {
// Full lock now since we're creating a child ClassLoader
synchronized (compilers) {
compiler = compilers.get(clToUse);
if (compiler == null) {
compiler = new SpelCompiler(clToUse);
compilers.put(clToUse, compiler);
}
}
return compiler;
}
return compiler;
}
/**
* Request that an attempt is made to compile the specified expression. It may fail if
* components of the expression are not suitable for compilation or the data types
* involved are not suitable for compilation. Used for testing.
* @return true if the expression was successfully compiled
* Request that an attempt is made to compile the specified expression.
* It may fail if components of the expression are not suitable for compilation
* or the data types involved are not suitable for compilation. Used for testing.
* @param expression the expression to compile
* @return {@code true} if the expression was successfully compiled,
* {@code false} otherwise
*/
public static boolean compile(Expression expression) {
return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression());
@ -250,21 +272,21 @@ public final class SpelCompiler implements Opcodes { @@ -250,21 +272,21 @@ public final class SpelCompiler implements Opcodes {
private static final URL[] NO_URLS = new URL[0];
private int classesDefinedCount = 0;
private final AtomicInteger classesDefinedCount = new AtomicInteger(0);
public ChildClassLoader(@Nullable ClassLoader classLoader) {
super(NO_URLS, classLoader);
}
int getClassesDefinedCount() {
return this.classesDefinedCount;
}
public Class<?> defineClass(String name, byte[] bytes) {
Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length);
this.classesDefinedCount++;
this.classesDefinedCount.incrementAndGet();
return clazz;
}
public int getClassesDefinedCount() {
return this.classesDefinedCount.get();
}
}
@ -276,7 +298,7 @@ public final class SpelCompiler implements Opcodes { @@ -276,7 +298,7 @@ public final class SpelCompiler implements Opcodes {
@Override
protected ClassLoader getClassLoader() {
return ccl;
return childClassLoader;
}
}

Loading…
Cancel
Save