SPR-6764: filtering strategy for SpEL method invocation
This commit is contained in:
parent
4343714c6d
commit
66f708392e
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2002-2009 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.expression;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MethodFilter instances allow SpEL users to fine tune the behaviour of the method resolution
|
||||
* process. Method resolution (which translates from a method name in an expression to a real
|
||||
* method to invoke) will normally retrieve candidate methods for invocation via a simple call
|
||||
* to 'Class.getMethods()' and will choose the first one that is suitable for the
|
||||
* input parameters. By registering a MethodFilter the user can receive a callback
|
||||
* and change the methods that will be considered suitable.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 3.0.1
|
||||
*/
|
||||
public interface MethodFilter {
|
||||
|
||||
/**
|
||||
* Called by the method resolver to allow the SpEL user to organize the list of candidate
|
||||
* methods that may be invoked. The filter can remove methods that should not be
|
||||
* considered candidates and it may sort the results. The resolver will then search
|
||||
* through the methods as returned from the filter when looking for a suitable
|
||||
* candidate to invoke.
|
||||
*
|
||||
* @param methods the full list of methods the resolver was going to choose from
|
||||
* @return a possible subset of input methods that may be sorted by order of relevance
|
||||
*/
|
||||
List<Method> filter(List<Method> methods);
|
||||
|
||||
}
|
||||
|
|
@ -17,16 +17,23 @@
|
|||
package org.springframework.expression.spel.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.MethodExecutor;
|
||||
import org.springframework.expression.MethodFilter;
|
||||
import org.springframework.expression.MethodResolver;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.expression.spel.SpelMessage;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A method resolver that uses reflection to locate the method that should be invoked
|
||||
*
|
||||
|
|
@ -35,6 +42,11 @@ import org.springframework.expression.spel.SpelMessage;
|
|||
*/
|
||||
public class ReflectiveMethodResolver implements MethodResolver {
|
||||
|
||||
private static Method[] NO_METHODS = new Method[0];
|
||||
|
||||
private Map<Class<?>,MethodFilter> filters = null;
|
||||
|
||||
|
||||
/**
|
||||
* Locate a method on a type. There are three kinds of match that might occur:
|
||||
* <ol>
|
||||
|
|
@ -49,6 +61,23 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
|||
TypeConverter typeConverter = context.getTypeConverter();
|
||||
Class<?> type = (targetObject instanceof Class ? (Class<?>) targetObject : targetObject.getClass());
|
||||
Method[] methods = type.getMethods();
|
||||
|
||||
// If a filter is registered for this type, call it
|
||||
MethodFilter methodfilter = (filters==null?null:filters.get(type));
|
||||
if (methodfilter!=null) {
|
||||
List<Method> methodsForFiltering = new ArrayList<Method>();
|
||||
for (Method method: methods) {
|
||||
methodsForFiltering.add(method);
|
||||
}
|
||||
List<Method> methodsFiltered = methodfilter.filter(methodsForFiltering);
|
||||
if (methodsFiltered == null || methodsFiltered.size()==0) {
|
||||
methods = NO_METHODS;
|
||||
}
|
||||
else {
|
||||
methods = methodsFiltered.toArray(new Method[methodsFiltered.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
Method closeMatch = null;
|
||||
int[] argsToConvert = null;
|
||||
boolean multipleOptions = false;
|
||||
|
|
@ -101,4 +130,16 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
|||
}
|
||||
}
|
||||
|
||||
public void registerMethodFilter(Class<?> type, MethodFilter filter) {
|
||||
if (filters==null) {
|
||||
filters = new HashMap<Class<?>,MethodFilter>();
|
||||
}
|
||||
if (filter==null) {
|
||||
filters.remove(type);
|
||||
}
|
||||
else {
|
||||
filters.put(type,filter);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import java.util.Map;
|
|||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.ConstructorResolver;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.MethodFilter;
|
||||
import org.springframework.expression.MethodResolver;
|
||||
import org.springframework.expression.OperatorOverloader;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
|
|
@ -50,6 +51,8 @@ public class StandardEvaluationContext implements EvaluationContext {
|
|||
private List<ConstructorResolver> constructorResolvers;
|
||||
|
||||
private List<MethodResolver> methodResolvers;
|
||||
|
||||
private ReflectiveMethodResolver reflectiveMethodResolver;
|
||||
|
||||
private List<PropertyAccessor> propertyAccessors;
|
||||
|
||||
|
|
@ -120,7 +123,7 @@ public class StandardEvaluationContext implements EvaluationContext {
|
|||
private void ensureMethodResolversInitialized() {
|
||||
if (this.methodResolvers == null) {
|
||||
this.methodResolvers = new ArrayList<MethodResolver>();
|
||||
this.methodResolvers.add(new ReflectiveMethodResolver());
|
||||
this.methodResolvers.add(reflectiveMethodResolver=new ReflectiveMethodResolver());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -199,4 +202,17 @@ public class StandardEvaluationContext implements EvaluationContext {
|
|||
return this.variables.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a MethodFilter which will be called during method resolution for the
|
||||
* specified type. The MethodFilter may remove methods and/or sort the methods
|
||||
* which will then be used by SpEL as the candidates to look through for a match.
|
||||
*
|
||||
* @param type the type for which the filter should be called
|
||||
* @param filter a MethodFilter, or NULL to deregister a filter for the type
|
||||
*/
|
||||
public void registerMethodFilter(Class<?> type, MethodFilter filter) {
|
||||
ensureMethodResolversInitialized();
|
||||
reflectiveMethodResolver.registerMethodFilter(type,filter);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,18 @@
|
|||
|
||||
package org.springframework.expression.spel;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.MethodFilter;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.expression.spel.testresources.PlaceOfBirth;
|
||||
|
|
@ -141,7 +150,99 @@ public class MethodInvocationTests extends ExpressionTestCase {
|
|||
}
|
||||
// If counter is 5 then the method got called twice!
|
||||
Assert.assertEquals(4,parser.parseExpression("counter").getValue(eContext));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodFiltering_SPR6764() {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setRootObject(new TestObject());
|
||||
LocalFilter filter = new LocalFilter();
|
||||
context.registerMethodFilter(TestObject.class,filter);
|
||||
|
||||
// Filter will be called but not do anything, so first doit() will be invoked
|
||||
SpelExpression expr = (SpelExpression) parser.parseExpression("doit(1)");
|
||||
String result = expr.getValue(context,String.class);
|
||||
Assert.assertEquals("1",result);
|
||||
Assert.assertTrue(filter.filterCalled);
|
||||
|
||||
// Filter will now remove non @Anno annotated methods
|
||||
filter.removeIfNotAnnotated = true;
|
||||
filter.filterCalled = false;
|
||||
expr = (SpelExpression) parser.parseExpression("doit(1)");
|
||||
result = expr.getValue(context,String.class);
|
||||
Assert.assertEquals("double 1.0",result);
|
||||
Assert.assertTrue(filter.filterCalled);
|
||||
|
||||
// check not called for other types
|
||||
filter.filterCalled=false;
|
||||
context.setRootObject(new String("abc"));
|
||||
expr = (SpelExpression) parser.parseExpression("charAt(0)");
|
||||
result = expr.getValue(context,String.class);
|
||||
Assert.assertEquals("a",result);
|
||||
Assert.assertFalse(filter.filterCalled);
|
||||
|
||||
// check de-registration works
|
||||
filter.filterCalled = false;
|
||||
context.registerMethodFilter(TestObject.class,null);//clear filter
|
||||
context.setRootObject(new TestObject());
|
||||
expr = (SpelExpression) parser.parseExpression("doit(1)");
|
||||
result = expr.getValue(context,String.class);
|
||||
Assert.assertEquals("1",result);
|
||||
Assert.assertFalse(filter.filterCalled);
|
||||
}
|
||||
|
||||
// Simple filter
|
||||
static class LocalFilter implements MethodFilter {
|
||||
|
||||
public boolean removeIfNotAnnotated = false;
|
||||
|
||||
public boolean filterCalled = false;
|
||||
|
||||
private boolean isAnnotated(Method m) {
|
||||
Annotation[] annos = m.getAnnotations();
|
||||
if (annos==null) {
|
||||
return false;
|
||||
}
|
||||
for (Annotation anno: annos) {
|
||||
String s = anno.annotationType().getName();
|
||||
if (s.endsWith("Anno")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<Method> filter(List<Method> methods) {
|
||||
filterCalled = true;
|
||||
List<Method> forRemoval = new ArrayList<Method>();
|
||||
for (Method m: methods) {
|
||||
if (removeIfNotAnnotated && !isAnnotated(m)) {
|
||||
forRemoval.add(m);
|
||||
}
|
||||
}
|
||||
for (Method m: forRemoval) {
|
||||
methods.remove(m);
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Anno {}
|
||||
|
||||
class TestObject {
|
||||
public int doit(int i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
@Anno
|
||||
public String doit(double d) {
|
||||
return "double "+d;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVarargsInvocation01() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue