001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.expression.spel.standard; 018 019import java.net.URL; 020import java.net.URLClassLoader; 021import java.util.Map; 022import java.util.concurrent.atomic.AtomicInteger; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.asm.ClassWriter; 028import org.springframework.asm.MethodVisitor; 029import org.springframework.asm.Opcodes; 030import org.springframework.expression.Expression; 031import org.springframework.expression.spel.CodeFlow; 032import org.springframework.expression.spel.CompiledExpression; 033import org.springframework.expression.spel.SpelParserConfiguration; 034import org.springframework.expression.spel.ast.SpelNodeImpl; 035import org.springframework.lang.Nullable; 036import org.springframework.util.ClassUtils; 037import org.springframework.util.ConcurrentReferenceHashMap; 038import org.springframework.util.ReflectionUtils; 039import org.springframework.util.StringUtils; 040 041/** 042 * A SpelCompiler will take a regular parsed expression and create (and load) a class 043 * containing byte code that does the same thing as that expression. The compiled form of 044 * an expression will evaluate far faster than the interpreted form. 045 * 046 * <p>The SpelCompiler is not currently handling all expression types but covers many of 047 * the common cases. The framework is extensible to cover more cases in the future. For 048 * absolute maximum speed there is *no checking* in the compiled code. The compiled 049 * version of the expression uses information learned during interpreted runs of the 050 * expression when it generates the byte code. For example if it knows that a particular 051 * property dereference always seems to return a Map then it will generate byte code that 052 * expects the result of the property dereference to be a Map. This ensures maximal 053 * performance but should the dereference result in something other than a map, the 054 * compiled expression will fail - like a ClassCastException would occur if passing data 055 * of an unexpected type in a regular Java program. 056 * 057 * <p>Due to the lack of checking there are likely some expressions that should never be 058 * compiled, for example if an expression is continuously dealing with different types of 059 * data. Due to these cases the compiler is something that must be selectively turned on 060 * for an associated SpelExpressionParser (through the {@link SpelParserConfiguration} 061 * object), it is not on by default. 062 * 063 * <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}. 064 * 065 * @author Andy Clement 066 * @author Juergen Hoeller 067 * @since 4.1 068 */ 069public final class SpelCompiler implements Opcodes { 070 071 private static final int CLASSES_DEFINED_LIMIT = 100; 072 073 private static final Log logger = LogFactory.getLog(SpelCompiler.class); 074 075 // A compiler is created for each classloader, it manages a child class loader of that 076 // classloader and the child is used to load the compiled expressions. 077 private static final Map<ClassLoader, SpelCompiler> compilers = new ConcurrentReferenceHashMap<>(); 078 079 080 // The child ClassLoader used to load the compiled expression classes 081 private ChildClassLoader ccl; 082 083 // Counter suffix for generated classes within this SpelCompiler instance 084 private final AtomicInteger suffixId = new AtomicInteger(1); 085 086 087 private SpelCompiler(@Nullable ClassLoader classloader) { 088 this.ccl = new ChildClassLoader(classloader); 089 } 090 091 092 /** 093 * Attempt compilation of the supplied expression. A check is made to see 094 * if it is compilable before compilation proceeds. The check involves 095 * visiting all the nodes in the expression AST and ensuring enough state 096 * is known about them that bytecode can be generated for them. 097 * @param expression the expression to compile 098 * @return an instance of the class implementing the compiled expression, 099 * or {@code null} if compilation is not possible 100 */ 101 @Nullable 102 public CompiledExpression compile(SpelNodeImpl expression) { 103 if (expression.isCompilable()) { 104 if (logger.isDebugEnabled()) { 105 logger.debug("SpEL: compiling " + expression.toStringAST()); 106 } 107 Class<? extends CompiledExpression> clazz = createExpressionClass(expression); 108 if (clazz != null) { 109 try { 110 return ReflectionUtils.accessibleConstructor(clazz).newInstance(); 111 } 112 catch (Throwable ex) { 113 throw new IllegalStateException("Failed to instantiate CompiledExpression", ex); 114 } 115 } 116 } 117 118 if (logger.isDebugEnabled()) { 119 logger.debug("SpEL: unable to compile " + expression.toStringAST()); 120 } 121 return null; 122 } 123 124 private int getNextSuffix() { 125 return this.suffixId.incrementAndGet(); 126 } 127 128 /** 129 * Generate the class that encapsulates the compiled expression and define it. 130 * The generated class will be a subtype of CompiledExpression. 131 * @param expressionToCompile the expression to be compiled 132 * @return the expression call, or {@code null} if the decision was to opt out of 133 * compilation during code generation 134 */ 135 @Nullable 136 private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl expressionToCompile) { 137 // Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression' 138 String className = "spel/Ex" + getNextSuffix(); 139 ClassWriter cw = new ExpressionClassWriter(); 140 cw.visit(V1_5, ACC_PUBLIC, className, null, "org/springframework/expression/spel/CompiledExpression", null); 141 142 // Create default constructor 143 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 144 mv.visitCode(); 145 mv.visitVarInsn(ALOAD, 0); 146 mv.visitMethodInsn(INVOKESPECIAL, "org/springframework/expression/spel/CompiledExpression", 147 "<init>", "()V", false); 148 mv.visitInsn(RETURN); 149 mv.visitMaxs(1, 1); 150 mv.visitEnd(); 151 152 // Create getValue() method 153 mv = cw.visitMethod(ACC_PUBLIC, "getValue", 154 "(Ljava/lang/Object;Lorg/springframework/expression/EvaluationContext;)Ljava/lang/Object;", null, 155 new String[] {"org/springframework/expression/EvaluationException"}); 156 mv.visitCode(); 157 158 CodeFlow cf = new CodeFlow(className, cw); 159 160 // Ask the expression AST to generate the body of the method 161 try { 162 expressionToCompile.generateCode(mv, cf); 163 } 164 catch (IllegalStateException ex) { 165 if (logger.isDebugEnabled()) { 166 logger.debug(expressionToCompile.getClass().getSimpleName() + 167 ".generateCode opted out of compilation: " + ex.getMessage()); 168 } 169 return null; 170 } 171 172 CodeFlow.insertBoxIfNecessary(mv, cf.lastDescriptor()); 173 if ("V".equals(cf.lastDescriptor())) { 174 mv.visitInsn(ACONST_NULL); 175 } 176 mv.visitInsn(ARETURN); 177 178 mv.visitMaxs(0, 0); // not supplied due to COMPUTE_MAXS 179 mv.visitEnd(); 180 cw.visitEnd(); 181 182 cf.finish(); 183 184 byte[] data = cw.toByteArray(); 185 // TODO need to make this conditionally occur based on a debug flag 186 // dump(expressionToCompile.toStringAST(), clazzName, data); 187 return loadClass(StringUtils.replace(className, "/", "."), data); 188 } 189 190 /** 191 * Load a compiled expression class. Makes sure the classloaders aren't used too much 192 * because they anchor compiled classes in memory and prevent GC. If you have expressions 193 * continually recompiling over time then by replacing the classloader periodically 194 * at least some of the older variants can be garbage collected. 195 * @param name the name of the class 196 * @param bytes the bytecode for the class 197 * @return the Class object for the compiled expression 198 */ 199 @SuppressWarnings("unchecked") 200 private Class<? extends CompiledExpression> loadClass(String name, byte[] bytes) { 201 if (this.ccl.getClassesDefinedCount() > CLASSES_DEFINED_LIMIT) { 202 this.ccl = new ChildClassLoader(this.ccl.getParent()); 203 } 204 return (Class<? extends CompiledExpression>) this.ccl.defineClass(name, bytes); 205 } 206 207 208 /** 209 * Factory method for compiler instances. The returned SpelCompiler will 210 * attach a class loader as the child of the given class loader and this 211 * child will be used to load compiled expressions. 212 * @param classLoader the ClassLoader to use as the basis for compilation 213 * @return a corresponding SpelCompiler instance 214 */ 215 public static SpelCompiler getCompiler(@Nullable ClassLoader classLoader) { 216 ClassLoader clToUse = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); 217 synchronized (compilers) { 218 SpelCompiler compiler = compilers.get(clToUse); 219 if (compiler == null) { 220 compiler = new SpelCompiler(clToUse); 221 compilers.put(clToUse, compiler); 222 } 223 return compiler; 224 } 225 } 226 227 /** 228 * Request that an attempt is made to compile the specified expression. 229 * It may fail if components of the expression are not suitable for compilation 230 * or the data types involved are not suitable for compilation. Used for testing. 231 * @param expression the expression to compile 232 * @return {@code true} if the expression was successfully compiled, 233 * {@code false} otherwise 234 */ 235 public static boolean compile(Expression expression) { 236 return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression()); 237 } 238 239 /** 240 * Request to revert to the interpreter for expression evaluation. 241 * Any compiled form is discarded but can be recreated by later recompiling again. 242 * @param expression the expression 243 */ 244 public static void revertToInterpreted(Expression expression) { 245 if (expression instanceof SpelExpression) { 246 ((SpelExpression) expression).revertToInterpreted(); 247 } 248 } 249 250 251 /** 252 * A ChildClassLoader will load the generated compiled expression classes. 253 */ 254 private static class ChildClassLoader extends URLClassLoader { 255 256 private static final URL[] NO_URLS = new URL[0]; 257 258 private int classesDefinedCount = 0; 259 260 public ChildClassLoader(@Nullable ClassLoader classLoader) { 261 super(NO_URLS, classLoader); 262 } 263 264 public Class<?> defineClass(String name, byte[] bytes) { 265 Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length); 266 this.classesDefinedCount++; 267 return clazz; 268 } 269 270 public int getClassesDefinedCount() { 271 return this.classesDefinedCount; 272 } 273 } 274 275 276 /** 277 * An ASM ClassWriter extension bound to the SpelCompiler's ClassLoader. 278 */ 279 private class ExpressionClassWriter extends ClassWriter { 280 281 public ExpressionClassWriter() { 282 super(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); 283 } 284 285 @Override 286 protected ClassLoader getClassLoader() { 287 return ccl; 288 } 289 } 290 291}