Upgrade SpelCompiler bytecode level to 1.8 and optimize for concurrent access
Closes gh-26033
This commit is contained in:
parent
99ed01e3f7
commit
6acb091c4e
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -63,27 +63,29 @@ import org.springframework.util.StringUtils;
|
||||||
* <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}.
|
* <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}.
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
|
* @author Juergen Hoeller
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
*/
|
*/
|
||||||
public final class SpelCompiler implements Opcodes {
|
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
|
// 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.
|
// classloader and the child is used to load the compiled expressions.
|
||||||
private static final Map<ClassLoader, SpelCompiler> compilers = new ConcurrentReferenceHashMap<>();
|
private static final Map<ClassLoader, SpelCompiler> compilers = new ConcurrentReferenceHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
// The child ClassLoader used to load the compiled expression classes
|
// 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
|
// Counter suffix for generated classes within this SpelCompiler instance
|
||||||
private final AtomicInteger suffixId = new AtomicInteger(1);
|
private final AtomicInteger suffixId = new AtomicInteger(1);
|
||||||
|
|
||||||
|
|
||||||
private SpelCompiler(@Nullable ClassLoader classloader) {
|
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 {
|
||||||
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
|
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
|
||||||
String className = "spel/Ex" + getNextSuffix();
|
String className = "spel/Ex" + getNextSuffix();
|
||||||
ClassWriter cw = new ExpressionClassWriter();
|
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
|
// Create default constructor
|
||||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
|
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
|
||||||
|
@ -196,11 +198,24 @@ public final class SpelCompiler implements Opcodes {
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private Class<? extends CompiledExpression> loadClass(String name, byte[] bytes) {
|
private Class<? extends CompiledExpression> loadClass(String name, byte[] bytes) {
|
||||||
if (this.ccl.getClassesDefinedCount() > CLASSES_DEFINED_LIMIT) {
|
ChildClassLoader ccl = this.childClassLoader;
|
||||||
this.ccl = new ChildClassLoader(this.ccl.getParent());
|
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;
|
||||||
}
|
}
|
||||||
return (Class<? extends CompiledExpression>) this.ccl.defineClass(name, bytes);
|
else {
|
||||||
|
// Already replaced by some other thread, let's pick it up.
|
||||||
|
ccl = currentCcl;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (Class<? extends CompiledExpression>) ccl.defineClass(name, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method for compiler instances. The returned SpelCompiler will
|
* Factory method for compiler instances. The returned SpelCompiler will
|
||||||
|
@ -211,21 +226,28 @@ public final class SpelCompiler implements Opcodes {
|
||||||
*/
|
*/
|
||||||
public static SpelCompiler getCompiler(@Nullable ClassLoader classLoader) {
|
public static SpelCompiler getCompiler(@Nullable ClassLoader classLoader) {
|
||||||
ClassLoader clToUse = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
|
ClassLoader clToUse = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
|
||||||
synchronized (compilers) {
|
// Quick check for existing compiler without lock contention
|
||||||
SpelCompiler compiler = compilers.get(clToUse);
|
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) {
|
if (compiler == null) {
|
||||||
compiler = new SpelCompiler(clToUse);
|
compiler = new SpelCompiler(clToUse);
|
||||||
compilers.put(clToUse, compiler);
|
compilers.put(clToUse, compiler);
|
||||||
}
|
}
|
||||||
return compiler;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return compiler;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request that an attempt is made to compile the specified expression. It may fail if
|
* Request that an attempt is made to compile the specified expression.
|
||||||
* components of the expression are not suitable for compilation or the data types
|
* It may fail if components of the expression are not suitable for compilation
|
||||||
* involved are not suitable for compilation. Used for testing.
|
* or the data types involved are not suitable for compilation. Used for testing.
|
||||||
* @return true if the expression was successfully compiled
|
* @param expression the expression to compile
|
||||||
|
* @return {@code true} if the expression was successfully compiled,
|
||||||
|
* {@code false} otherwise
|
||||||
*/
|
*/
|
||||||
public static boolean compile(Expression expression) {
|
public static boolean compile(Expression expression) {
|
||||||
return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression());
|
return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression());
|
||||||
|
@ -250,21 +272,21 @@ public final class SpelCompiler implements Opcodes {
|
||||||
|
|
||||||
private static final URL[] NO_URLS = new URL[0];
|
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) {
|
public ChildClassLoader(@Nullable ClassLoader classLoader) {
|
||||||
super(NO_URLS, classLoader);
|
super(NO_URLS, classLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
int getClassesDefinedCount() {
|
|
||||||
return this.classesDefinedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<?> defineClass(String name, byte[] bytes) {
|
public Class<?> defineClass(String name, byte[] bytes) {
|
||||||
Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length);
|
Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length);
|
||||||
this.classesDefinedCount++;
|
this.classesDefinedCount.incrementAndGet();
|
||||||
return clazz;
|
return clazz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getClassesDefinedCount() {
|
||||||
|
return this.classesDefinedCount.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -276,7 +298,7 @@ public final class SpelCompiler implements Opcodes {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ClassLoader getClassLoader() {
|
protected ClassLoader getClassLoader() {
|
||||||
return ccl;
|
return childClassLoader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue