001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.expression.spel.support; 018 019import java.lang.reflect.Method; 020import java.lang.reflect.Modifier; 021import java.lang.reflect.Proxy; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.Comparator; 026import java.util.HashMap; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031 032import org.springframework.core.BridgeMethodResolver; 033import org.springframework.core.MethodParameter; 034import org.springframework.core.convert.TypeDescriptor; 035import org.springframework.expression.AccessException; 036import org.springframework.expression.EvaluationContext; 037import org.springframework.expression.EvaluationException; 038import org.springframework.expression.MethodExecutor; 039import org.springframework.expression.MethodFilter; 040import org.springframework.expression.MethodResolver; 041import org.springframework.expression.TypeConverter; 042import org.springframework.expression.spel.SpelEvaluationException; 043import org.springframework.expression.spel.SpelMessage; 044 045/** 046 * Reflection-based {@link MethodResolver} used by default in {@link StandardEvaluationContext} 047 * unless explicit method resolvers have been specified. 048 * 049 * @author Andy Clement 050 * @author Juergen Hoeller 051 * @author Chris Beams 052 * @since 3.0 053 * @see StandardEvaluationContext#addMethodResolver(MethodResolver) 054 */ 055public class ReflectiveMethodResolver implements MethodResolver { 056 057 // Using distance will ensure a more accurate match is discovered, 058 // more closely following the Java rules. 059 private final boolean useDistance; 060 061 private Map<Class<?>, MethodFilter> filters; 062 063 064 public ReflectiveMethodResolver() { 065 this.useDistance = true; 066 } 067 068 /** 069 * This constructor allows the ReflectiveMethodResolver to be configured such that it 070 * will use a distance computation to check which is the better of two close matches 071 * (when there are multiple matches). Using the distance computation is intended to 072 * ensure matches are more closely representative of what a Java compiler would do 073 * when taking into account boxing/unboxing and whether the method candidates are 074 * declared to handle a supertype of the type (of the argument) being passed in. 075 * @param useDistance {@code true} if distance computation should be used when 076 * calculating matches; {@code false} otherwise 077 */ 078 public ReflectiveMethodResolver(boolean useDistance) { 079 this.useDistance = useDistance; 080 } 081 082 083 /** 084 * Register a filter for methods on the given type. 085 * @param type the type to filter on 086 * @param filter the corresponding method filter, 087 * or {@code null} to clear any filter for the given type 088 */ 089 public void registerMethodFilter(Class<?> type, MethodFilter filter) { 090 if (this.filters == null) { 091 this.filters = new HashMap<Class<?>, MethodFilter>(); 092 } 093 if (filter != null) { 094 this.filters.put(type, filter); 095 } 096 else { 097 this.filters.remove(type); 098 } 099 } 100 101 /** 102 * Locate a method on a type. There are three kinds of match that might occur: 103 * <ol> 104 * <li>an exact match where the types of the arguments match the types of the constructor 105 * <li>an in-exact match where the types we are looking for are subtypes of those defined on the constructor 106 * <li>a match where we are able to convert the arguments into those expected by the constructor, 107 * according to the registered type converter 108 * </ol> 109 */ 110 @Override 111 public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name, 112 List<TypeDescriptor> argumentTypes) throws AccessException { 113 114 try { 115 TypeConverter typeConverter = context.getTypeConverter(); 116 Class<?> type = (targetObject instanceof Class ? (Class<?>) targetObject : targetObject.getClass()); 117 List<Method> methods = new ArrayList<Method>(getMethods(type, targetObject)); 118 119 // If a filter is registered for this type, call it 120 MethodFilter filter = (this.filters != null ? this.filters.get(type) : null); 121 if (filter != null) { 122 List<Method> filtered = filter.filter(methods); 123 methods = (filtered instanceof ArrayList ? filtered : new ArrayList<Method>(filtered)); 124 } 125 126 // Sort methods into a sensible order 127 if (methods.size() > 1) { 128 Collections.sort(methods, new Comparator<Method>() { 129 @Override 130 public int compare(Method m1, Method m2) { 131 int m1pl = m1.getParameterTypes().length; 132 int m2pl = m2.getParameterTypes().length; 133 // varargs methods go last 134 if (m1pl == m2pl) { 135 if (!m1.isVarArgs() && m2.isVarArgs()) { 136 return -1; 137 } 138 else if (m1.isVarArgs() && !m2.isVarArgs()) { 139 return 1; 140 } 141 else { 142 return 0; 143 } 144 } 145 return (m1pl < m2pl ? -1 : (m1pl > m2pl ? 1 : 0)); 146 } 147 }); 148 } 149 150 // Resolve any bridge methods 151 for (int i = 0; i < methods.size(); i++) { 152 methods.set(i, BridgeMethodResolver.findBridgedMethod(methods.get(i))); 153 } 154 155 // Remove duplicate methods (possible due to resolved bridge methods) 156 Set<Method> methodsToIterate = new LinkedHashSet<Method>(methods); 157 158 Method closeMatch = null; 159 int closeMatchDistance = Integer.MAX_VALUE; 160 Method matchRequiringConversion = null; 161 boolean multipleOptions = false; 162 163 for (Method method : methodsToIterate) { 164 if (method.getName().equals(name)) { 165 Class<?>[] paramTypes = method.getParameterTypes(); 166 List<TypeDescriptor> paramDescriptors = new ArrayList<TypeDescriptor>(paramTypes.length); 167 for (int i = 0; i < paramTypes.length; i++) { 168 paramDescriptors.add(new TypeDescriptor(new MethodParameter(method, i))); 169 } 170 ReflectionHelper.ArgumentsMatchInfo matchInfo = null; 171 if (method.isVarArgs() && argumentTypes.size() >= (paramTypes.length - 1)) { 172 // *sigh* complicated 173 matchInfo = ReflectionHelper.compareArgumentsVarargs(paramDescriptors, argumentTypes, typeConverter); 174 } 175 else if (paramTypes.length == argumentTypes.size()) { 176 // Name and parameter number match, check the arguments 177 matchInfo = ReflectionHelper.compareArguments(paramDescriptors, argumentTypes, typeConverter); 178 } 179 if (matchInfo != null) { 180 if (matchInfo.isExactMatch()) { 181 return new ReflectiveMethodExecutor(method); 182 } 183 else if (matchInfo.isCloseMatch()) { 184 if (this.useDistance) { 185 int matchDistance = ReflectionHelper.getTypeDifferenceWeight(paramDescriptors, argumentTypes); 186 if (closeMatch == null || matchDistance < closeMatchDistance) { 187 // This is a better match... 188 closeMatch = method; 189 closeMatchDistance = matchDistance; 190 } 191 } 192 else { 193 // Take this as a close match if there isn't one already 194 if (closeMatch == null) { 195 closeMatch = method; 196 } 197 } 198 } 199 else if (matchInfo.isMatchRequiringConversion()) { 200 if (matchRequiringConversion != null) { 201 multipleOptions = true; 202 } 203 matchRequiringConversion = method; 204 } 205 } 206 } 207 } 208 if (closeMatch != null) { 209 return new ReflectiveMethodExecutor(closeMatch); 210 } 211 else if (matchRequiringConversion != null) { 212 if (multipleOptions) { 213 throw new SpelEvaluationException(SpelMessage.MULTIPLE_POSSIBLE_METHODS, name); 214 } 215 return new ReflectiveMethodExecutor(matchRequiringConversion); 216 } 217 else { 218 return null; 219 } 220 } 221 catch (EvaluationException ex) { 222 throw new AccessException("Failed to resolve method", ex); 223 } 224 } 225 226 private Set<Method> getMethods(Class<?> type, Object targetObject) { 227 if (targetObject instanceof Class) { 228 Set<Method> result = new LinkedHashSet<Method>(); 229 // Add these so that static methods are invocable on the type: e.g. Float.valueOf(..) 230 Method[] methods = getMethods(type); 231 for (Method method : methods) { 232 if (Modifier.isStatic(method.getModifiers())) { 233 result.add(method); 234 } 235 } 236 // Also expose methods from java.lang.Class itself 237 result.addAll(Arrays.asList(getMethods(Class.class))); 238 return result; 239 } 240 else if (Proxy.isProxyClass(type)) { 241 Set<Method> result = new LinkedHashSet<Method>(); 242 // Expose interface methods (not proxy-declared overrides) for proper vararg introspection 243 for (Class<?> ifc : type.getInterfaces()) { 244 Method[] methods = getMethods(ifc); 245 for (Method method : methods) { 246 if (isCandidateForInvocation(method, type)) { 247 result.add(method); 248 } 249 } 250 } 251 return result; 252 } 253 else { 254 Set<Method> result = new LinkedHashSet<Method>(); 255 Method[] methods = getMethods(type); 256 for (Method method : methods) { 257 if (isCandidateForInvocation(method, type)) { 258 result.add(method); 259 } 260 } 261 return result; 262 } 263 } 264 265 /** 266 * Return the set of methods for this type. The default implementation returns the 267 * result of {@link Class#getMethods()} for the given {@code type}, but subclasses 268 * may override in order to alter the results, e.g. specifying static methods 269 * declared elsewhere. 270 * @param type the class for which to return the methods 271 * @since 3.1.1 272 */ 273 protected Method[] getMethods(Class<?> type) { 274 return type.getMethods(); 275 } 276 277 /** 278 * Determine whether the given {@code Method} is a candidate for method resolution 279 * on an instance of the given target class. 280 * <p>The default implementation considers any method as a candidate, even for 281 * static methods sand non-user-declared methods on the {@link Object} base class. 282 * @param method the Method to evaluate 283 * @param targetClass the concrete target class that is being introspected 284 * @since 4.3.15 285 */ 286 protected boolean isCandidateForInvocation(Method method, Class<?> targetClass) { 287 return true; 288 } 289 290}