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.
63
63
* <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}.
64
64
*
65
65
* @author Andy Clement
66
+ * @author Juergen Hoeller
66
67
* @since 4.1
67
68
*/
68
69
public final class SpelCompiler implements Opcodes {
69
70
70
- private static final Log logger = LogFactory . getLog ( SpelCompiler . class ) ;
71
+ private static final int CLASSES_DEFINED_LIMIT = 10 ;
71
72
72
- private static final int CLASSES_DEFINED_LIMIT = 100 ;
73
+ private static final Log logger = LogFactory . getLog ( SpelCompiler . class ) ;
73
74
74
75
// A compiler is created for each classloader, it manages a child class loader of that
75
76
// classloader and the child is used to load the compiled expressions.
76
77
private static final Map <ClassLoader , SpelCompiler > compilers = new ConcurrentReferenceHashMap <>();
77
78
79
+
78
80
// The child ClassLoader used to load the compiled expression classes
79
- private ChildClassLoader ccl ;
81
+ private volatile ChildClassLoader childClassLoader ;
80
82
81
83
// Counter suffix for generated classes within this SpelCompiler instance
82
84
private final AtomicInteger suffixId = new AtomicInteger (1 );
83
85
84
86
85
87
private SpelCompiler (@ Nullable ClassLoader classloader ) {
86
- this .ccl = new ChildClassLoader (classloader );
88
+ this .childClassLoader = new ChildClassLoader (classloader );
87
89
}
88
90
89
91
@@ -135,7 +137,7 @@ private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl e
135
137
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
136
138
String className = "spel/Ex" + getNextSuffix ();
137
139
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 );
139
141
140
142
// Create default constructor
141
143
MethodVisitor mv = cw .visitMethod (ACC_PUBLIC , "<init>" , "()V" , null , null );
@@ -150,7 +152,7 @@ private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl e
150
152
// Create getValue() method
151
153
mv = cw .visitMethod (ACC_PUBLIC , "getValue" ,
152
154
"(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" });
154
156
mv .visitCode ();
155
157
156
158
CodeFlow cf = new CodeFlow (className , cw );
@@ -187,7 +189,7 @@ private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl e
187
189
188
190
/**
189
191
* 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
191
193
* continually recompiling over time then by replacing the classloader periodically
192
194
* at least some of the older variants can be garbage collected.
193
195
* @param name the name of the class
@@ -196,12 +198,25 @@ private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl e
196
198
*/
197
199
@ SuppressWarnings ("unchecked" )
198
200
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
+ }
201
215
}
202
- return (Class <? extends CompiledExpression >) this . ccl .defineClass (name , bytes );
216
+ return (Class <? extends CompiledExpression >) ccl .defineClass (name , bytes );
203
217
}
204
218
219
+
205
220
/**
206
221
* Factory method for compiler instances. The returned SpelCompiler will
207
222
* 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)
211
226
*/
212
227
public static SpelCompiler getCompiler (@ Nullable ClassLoader classLoader ) {
213
228
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
+ }
219
239
}
220
- return compiler ;
221
240
}
241
+ return compiler ;
222
242
}
223
243
224
244
/**
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
229
251
*/
230
252
public static boolean compile (Expression expression ) {
231
253
return (expression instanceof SpelExpression && ((SpelExpression ) expression ).compileExpression ());
@@ -250,21 +272,21 @@ private static class ChildClassLoader extends URLClassLoader {
250
272
251
273
private static final URL [] NO_URLS = new URL [0 ];
252
274
253
- private int classesDefinedCount = 0 ;
275
+ private final AtomicInteger classesDefinedCount = new AtomicInteger ( 0 ) ;
254
276
255
277
public ChildClassLoader (@ Nullable ClassLoader classLoader ) {
256
278
super (NO_URLS , classLoader );
257
279
}
258
280
259
- int getClassesDefinedCount () {
260
- return this .classesDefinedCount ;
261
- }
262
-
263
281
public Class <?> defineClass (String name , byte [] bytes ) {
264
282
Class <?> clazz = super .defineClass (name , bytes , 0 , bytes .length );
265
- this .classesDefinedCount ++ ;
283
+ this .classesDefinedCount . incrementAndGet () ;
266
284
return clazz ;
267
285
}
286
+
287
+ public int getClassesDefinedCount () {
288
+ return this .classesDefinedCount .get ();
289
+ }
268
290
}
269
291
270
292
@@ -276,7 +298,7 @@ public ExpressionClassWriter() {
276
298
277
299
@ Override
278
300
protected ClassLoader getClassLoader () {
279
- return ccl ;
301
+ return childClassLoader ;
280
302
}
281
303
}
282
304
0 commit comments