Revision of SpelCompiler support, resolving a subpackage cycle through moving CodeFlow and CompilablePropertyAccessor to the main spel package

Also contains explicit ClassLoader management, passed through StandardBeanExpressionResolver and SpelParserConfiguration to SpelCompiler lookup.

Issue: SPR-10943
This commit is contained in:
Juergen Hoeller 2014-07-16 14:41:46 +02:00
parent 8fb592712c
commit bad74dc836
47 changed files with 600 additions and 511 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@ -27,6 +27,7 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
@ -58,7 +59,7 @@ public class StandardBeanExpressionResolver implements BeanExpressionResolver {
private String expressionSuffix = DEFAULT_EXPRESSION_SUFFIX;
private ExpressionParser expressionParser = new SpelExpressionParser();
private ExpressionParser expressionParser;
private final Map<String, Expression> expressionCache = new ConcurrentHashMap<String, Expression>(256);
@ -81,6 +82,23 @@ public class StandardBeanExpressionResolver implements BeanExpressionResolver {
};
/**
* Create a new {@code StandardBeanExpressionResolver} with default settings.
*/
public StandardBeanExpressionResolver() {
this.expressionParser = new SpelExpressionParser();
}
/**
* Create a new {@code StandardBeanExpressionResolver} with the given bean class loader,
* using it as the basis for expression compilation.
* @param beanClassLoader the factory's bean class loader
*/
public StandardBeanExpressionResolver(ClassLoader beanClassLoader) {
this.expressionParser = new SpelExpressionParser(new SpelParserConfiguration(null, beanClassLoader));
}
/**
* Set the prefix that an expression string starts with.
* The default is "#{".

View File

@ -548,7 +548,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Tell the internal bean factory to use the context's class loader etc.
beanFactory.setBeanClassLoader(getClassLoader());
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver());
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
// Configure the bean factory with context callbacks.

View File

@ -1,11 +1,11 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.expression.spel.standard;
package org.springframework.expression.spel;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
@ -40,13 +40,15 @@ public class CodeFlow implements Opcodes {
* sub-expressions like the expressions for the argument values in a method invocation
* expression.
*/
private Stack<ArrayList<String>> compilationScopes;
private final Stack<ArrayList<String>> compilationScopes;
public CodeFlow() {
compilationScopes = new Stack<ArrayList<String>>();
compilationScopes.add(new ArrayList<String>());
this.compilationScopes = new Stack<ArrayList<String>>();
this.compilationScopes.add(new ArrayList<String>());
}
/**
* Push the byte code to load the target (i.e. what was passed as the first argument
* to CompiledExpression.getValue(target, context))
@ -62,7 +64,7 @@ public class CodeFlow implements Opcodes {
*/
public void pushDescriptor(String descriptor) {
Assert.notNull(descriptor);
compilationScopes.peek().add(descriptor);
this.compilationScopes.peek().add(descriptor);
}
/**
@ -71,7 +73,7 @@ public class CodeFlow implements Opcodes {
* each argument will be evaluated in a new scope.
*/
public void enterCompilationScope() {
compilationScopes.push(new ArrayList<String>());
this.compilationScopes.push(new ArrayList<String>());
}
/**
@ -80,18 +82,17 @@ public class CodeFlow implements Opcodes {
* returns us to the previous (outer) scope.
*/
public void exitCompilationScope() {
compilationScopes.pop();
this.compilationScopes.pop();
}
/**
* @return the descriptor for the item currently on top of the stack (in the current
* scope)
* @return the descriptor for the item currently on top of the stack (in the current scope)
*/
public String lastDescriptor() {
if (compilationScopes.peek().size()==0) {
if (this.compilationScopes.peek().size() == 0) {
return null;
}
return compilationScopes.peek().get(compilationScopes.peek().size()-1);
return this.compilationScopes.peek().get(this.compilationScopes.peek().size() - 1);
}
/**
@ -105,13 +106,14 @@ public class CodeFlow implements Opcodes {
}
}
/**
* Insert any necessary cast and value call to convert from a boxed type to a
* primitive value
* @param mv the method visitor into which instructions should be inserted
* @param ch the primitive type desired as output
* @param isObject indicates whether the type on the stack is being thought of as
* Object (and so requires a cast)
* @param isObject indicates whether the type on the stack is being thought of
* as Object (and so requires a cast)
*/
public static void insertUnboxInsns(MethodVisitor mv, char ch, boolean isObject) {
switch (ch) {
@ -179,14 +181,14 @@ public class CodeFlow implements Opcodes {
*/
public static String createSignatureDescriptor(Method method) {
Class<?>[] params = method.getParameterTypes();
StringBuilder s = new StringBuilder();
s.append("(");
for (int i = 0, max = params.length; i < max; i++) {
s.append(toJVMDescriptor(params[i]));
StringBuilder sb = new StringBuilder();
sb.append("(");
for (Class<?> param : params) {
sb.append(toJVMDescriptor(param));
}
s.append(")");
s.append(toJVMDescriptor(method.getReturnType()));
return s.toString();
sb.append(")");
sb.append(toJVMDescriptor(method.getReturnType()));
return sb.toString();
}
/**
@ -199,13 +201,13 @@ public class CodeFlow implements Opcodes {
*/
public static String createSignatureDescriptor(Constructor<?> ctor) {
Class<?>[] params = ctor.getParameterTypes();
StringBuilder s = new StringBuilder();
s.append("(");
for (int i = 0, max = params.length; i < max; i++) {
s.append(toJVMDescriptor(params[i]));
StringBuilder sb = new StringBuilder();
sb.append("(");
for (Class<?> param : params) {
sb.append(toJVMDescriptor(param));
}
s.append(")V");
return s.toString();
sb.append(")V");
return sb.toString();
}
/**
@ -213,7 +215,6 @@ public class CodeFlow implements Opcodes {
* used in the compilation process, this is the one the JVM wants, so this one
* includes any necessary trailing semicolon (e.g. Ljava/lang/String; rather than
* Ljava/lang/String)
*
* @param clazz a class
* @return the JVM descriptor for the class
*/
@ -262,39 +263,39 @@ public class CodeFlow implements Opcodes {
}
/**
* Determine the descriptor for an object instance (or null).
* @param value an object (possibly null)
* @return the type descriptor for the object (descriptor is "Ljava/lang/Object" for
* null value)
* Determine the descriptor for an object instance (or {@code null}).
* @param value an object (possibly {@code null})
* @return the type descriptor for the object
* (descriptor is "Ljava/lang/Object" for {@code null} value)
*/
public static String toDescriptorFromObject(Object value) {
if (value == null) {
return "Ljava/lang/Object";
} else {
}
else {
return toDescriptor(value.getClass());
}
}
/**
* @param descriptor type descriptor
* @return true if the descriptor is for a boolean primitive or boolean reference type
* @return {@code true} if the descriptor is for a boolean primitive or boolean reference type
*/
public static boolean isBooleanCompatible(String descriptor) {
return descriptor != null
&& (descriptor.equals("Z") || descriptor.equals("Ljava/lang/Boolean"));
return (descriptor != null && (descriptor.equals("Z") || descriptor.equals("Ljava/lang/Boolean")));
}
/**
* @param descriptor type descriptor
* @return true if the descriptor is for a primitive type
* @return {@code true} if the descriptor is for a primitive type
*/
public static boolean isPrimitive(String descriptor) {
return descriptor!=null && descriptor.length()==1;
return (descriptor != null && descriptor.length() == 1);
}
/**
* @param descriptor the descriptor for a possible primitive array
* @return true if the descriptor is for a primitive array (e.g. "[[I")
* @return {@code true} if the descriptor is for a primitive array (e.g. "[[I")
*/
public static boolean isPrimitiveArray(String descriptor) {
boolean primitive = true;
@ -312,14 +313,13 @@ public class CodeFlow implements Opcodes {
/**
* Determine if boxing/unboxing can get from one type to the other. Assumes at least
* one of the types is in boxed form (i.e. single char descriptor).
*
* @return true if it is possible to get (via boxing) from one descriptor to the other
* @return {@code true} if it is possible to get (via boxing) from one descriptor to the other
*/
public static boolean areBoxingCompatible(String desc1, String desc2) {
if (desc1.equals(desc2)) {
return true;
}
if (desc1.length()==1) {
if (desc1.length() == 1) {
if (desc1.equals("D")) {
return desc2.equals("Ljava/lang/Double");
}
@ -336,7 +336,7 @@ public class CodeFlow implements Opcodes {
return desc2.equals("Ljava/lang/Boolean");
}
}
else if (desc2.length()==1) {
else if (desc2.length() == 1) {
if (desc2.equals("D")) {
return desc1.equals("Ljava/lang/Double");
}
@ -361,14 +361,14 @@ public class CodeFlow implements Opcodes {
* compilation process only (currently) supports certain number types. These are
* double, float, long and int.
* @param descriptor the descriptor for a type
* @return true if the descriptor is for a supported numeric type or boolean
* @return {@code true} if the descriptor is for a supported numeric type or boolean
*/
public static boolean isPrimitiveOrUnboxableSupportedNumberOrBoolean(String descriptor) {
if (descriptor==null) {
if (descriptor == null) {
return false;
}
if (descriptor.length()==1) {
return "DFJZI".indexOf(descriptor.charAt(0))!=-1;
if (descriptor.length( )== 1) {
return ("DFJZI".indexOf(descriptor.charAt(0)) != -1);
}
if (descriptor.startsWith("Ljava/lang/")) {
if (descriptor.equals("Ljava/lang/Double") || descriptor.equals("Ljava/lang/Integer") ||
@ -385,14 +385,14 @@ public class CodeFlow implements Opcodes {
* process only (currently) supports certain number types. These are double, float,
* long and int.
* @param descriptor the descriptor for a type
* @return true if the descriptor is for a supported numeric type
* @return {@code true} if the descriptor is for a supported numeric type
*/
public static boolean isPrimitiveOrUnboxableSupportedNumber(String descriptor) {
if (descriptor==null) {
if (descriptor == null) {
return false;
}
if (descriptor.length()==1) {
return "DFJI".indexOf(descriptor.charAt(0))!=-1;
if (descriptor.length() == 1) {
return ("DFJI".indexOf(descriptor.charAt(0)) != -1);
}
if (descriptor.startsWith("Ljava/lang/")) {
if (descriptor.equals("Ljava/lang/Double") || descriptor.equals("Ljava/lang/Integer") ||
@ -404,12 +404,11 @@ public class CodeFlow implements Opcodes {
}
/**
* @param descriptor a descriptor for a type that should have a primitive
* representation
* @param descriptor a descriptor for a type that should have a primitive representation
* @return the single character descriptor for a primitive input descriptor
*/
public static char toPrimitiveTargetDesc(String descriptor) {
if (descriptor.length()==1) {
if (descriptor.length() == 1) {
return descriptor.charAt(0);
}
if (descriptor.equals("Ljava/lang/Double")) {
@ -438,13 +437,13 @@ public class CodeFlow implements Opcodes {
* @param descriptor the descriptor of the type to cast to
*/
public static void insertCheckCast(MethodVisitor mv, String descriptor) {
if (descriptor.length()!=1) {
if (descriptor.charAt(0)=='[') {
if (CodeFlow.isPrimitiveArray(descriptor)) {
if (descriptor.length() != 1) {
if (descriptor.charAt(0) == '[') {
if (isPrimitiveArray(descriptor)) {
mv.visitTypeInsn(CHECKCAST, descriptor);
}
else {
mv.visitTypeInsn(CHECKCAST, descriptor+";");
mv.visitTypeInsn(CHECKCAST, descriptor + ";");
}
}
else {
@ -557,14 +556,14 @@ public class CodeFlow implements Opcodes {
}
else {
if (name.charAt(0) != '[') {
return new StringBuilder("L").append(type.getName().replace('.', '/')).toString();
return "L" + type.getName().replace('.', '/');
}
else {
if (name.endsWith(";")) {
return name.substring(0, name.length() - 1).replace('.', '/');
}
else {
return name; // array has primitive component type
return name; // array has primitive component type
}
}
}
@ -595,7 +594,7 @@ public class CodeFlow implements Opcodes {
int typesCount = types.length;
String[] descriptors = new String[typesCount];
for (int p = 0; p < typesCount; p++) {
descriptors[p] = CodeFlow.toDescriptor(types[p]);
descriptors[p] = toDescriptor(types[p]);
}
return descriptors;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2002-2014 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.
@ -14,13 +14,11 @@
* limitations under the License.
*/
package org.springframework.expression;
package org.springframework.expression.spel;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.PropertyAccessor;
/**
* A compilable property accessor is able to generate bytecode that represents
@ -33,21 +31,22 @@ import org.springframework.expression.spel.standard.CodeFlow;
public interface CompilablePropertyAccessor extends PropertyAccessor, Opcodes {
/**
* @return true if this property accessor is currently suitable for compilation.
* Return {@code true} if this property accessor is currently suitable for compilation.
*/
boolean isCompilable();
/**
* Generate the bytecode the performs the access operation into the specified MethodVisitor using
* context information from the codeflow where necessary.
* @param propertyReference the property reference for which code is being generated
* Return the type of the accessed property - may only be known once an access has occurred.
*/
Class<?> getPropertyType();
/**
* Generate the bytecode the performs the access operation into the specified MethodVisitor
* using context information from the codeflow where necessary.
* @param propertyName the name of the property
* @param mv the Asm method visitor into which code should be generated
* @param codeflow the current state of the expression compiler
*/
void generateCode(PropertyOrFieldReference propertyReference, MethodVisitor mv, CodeFlow codeflow);
void generateCode(String propertyName, MethodVisitor mv, CodeFlow codeflow);
/**
* @return the type of the accessed property - may only be known once an access has occurred.
*/
Class<?> getPropertyType();
}

View File

@ -1,11 +1,11 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -30,8 +30,8 @@ import org.springframework.expression.EvaluationException;
public abstract class CompiledExpression {
/**
* Subclasses of CompiledExpression generated by SpelCompiler will provide an implementation of
* this method.
* Subclasses of CompiledExpression generated by SpelCompiler will provide an
* implementation of this method.
*/
public abstract Object getValue(Object target, EvaluationContext context) throws EvaluationException;

View File

@ -1,11 +1,11 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -24,22 +24,24 @@ package org.springframework.expression.spel;
* @since 4.1
*/
public enum SpelCompilerMode {
/**
/**
* The compiler is switched off, this is the default.
*/
off,
OFF,
/**
* In immediate mode, expressions are compiled as soon as possible (usually after 1 interpreted run).
* If a compiled expression fails it will throw an exception to the caller.
*/
immediate,
IMMEDIATE,
/**
* In mixed mode, expression evaluate silently switches between interpreted and compiled over time.
* After a number of runs the expression gets compiled. If it later fails (possibly due to inferred
* type information changing) then that will be caught internally and the system switches back to
* interpreted mode. It may subsequently compile it again later.
*/
mixed
}
MIXED
}

View File

@ -30,24 +30,42 @@ import org.springframework.core.SpringProperties;
*/
public class SpelParserConfiguration {
private final boolean autoGrowNullReferences;
private final boolean autoGrowCollections;
private static SpelCompilerMode defaultCompilerMode = SpelCompilerMode.off;
private SpelCompilerMode compilerMode;
private final int maximumAutoGrowSize;
private static final SpelCompilerMode defaultCompilerMode;
static {
String compilerMode = SpringProperties.getProperty("spring.expression.compiler.mode");
if (compilerMode != null) {
defaultCompilerMode = SpelCompilerMode.valueOf(compilerMode.toLowerCase());
// System.out.println("SpelCompiler: switched to "+defaultCompilerMode+" mode");
}
defaultCompilerMode = (compilerMode != null ?
SpelCompilerMode.valueOf(compilerMode.toUpperCase()) : SpelCompilerMode.OFF);
}
private final SpelCompilerMode compilerMode;
private final ClassLoader compilerClassLoader;
private final boolean autoGrowNullReferences;
private final boolean autoGrowCollections;
private final int maximumAutoGrowSize;
/**
* Create a new {@link SpelParserConfiguration} instance with default settings.
*/
public SpelParserConfiguration() {
this(null, null, false, false, Integer.MAX_VALUE);
}
/**
* Create a new {@link SpelParserConfiguration} instance.
* @param compilerMode the compiler mode for the parser
* @param compilerClassLoader the ClassLoader to use as the basis for expression compilation
*/
public SpelParserConfiguration(SpelCompilerMode compilerMode, ClassLoader compilerClassLoader) {
this(compilerMode, compilerClassLoader, false, false, Integer.MAX_VALUE);
}
/**
* Create a new {@link SpelParserConfiguration} instance.
* @param autoGrowNullReferences if null references should automatically grow
@ -55,7 +73,7 @@ public class SpelParserConfiguration {
* @see #SpelParserConfiguration(boolean, boolean, int)
*/
public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections) {
this(autoGrowNullReferences, autoGrowCollections, Integer.MAX_VALUE);
this(null, null, autoGrowNullReferences, autoGrowCollections, Integer.MAX_VALUE);
}
/**
@ -65,19 +83,28 @@ public class SpelParserConfiguration {
* @param maximumAutoGrowSize the maximum size that the collection can auto grow
*/
public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) {
this(null, null, autoGrowNullReferences, autoGrowCollections, maximumAutoGrowSize);
}
/**
* Create a new {@link SpelParserConfiguration} instance.
* @param compilerMode the compiler mode that parsers using this configuration object should use
* @param compilerClassLoader the ClassLoader to use as the basis for expression compilation
* @param autoGrowNullReferences if null references should automatically grow
* @param autoGrowCollections if collections should automatically grow
* @param maximumAutoGrowSize the maximum size that the collection can auto grow
*/
public SpelParserConfiguration(SpelCompilerMode compilerMode, ClassLoader compilerClassLoader,
boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) {
this.compilerMode = (compilerMode != null ? compilerMode : defaultCompilerMode);
this.compilerClassLoader = compilerClassLoader;
this.autoGrowNullReferences = autoGrowNullReferences;
this.autoGrowCollections = autoGrowCollections;
this.maximumAutoGrowSize = maximumAutoGrowSize;
this.compilerMode = defaultCompilerMode;
}
/**
* @param compilerMode the compiler mode that parsers using this configuration object should use
*/
public void setCompilerMode(SpelCompilerMode compilerMode) {
this.compilerMode = compilerMode;
}
/**
* @return the configuration mode for parsers using this configuration object
*/
@ -85,6 +112,13 @@ public class SpelParserConfiguration {
return this.compilerMode;
}
/**
* @return the ClassLoader to use as the basis for expression compilation
*/
public ClassLoader getCompilerClassLoader() {
return this.compilerClassLoader;
}
/**
* @return {@code true} if {@code null} references should be automatically grown
*/

View File

@ -16,7 +16,7 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**

View File

@ -19,9 +19,9 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Represents a DOT separated expression sequence, such as 'property1.property2.methodOne()'

View File

@ -33,11 +33,11 @@ import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.ReflectiveConstructorExecutor;
/**

View File

@ -20,8 +20,8 @@ import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Represents the elvis operator ?:. For an expression "a?:b" if a is not null, the value

View File

@ -18,7 +18,7 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.CodeFlow;
/**
* Expression language AST node that represents a float literal.

View File

@ -25,10 +25,10 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.ReflectionHelper;
import org.springframework.util.ReflectionUtils;

View File

@ -32,10 +32,10 @@ import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
/**

View File

@ -17,7 +17,7 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.CodeFlow;
/**
* Expression language AST node that represents an integer literal.

View File

@ -18,7 +18,7 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.CodeFlow;
/**
* Expression language AST node that represents a long integer literal.

View File

@ -32,10 +32,10 @@ import org.springframework.expression.ExpressionInvocationTargetException;
import org.springframework.expression.MethodExecutor;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.ReflectiveMethodExecutor;
import org.springframework.expression.spel.support.ReflectiveMethodResolver;

View File

@ -18,7 +18,7 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.CodeFlow;
/**
* Expression language AST node that represents null.

View File

@ -20,10 +20,10 @@ import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**

View File

@ -16,16 +16,15 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.util.NumberUtils;
/**

View File

@ -19,8 +19,8 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**

View File

@ -15,11 +15,12 @@
*/
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
import org.springframework.util.NumberUtils;

View File

@ -16,11 +16,12 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
import org.springframework.util.NumberUtils;

View File

@ -16,11 +16,12 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
import org.springframework.util.NumberUtils;

View File

@ -16,11 +16,12 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
import org.springframework.util.NumberUtils;

View File

@ -16,15 +16,14 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.util.NumberUtils;
/**

View File

@ -16,15 +16,14 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.util.NumberUtils;
/**

View File

@ -19,8 +19,8 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**

View File

@ -19,10 +19,10 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**

View File

@ -16,17 +16,16 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;

View File

@ -16,12 +16,11 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.spel.standard.CodeFlow;
import java.math.BigDecimal;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.util.ClassUtils;
import org.springframework.util.NumberUtils;

View File

@ -20,10 +20,10 @@ import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Type;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;

View File

@ -19,10 +19,10 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@ -24,15 +24,15 @@ import java.util.Map;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.CompilablePropertyAccessor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.CompilablePropertyAccessor;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
/**
@ -331,19 +331,14 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@Override
public boolean isCompilable() {
if (this.cachedReadAccessor == null) {
return false;
}
if (this.cachedReadAccessor instanceof CompilablePropertyAccessor) {
return ((CompilablePropertyAccessor)this.cachedReadAccessor).isCompilable();
}
return false;
return (this.cachedReadAccessor instanceof CompilablePropertyAccessor &&
((CompilablePropertyAccessor) this.cachedReadAccessor).isCompilable());
}
@Override
public void generateCode(MethodVisitor mv,CodeFlow codeflow) {
((CompilablePropertyAccessor)this.cachedReadAccessor).generateCode(this, mv, codeflow);
codeflow.pushDescriptor(exitTypeDescriptor);
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
((CompilablePropertyAccessor) this.cachedReadAccessor).generateCode(this.name, mv, codeflow);
codeflow.pushDescriptor(this.exitTypeDescriptor);
}

View File

@ -18,7 +18,7 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.CodeFlow;
/**
* Expression language AST node that represents a real literal.

View File

@ -21,11 +21,11 @@ import org.springframework.asm.Opcodes;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
@ -58,6 +58,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
*/
protected String exitTypeDescriptor;
public SpelNodeImpl(int pos, SpelNodeImpl... operands) {
this.pos = pos;
// pos combines start and end so can never be zero because tokens cannot be zero length
@ -184,7 +185,6 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
* Check whether a node can be compiled to bytecode. The reasoning in each node may
* be different but will typically involve checking whether the exit type descriptor
* of the node is known and any relevant child nodes are compilable.
*
* @return true if this node can be compiled to bytecode
*/
public boolean isCompilable() {
@ -196,7 +196,6 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
* the current expression being compiled is available in the codeflow object. For
* example it will include information about the type of the object currently
* on the stack.
*
* @param mv the ASM MethodVisitor into which code should be generated
* @param codeflow a context object with info about what is on the stack
*/

View File

@ -18,7 +18,7 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.CodeFlow;
/**
* Expression language AST node that represents a string literal.

View File

@ -20,10 +20,10 @@ import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Represents a ternary expression, for example: "someCheck()?true:false".

View File

@ -22,8 +22,8 @@ import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Type;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Represents a reference to a type, for example "T(String)" or "T(com.somewhere.Foo)"

View File

@ -19,9 +19,9 @@ package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Represents a variable reference, eg. #someVar. Note this is different to a *local*

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@ -29,7 +29,48 @@ import org.springframework.expression.spel.InternalParseException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelParseException;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.ast.*;
import org.springframework.expression.spel.ast.Assign;
import org.springframework.expression.spel.ast.BeanReference;
import org.springframework.expression.spel.ast.BooleanLiteral;
import org.springframework.expression.spel.ast.CompoundExpression;
import org.springframework.expression.spel.ast.ConstructorReference;
import org.springframework.expression.spel.ast.Elvis;
import org.springframework.expression.spel.ast.FunctionReference;
import org.springframework.expression.spel.ast.Identifier;
import org.springframework.expression.spel.ast.Indexer;
import org.springframework.expression.spel.ast.InlineList;
import org.springframework.expression.spel.ast.Literal;
import org.springframework.expression.spel.ast.MethodReference;
import org.springframework.expression.spel.ast.NullLiteral;
import org.springframework.expression.spel.ast.OpAnd;
import org.springframework.expression.spel.ast.OpDec;
import org.springframework.expression.spel.ast.OpDivide;
import org.springframework.expression.spel.ast.OpEQ;
import org.springframework.expression.spel.ast.OpGE;
import org.springframework.expression.spel.ast.OpGT;
import org.springframework.expression.spel.ast.OpInc;
import org.springframework.expression.spel.ast.OpLE;
import org.springframework.expression.spel.ast.OpLT;
import org.springframework.expression.spel.ast.OpMinus;
import org.springframework.expression.spel.ast.OpModulus;
import org.springframework.expression.spel.ast.OpMultiply;
import org.springframework.expression.spel.ast.OpNE;
import org.springframework.expression.spel.ast.OpOr;
import org.springframework.expression.spel.ast.OpPlus;
import org.springframework.expression.spel.ast.OperatorBetween;
import org.springframework.expression.spel.ast.OperatorInstanceof;
import org.springframework.expression.spel.ast.OperatorMatches;
import org.springframework.expression.spel.ast.OperatorNot;
import org.springframework.expression.spel.ast.OperatorPower;
import org.springframework.expression.spel.ast.Projection;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
import org.springframework.expression.spel.ast.QualifiedIdentifier;
import org.springframework.expression.spel.ast.Selection;
import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.expression.spel.ast.StringLiteral;
import org.springframework.expression.spel.ast.Ternary;
import org.springframework.expression.spel.ast.TypeReference;
import org.springframework.expression.spel.ast.VariableReference;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -84,13 +125,13 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
this.constructedNodes.clear();
SpelNodeImpl ast = eatExpression();
if (moreTokens()) {
throw new SpelParseException(peekToken().startpos,SpelMessage.MORE_INPUT,toString(nextToken()));
throw new SpelParseException(peekToken().startpos, SpelMessage.MORE_INPUT, toString(nextToken()));
}
Assert.isTrue(this.constructedNodes.isEmpty());
return new SpelExpression(expressionString, ast, this.configuration);
}
catch (InternalParseException ipe) {
throw ipe.getCause();
catch (InternalParseException ex) {
throw ex.getCause();
}
}

View File

@ -1,11 +1,11 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -20,26 +20,30 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.asm.ClassWriter;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.CompiledExpression;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* A SpelCompiler will take a regular parsed expression and create (and load) a class
* containing byte code that does the same thing as that expression. The compiled form of
* an expression will evaluate far faster than the interpreted form.
* <p>
* The SpelCompiler is not currently handling all expression types but covers many of the
* common cases. The framework is extensible to cover more cases in the future. For
*
* <p>The SpelCompiler is not currently handling all expression types but covers many of
* the common cases. The framework is extensible to cover more cases in the future. For
* absolute maximum speed there is *no checking* in the compiled code. The compiled
* version of the expression uses information learned during interpreted runs of the
* expression when it generates the byte code. For example if it knows that a particular
@ -48,64 +52,40 @@ import org.springframework.util.ClassUtils;
* performance but should the dereference result in something other than a map, the
* compiled expression will fail - like a ClassCastException would occur if passing data
* of an unexpected type in a regular Java program.
* <p>
* Due to the lack of checking there are likely some expressions that should never be
*
* <p>Due to the lack of checking there are likely some expressions that should never be
* compiled, for example if an expression is continuously dealing with different types of
* data. Due to these cases the compiler is something that must be selectively turned on
* for an associated SpelExpressionParser (through the {@link SpelParserConfiguration}
* object), it is not on by default.
* <p>
* Individual expressions can be compiled by calling
* <tt>SpelCompiler.compile(expression)</tt>.
*
* <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}.
*
* @author Andy Clement
* @since 4.1
*/
public class SpelCompiler implements Opcodes {
// Default number of times to interpret an expression before compiling it
private static int DEFAULT_INTERPRETED_COUNT_THRESHOLD = 100;
// Once an expression is evaluated the threshold number of times, it will be a candidate for compilation
public static int interpretedCountThreshold = DEFAULT_INTERPRETED_COUNT_THRESHOLD;
// Useful for debugging
public static final boolean verbose = false;
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 Map<ClassLoader,SpelCompiler> compilers = Collections.synchronizedMap(new WeakHashMap<ClassLoader,SpelCompiler>());
private static final Map<ClassLoader, SpelCompiler> compilers =
new ConcurrentReferenceHashMap<ClassLoader, SpelCompiler>();
// The child classloader used to load the compiled expression classes
private ChildClassLoader ccl;
// The child ClassLoader used to load the compiled expression classes
private final ChildClassLoader ccl;
// counter suffix for generated classes within this SpelCompiler instance
private int suffixId;
private final AtomicInteger suffixId = new AtomicInteger(1);
/**
* Factory method for compiler instances. The returned SpelCompiler will
* attach a class loader as the child of the default class loader and this
* child will be used to load compiled expressions.
*
* @return a SpelCompiler instance
*/
public static SpelCompiler getCompiler() {
ClassLoader classloader = ClassUtils.getDefaultClassLoader();
synchronized (compilers) {
SpelCompiler compiler = compilers.get(classloader);
if (compiler == null) {
compiler = new SpelCompiler(classloader);
compilers.put(classloader,compiler);
}
return compiler;
}
}
private SpelCompiler(ClassLoader classloader) {
this.ccl = new ChildClassLoader(classloader);
this.suffixId = 1;
}
/**
* Attempt compilation of the supplied expression. A check is
* made to see if it is compilable before compilation proceeds. The
@ -118,31 +98,27 @@ public class SpelCompiler implements Opcodes {
*/
public CompiledExpression compile(SpelNodeImpl expression) {
if (expression.isCompilable()) {
if (verbose) {
System.out.println("SpEL: compiling " + expression.toStringAST());
if (logger.isDebugEnabled()) {
logger.debug("SpEL: compiling " + expression.toStringAST());
}
Class<? extends CompiledExpression> clazz = createExpressionClass(expression);
try {
CompiledExpression instance = clazz.newInstance();
return instance;
return clazz.newInstance();
}
catch (InstantiationException ie) {
ie.printStackTrace();
}
catch (IllegalAccessException iae) {
iae.printStackTrace();
catch (Exception ex) {
throw new IllegalStateException("Failed to instantiate CompiledExpression", ex);
}
}
else {
if (verbose) {
System.out.println("SpEL: unable to compile " + expression.toStringAST());
if (logger.isDebugEnabled()) {
logger.debug("SpEL: unable to compile " + expression.toStringAST());
}
}
return null;
}
private synchronized int getNextSuffix() {
return suffixId++;
private int getNextSuffix() {
return this.suffixId.incrementAndGet();
}
/**
@ -152,7 +128,6 @@ public class SpelCompiler implements Opcodes {
*/
@SuppressWarnings("unchecked")
private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl expressionToCompile) {
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
String clazzName = "spel/Ex" + getNextSuffix();
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES);
@ -179,7 +154,7 @@ public class SpelCompiler implements Opcodes {
expressionToCompile.generateCode(mv,codeflow);
CodeFlow.insertBoxIfNecessary(mv,codeflow.lastDescriptor());
if (codeflow.lastDescriptor() == "V") {
if ("V".equals(codeflow.lastDescriptor())) {
mv.visitInsn(ACONST_NULL);
}
mv.visitInsn(ARETURN);
@ -190,44 +165,79 @@ public class SpelCompiler implements Opcodes {
byte[] data = cw.toByteArray();
// TODO need to make this conditionally occur based on a debug flag
// dump(expressionToCompile.toStringAST(), clazzName, data);
Class<? extends CompiledExpression> clazz = (Class<? extends CompiledExpression>) ccl.defineClass(clazzName.replaceAll("/","."),data);
return clazz;
return (Class<? extends CompiledExpression>) ccl.defineClass(clazzName.replaceAll("/","."),data);
}
/**
* Factory method for compiler instances. The returned SpelCompiler will
* attach a class loader as the child of the given class loader and this
* child will be used to load compiled expressions.
* @param classLoader the ClassLoader to use as the basis for compilation
* @return a corresponding SpelCompiler instance
*/
public static SpelCompiler getCompiler(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);
}
return compiler;
}
}
/**
* For debugging purposes, dump the specified byte code into a file on the disk. Not
* yet hooked in, needs conditionally calling based on a sys prop.
*
* 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
*/
public static boolean compile(Expression expression) {
return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression());
}
/**
* Request to revert to the interpreter for expression evaluation. Any compiled form
* is discarded but can be recreated by later recompiling again.
* @param expression the expression
*/
public static void revertToInterpreted(Expression expression) {
if (expression instanceof SpelExpression) {
((SpelExpression) expression).revertToInterpreted();
}
}
/**
* For debugging purposes, dump the specified byte code into a file on the disk.
* Not yet hooked in, needs conditionally calling based on a sys prop.
* @param expressionText the text of the expression compiled
* @param name the name of the class being used for the compiled expression
* @param bytecode the bytecode for the generated class
*/
@SuppressWarnings("unused")
private static void dump(String expressionText, String name, byte[] bytecode) {
name = name.replace('.', '/');
String dir = "";
if (name.indexOf('/') != -1) {
dir = name.substring(0, name.lastIndexOf('/'));
}
String dumplocation = null;
String nameToUse = name.replace('.', '/');
String dir = (nameToUse.indexOf('/') != -1 ? nameToUse.substring(0, nameToUse.lastIndexOf('/')) : "");
String dumpLocation = null;
try {
File tempfile = null;
tempfile = File.createTempFile("tmp", null);
tempfile.delete();
File f = new File(tempfile, dir);
File tempFile = File.createTempFile("tmp", null);
dumpLocation = tempFile + File.separator + nameToUse + ".class";
tempFile.delete();
File f = new File(tempFile, dir);
f.mkdirs();
dumplocation = tempfile + File.separator + name + ".class";
System.out.println("Expression '" + expressionText + "' compiled code dumped to "
+ dumplocation);
f = new File(dumplocation);
if (logger.isDebugEnabled()) {
logger.debug("Expression '" + expressionText + "' compiled code dumped to " + dumpLocation);
}
f = new File(dumpLocation);
FileOutputStream fos = new FileOutputStream(f);
fos.write(bytecode);
fos.flush();
fos.close();
}
catch (IOException ioe) {
throw new IllegalStateException("Unexpected problem dumping class "
+ name + " into " + dumplocation, ioe);
catch (IOException ex) {
throw new IllegalStateException("Unexpected problem dumping class " + nameToUse + " into " + dumpLocation, ex);
}
}
@ -246,32 +256,6 @@ public class SpelCompiler implements Opcodes {
public Class<?> defineClass(String name, byte[] bytes) {
return super.defineClass(name, bytes, 0, bytes.length);
}
}
/**
* 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
*/
public static boolean compile(Expression expression) {
if (expression instanceof SpelExpression) {
SpelExpression spelExpression = (SpelExpression)expression;
return spelExpression.compileExpression();
}
return false;
}
/**
* Request to revert to the interpreter for expression evaluation. Any compiled form
* is discarded but can be recreated by later recompiling again.
* @param expression the expression
*/
public static void revertToInterpreted(Expression expression) {
if (expression instanceof SpelExpression) {
SpelExpression spelExpression = (SpelExpression)expression;
spelExpression.revertToInterpreted();
}
}
}

View File

@ -44,25 +44,32 @@ import org.springframework.util.Assert;
*/
public class SpelExpression implements Expression {
// Number of times to interpret an expression before compiling it
private static final int INTERPRETED_COUNT_THRESHOLD = 100;
// Number of times to try compiling an expression before giving up
private static final int FAILED_ATTEMPTS_THRESHOLD = 100;
private final String expression;
// Holds the compiled form of the expression (if it has been compiled)
private CompiledExpression compiledAst;
private SpelNodeImpl ast;
private final SpelNodeImpl ast;
private final SpelParserConfiguration configuration;
// the default context is used if no override is supplied by the user
private EvaluationContext defaultContext;
private EvaluationContext evaluationContext;
// Holds the compiled form of the expression (if it has been compiled)
private CompiledExpression compiledAst;
// Count of many times as the expression been interpreted - can trigger compilation
// when certain limit reached
private int interpretedCount = 0;
private volatile int interpretedCount = 0;
// The number of times compilation was attempted and failed - enables us to eventually
// give up trying to compile it when it just doesn't seem to be possible.
private int failedAttempts = 0;
private volatile int failedAttempts = 0;
/**
@ -75,23 +82,44 @@ public class SpelExpression implements Expression {
}
/**
* Set the evaluation context that will be used if none is specified on an evaluation call.
* @param evaluationContext the evaluation context to use
*/
public void setEvaluationContext(EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
}
/**
* Return the default evaluation context that will be used if none is supplied on an evaluation call.
* @return the default evaluation context
*/
public EvaluationContext getEvaluationContext() {
if (this.evaluationContext == null) {
this.evaluationContext = new StandardEvaluationContext();
}
return this.evaluationContext;
}
// implementing Expression
@Override
public Object getValue() throws EvaluationException {
Object result = null;
if (compiledAst != null) {
Object result;
if (this.compiledAst != null) {
try {
return this.compiledAst.getValue(null,null);
} catch (Throwable t) {
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
@ -103,19 +131,20 @@ public class SpelExpression implements Expression {
@Override
public Object getValue(Object rootObject) throws EvaluationException {
Object result = null;
if (compiledAst!=null) {
Object result;
if (this.compiledAst != null) {
try {
return this.compiledAst.getValue(rootObject,null);
} catch (Throwable t) {
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
@ -128,23 +157,25 @@ public class SpelExpression implements Expression {
@SuppressWarnings("unchecked")
@Override
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
if (compiledAst!=null) {
if (this.compiledAst != null) {
try {
Object result = this.compiledAst.getValue(null,null);
if (expectedResultType == null) {
return (T)result;
} else {
}
else {
return ExpressionUtils.convertTypedValue(getEvaluationContext(), new TypedValue(result), expectedResultType);
}
} catch (Throwable t) {
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
@ -157,23 +188,25 @@ public class SpelExpression implements Expression {
@SuppressWarnings("unchecked")
@Override
public <T> T getValue(Object rootObject, Class<T> expectedResultType) throws EvaluationException {
if (compiledAst!=null) {
if (this.compiledAst != null) {
try {
Object result = this.compiledAst.getValue(rootObject,null);
Object result = this.compiledAst.getValue(rootObject, null);
if (expectedResultType == null) {
return (T)result;
} else {
}
else {
return ExpressionUtils.convertTypedValue(getEvaluationContext(), new TypedValue(result), expectedResultType);
}
} catch (Throwable t) {
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
@ -190,15 +223,16 @@ public class SpelExpression implements Expression {
try {
Object result = this.compiledAst.getValue(null,context);
return result;
} catch (Throwable t) {
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
@ -211,18 +245,19 @@ public class SpelExpression implements Expression {
@Override
public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
if (compiledAst!=null) {
if (this.compiledAst != null) {
try {
return this.compiledAst.getValue(rootObject,context);
} catch (Throwable t) {
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
@ -235,23 +270,25 @@ public class SpelExpression implements Expression {
@SuppressWarnings("unchecked")
@Override
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
if (compiledAst!=null) {
if (this.compiledAst != null) {
try {
Object result = this.compiledAst.getValue(null,context);
if (expectedResultType!=null) {
return (T) result;
} else {
if (expectedResultType != null) {
return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType);
}
} catch (Throwable t) {
else {
return (T) result;
}
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
@ -264,23 +301,25 @@ public class SpelExpression implements Expression {
@SuppressWarnings("unchecked")
@Override
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> expectedResultType) throws EvaluationException {
if (compiledAst!=null) {
if (this.compiledAst != null) {
try {
Object result = this.compiledAst.getValue(rootObject,context);
if (expectedResultType!=null) {
return (T) result;
} else {
if (expectedResultType != null) {
return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType);
}
} catch (Throwable t) {
else {
return (T) result;
}
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
@ -305,14 +344,14 @@ public class SpelExpression implements Expression {
Assert.notNull(context, "The EvaluationContext is required");
ExpressionState eState = new ExpressionState(context, this.configuration);
TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor();
return typeDescriptor != null ? typeDescriptor.getType() : null;
return (typeDescriptor != null ? typeDescriptor.getType() : null);
}
@Override
public Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), this.configuration);
TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor();
return typeDescriptor != null ? typeDescriptor.getType() : null;
return (typeDescriptor != null ? typeDescriptor.getType() : null);
}
@Override
@ -379,6 +418,7 @@ public class SpelExpression implements Expression {
this.ast.setValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration), value);
}
// impl only
/**
@ -386,17 +426,17 @@ public class SpelExpression implements Expression {
* @param expressionState the expression state used to determine compilation mode
*/
private void checkCompile(ExpressionState expressionState) {
interpretedCount++;
this.interpretedCount++;
SpelCompilerMode compilerMode = expressionState.getConfiguration().getCompilerMode();
if (compilerMode != SpelCompilerMode.off) {
if (compilerMode == SpelCompilerMode.immediate) {
if (interpretedCount > 1) {
if (compilerMode != SpelCompilerMode.OFF) {
if (compilerMode == SpelCompilerMode.IMMEDIATE) {
if (this.interpretedCount > 1) {
compileExpression();
}
}
else {
// compilerMode = SpelCompilerMode.mixed
if (interpretedCount > SpelCompiler.interpretedCountThreshold) {
// compilerMode = SpelCompilerMode.MIXED
if (this.interpretedCount > INTERPRETED_COUNT_THRESHOLD) {
compileExpression();
}
}
@ -410,20 +450,20 @@ public class SpelExpression implements Expression {
* no longer considered suitable for compilation.
*/
public boolean compileExpression() {
if (failedAttempts > 100) {
if (this.failedAttempts > FAILED_ATTEMPTS_THRESHOLD) {
// Don't try again
return false;
}
if (this.compiledAst == null) {
synchronized (expression) {
// Possibly compiled by another thread before this thread got into the
// sync block
synchronized (this.expression) {
// Possibly compiled by another thread before this thread got into the sync block
if (this.compiledAst != null) {
return true;
}
this.compiledAst = SpelCompiler.getCompiler().compile(this.ast);
SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader());
this.compiledAst = compiler.compile(this.ast);
if (this.compiledAst == null) {
failedAttempts++;
this.failedAttempts++;
}
}
}
@ -458,25 +498,6 @@ public class SpelExpression implements Expression {
return this.ast.toStringAST();
}
/**
* Return the default evaluation context that will be used if none is supplied on an evaluation call
* @return the default evaluation context
*/
public EvaluationContext getEvaluationContext() {
if (this.defaultContext == null) {
this.defaultContext = new StandardEvaluationContext();
}
return this.defaultContext;
}
/**
* Set the evaluation context that will be used if none is specified on an evaluation call.
* @param context an evaluation context
*/
public void setEvaluationContext(EvaluationContext context) {
this.defaultContext = context;
}
private TypedValue toTypedValue(Object object) {
if (object == null) {
return TypedValue.NULL;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@ -35,14 +35,14 @@ public class SpelExpressionParser extends TemplateAwareExpressionParser {
/**
* Create a parser with standard configuration.
* Create a parser with default settings.
*/
public SpelExpressionParser() {
this.configuration = new SpelParserConfiguration(false, false);
this.configuration = new SpelParserConfiguration();
}
/**
* Create a parser with some configured behavior.
* Create a parser with the specified configuration.
* @param configuration custom configuration options
*/
public SpelExpressionParser(SpelParserConfiguration configuration) {
@ -51,13 +51,13 @@ public class SpelExpressionParser extends TemplateAwareExpressionParser {
}
public SpelExpression parseRaw(String expressionString) throws ParseException {
return doParseExpression(expressionString, null);
}
@Override
protected SpelExpression doParseExpression(String expressionString, ParserContext context) throws ParseException {
return new InternalSpelExpressionParser(this.configuration).doParseExpression(expressionString, context);
}
public SpelExpression parseRaw(String expressionString) throws ParseException {
return doParseExpression(expressionString, null);
}
}

View File

@ -35,13 +35,12 @@ import org.springframework.core.convert.Property;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.style.ToStringCreator;
import org.springframework.expression.AccessException;
import org.springframework.expression.CompilablePropertyAccessor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.CompilablePropertyAccessor;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@ -370,10 +369,10 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
Method[] methods = getSortedClassMethods(clazz);
for (String methodSuffix : methodSuffixes) {
for (Method method : methods) {
if (method.getName().equals(prefix + methodSuffix)
&& method.getParameterTypes().length == numberOfParams
&& (!mustBeStatic || Modifier.isStatic(method.getModifiers()))
&& (requiredReturnTypes.isEmpty() || requiredReturnTypes.contains(method.getReturnType()))) {
if (method.getName().equals(prefix + methodSuffix) &&
method.getParameterTypes().length == numberOfParams &&
(!mustBeStatic || Modifier.isStatic(method.getModifiers())) &&
(requiredReturnTypes.isEmpty() || requiredReturnTypes.contains(method.getReturnType()))) {
return method;
}
}
@ -649,16 +648,6 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
throw new AccessException("Neither getter nor field found for property '" + name + "'");
}
@Override
public Class<?> getPropertyType() {
if (member instanceof Field) {
return ((Field)member).getType();
}
else {
return ((Method)member).getReturnType();
}
}
@Override
public boolean canWrite(EvaluationContext context, Object target, String name) {
@ -678,11 +667,20 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
return true;
}
@Override
public void generateCode(PropertyOrFieldReference propertyReference, MethodVisitor mv,CodeFlow codeflow) {
boolean isStatic = Modifier.isStatic(member.getModifiers());
@Override
public Class<?> getPropertyType() {
if (member instanceof Field) {
return ((Field) member).getType();
}
else {
return ((Method) member).getReturnType();
}
}
@Override
public void generateCode(String propertyName, MethodVisitor mv, CodeFlow codeflow) {
boolean isStatic = Modifier.isStatic(member.getModifiers());
String descriptor = codeflow.lastDescriptor();
String memberDeclaringClassSlashedDescriptor = member.getDeclaringClass().getName().replace('.','/');
if (!isStatic) {
@ -694,9 +692,12 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
}
if (member instanceof Field) {
mv.visitFieldInsn(isStatic?GETSTATIC:GETFIELD,memberDeclaringClassSlashedDescriptor,member.getName(),CodeFlow.toJVMDescriptor(((Field) member).getType()));
} else {
mv.visitMethodInsn(isStatic?INVOKESTATIC:INVOKEVIRTUAL, memberDeclaringClassSlashedDescriptor, member.getName(),CodeFlow.createSignatureDescriptor((Method)member),false);
mv.visitFieldInsn(isStatic ? GETSTATIC : GETFIELD, memberDeclaringClassSlashedDescriptor,
member.getName(), CodeFlow.toJVMDescriptor(((Field) member).getType()));
}
else {
mv.visitMethodInsn(isStatic ? INVOKESTATIC : INVOKEVIRTUAL, memberDeclaringClassSlashedDescriptor,
member.getName(), CodeFlow.createSignatureDescriptor((Method) member),false);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@ -54,6 +54,8 @@ public class StandardEvaluationContext implements EvaluationContext {
private List<MethodResolver> methodResolvers;
private BeanResolver beanResolver;
private ReflectiveMethodResolver reflectiveMethodResolver;
private List<PropertyAccessor> propertyAccessors;
@ -68,15 +70,12 @@ public class StandardEvaluationContext implements EvaluationContext {
private final Map<String, Object> variables = new HashMap<String, Object>();
private BeanResolver beanResolver;
public StandardEvaluationContext() {
setRootObject(null);
}
public StandardEvaluationContext(Object rootObject) {
this();
setRootObject(rootObject);
}

View File

@ -25,18 +25,16 @@ import java.util.Map;
import java.util.StringTokenizer;
import org.junit.Test;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.AccessException;
import org.springframework.expression.CompilablePropertyAccessor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ast.CompoundExpression;
import org.springframework.expression.spel.ast.OpLT;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.expression.spel.ast.Ternary;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.standard.SpelCompiler;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@ -2174,63 +2172,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
vc = expression.getValue(payload);
assertNull(vc);
}
static class MyAccessor implements CompilablePropertyAccessor {
private Method method;
public Class<?>[] getSpecificTargetClasses() {
return new Class[]{Payload2.class};
}
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
// target is a Payload2 instance
return true;
}
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
Payload2 payload2 = (Payload2)target;
return new TypedValue(payload2.getField(name));
}
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
return false;
}
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public void generateCode(PropertyOrFieldReference propertyReference, MethodVisitor mv,CodeFlow codeflow) {
if (method == null) {
try {
method = Payload2.class.getDeclaredMethod("getField", String.class);
} catch (Exception e) {}
}
String descriptor = codeflow.lastDescriptor();
String memberDeclaringClassSlashedDescriptor = method.getDeclaringClass().getName().replace('.','/');
if (descriptor == null) {
codeflow.loadTarget(mv);
}
if (descriptor == null || !memberDeclaringClassSlashedDescriptor.equals(descriptor.substring(1))) {
mv.visitTypeInsn(CHECKCAST, memberDeclaringClassSlashedDescriptor);
}
mv.visitLdcInsn(propertyReference.getName());
mv.visitMethodInsn(INVOKEVIRTUAL, memberDeclaringClassSlashedDescriptor, method.getName(),CodeFlow.createSignatureDescriptor(method),false);
}
@Override
public Class<?> getPropertyType() {
return Object.class;
}
}
@Test
public void variantGetter() throws Exception {
Payload2Holder holder = new Payload2Holder();
@ -2257,9 +2199,67 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
// v = expression.getValue(ctx,holder);
// }
// System.out.println((System.currentTimeMillis()-stime));
}
static class MyAccessor implements CompilablePropertyAccessor {
private Method method;
public Class<?>[] getSpecificTargetClasses() {
return new Class[]{Payload2.class};
}
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
// target is a Payload2 instance
return true;
}
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
Payload2 payload2 = (Payload2)target;
return new TypedValue(payload2.getField(name));
}
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
return false;
}
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public Class<?> getPropertyType() {
return Object.class;
}
@Override
public void generateCode(String propertyName, MethodVisitor mv,CodeFlow codeflow) {
if (method == null) {
try {
method = Payload2.class.getDeclaredMethod("getField", String.class);
}
catch (Exception e) {
}
}
String descriptor = codeflow.lastDescriptor();
String memberDeclaringClassSlashedDescriptor = method.getDeclaringClass().getName().replace('.','/');
if (descriptor == null) {
codeflow.loadTarget(mv);
}
if (descriptor == null || !memberDeclaringClassSlashedDescriptor.equals(descriptor.substring(1))) {
mv.visitTypeInsn(CHECKCAST, memberDeclaringClassSlashedDescriptor);
}
mv.visitLdcInsn(propertyName);
mv.visitMethodInsn(INVOKEVIRTUAL, memberDeclaringClassSlashedDescriptor, method.getName(),CodeFlow.createSignatureDescriptor(method),false);
}
}
static class CompilableMapAccessor implements CompilablePropertyAccessor {
@Override
@ -2295,40 +2295,23 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
return new Class[] {Map.class};
}
/**
* Exception thrown from {@code read} in order to reset a cached
* PropertyAccessor, allowing other accessors to have a try.
*/
@SuppressWarnings("serial")
private static class MapAccessException extends AccessException {
private final String key;
public MapAccessException(String key) {
super(null);
this.key = key;
}
@Override
public String getMessage() {
return "Map does not contain a value for key '" + this.key + "'";
}
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public void generateCode(PropertyOrFieldReference propertyReference,
MethodVisitor mv, CodeFlow codeflow) {
public Class<?> getPropertyType() {
return Object.class;
}
@Override
public void generateCode(String propertyName, MethodVisitor mv, CodeFlow codeflow) {
String descriptor = codeflow.lastDescriptor();
if (descriptor == null) {
codeflow.loadTarget(mv);
}
mv.visitLdcInsn(propertyReference.getName());
mv.visitLdcInsn(propertyName);
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get","(Ljava/lang/Object;)Ljava/lang/Object;",true);
// if (method == null) {
@ -2348,12 +2331,27 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
// mv.visitMethodInsn(INVOKEVIRTUAL, memberDeclaringClassSlashedDescriptor, method.getName(),CodeFlow.createDescriptor(method));
// 6: invokeinterface #6, 2; //InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
}
}
@Override
public Class<?> getPropertyType() {
return Object.class;
/**
* Exception thrown from {@code read} in order to reset a cached
* PropertyAccessor, allowing other accessors to have a try.
*/
@SuppressWarnings("serial")
private static class MapAccessException extends AccessException {
private final String key;
public MapAccessException(String key) {
super(null);
this.key = key;
}
@Override
public String getMessage() {
return "Map does not contain a value for key '" + this.key + "'";
}
}