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.Collections; 024import java.util.HashMap; 025import java.util.LinkedHashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029 030import org.springframework.core.BridgeMethodResolver; 031import org.springframework.core.MethodParameter; 032import org.springframework.core.convert.TypeDescriptor; 033import org.springframework.expression.AccessException; 034import org.springframework.expression.EvaluationContext; 035import org.springframework.expression.EvaluationException; 036import org.springframework.expression.MethodExecutor; 037import org.springframework.expression.MethodFilter; 038import org.springframework.expression.MethodResolver; 039import org.springframework.expression.TypeConverter; 040import org.springframework.expression.spel.SpelEvaluationException; 041import org.springframework.expression.spel.SpelMessage; 042import org.springframework.lang.Nullable; 043 044/** 045 * Reflection-based {@link MethodResolver} used by default in {@link StandardEvaluationContext} 046 * unless explicit method resolvers have been specified. 047 * 048 * @author Andy Clement 049 * @author Juergen Hoeller 050 * @author Chris Beams 051 * @since 3.0 052 * @see StandardEvaluationContext#addMethodResolver(MethodResolver) 053 */ 054public class ReflectiveMethodResolver implements MethodResolver { 055 056 // Using distance will ensure a more accurate match is discovered, 057 // more closely following the Java rules. 058 private final boolean useDistance; 059 060 @Nullable 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, @Nullable MethodFilter filter) { 090 if (this.filters == null) { 091 this.filters = new HashMap<>(); 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 @Nullable 112 public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name, 113 List<TypeDescriptor> argumentTypes) throws AccessException { 114 115 try { 116 TypeConverter typeConverter = context.getTypeConverter(); 117 Class<?> type = (targetObject instanceof Class ? (Class<?>) targetObject : targetObject.getClass()); 118 ArrayList<Method> methods = new ArrayList<>(getMethods(type, targetObject)); 119 120 // If a filter is registered for this type, call it 121 MethodFilter filter = (this.filters != null ? this.filters.get(type) : null); 122 if (filter != null) { 123 List<Method> filtered = filter.filter(methods); 124 methods = (filtered instanceof ArrayList ? (ArrayList<Method>) filtered : new ArrayList<>(filtered)); 125 } 126 127 // Sort methods into a sensible order 128 if (methods.size() > 1) { 129 methods.sort((m1, m2) -> { 130 int m1pl = m1.getParameterCount(); 131 int m2pl = m2.getParameterCount(); 132 // vararg methods go last 133 if (m1pl == m2pl) { 134 if (!m1.isVarArgs() && m2.isVarArgs()) { 135 return -1; 136 } 137 else if (m1.isVarArgs() && !m2.isVarArgs()) { 138 return 1; 139 } 140 else { 141 return 0; 142 } 143 } 144 return Integer.compare(m1pl, m2pl); 145 }); 146 } 147 148 // Resolve any bridge methods 149 for (int i = 0; i < methods.size(); i++) { 150 methods.set(i, BridgeMethodResolver.findBridgedMethod(methods.get(i))); 151 } 152 153 // Remove duplicate methods (possible due to resolved bridge methods) 154 Set<Method> methodsToIterate = new LinkedHashSet<>(methods); 155 156 Method closeMatch = null; 157 int closeMatchDistance = Integer.MAX_VALUE; 158 Method matchRequiringConversion = null; 159 boolean multipleOptions = false; 160 161 for (Method method : methodsToIterate) { 162 if (method.getName().equals(name)) { 163 int paramCount = method.getParameterCount(); 164 List<TypeDescriptor> paramDescriptors = new ArrayList<>(paramCount); 165 for (int i = 0; i < paramCount; i++) { 166 paramDescriptors.add(new TypeDescriptor(new MethodParameter(method, i))); 167 } 168 ReflectionHelper.ArgumentsMatchInfo matchInfo = null; 169 if (method.isVarArgs() && argumentTypes.size() >= (paramCount - 1)) { 170 // *sigh* complicated 171 matchInfo = ReflectionHelper.compareArgumentsVarargs(paramDescriptors, argumentTypes, typeConverter); 172 } 173 else if (paramCount == argumentTypes.size()) { 174 // Name and parameter number match, check the arguments 175 matchInfo = ReflectionHelper.compareArguments(paramDescriptors, argumentTypes, typeConverter); 176 } 177 if (matchInfo != null) { 178 if (matchInfo.isExactMatch()) { 179 return new ReflectiveMethodExecutor(method); 180 } 181 else if (matchInfo.isCloseMatch()) { 182 if (this.useDistance) { 183 int matchDistance = ReflectionHelper.getTypeDifferenceWeight(paramDescriptors, argumentTypes); 184 if (closeMatch == null || matchDistance < closeMatchDistance) { 185 // This is a better match... 186 closeMatch = method; 187 closeMatchDistance = matchDistance; 188 } 189 } 190 else { 191 // Take this as a close match if there isn't one already 192 if (closeMatch == null) { 193 closeMatch = method; 194 } 195 } 196 } 197 else if (matchInfo.isMatchRequiringConversion()) { 198 if (matchRequiringConversion != null) { 199 multipleOptions = true; 200 } 201 matchRequiringConversion = method; 202 } 203 } 204 } 205 } 206 if (closeMatch != null) { 207 return new ReflectiveMethodExecutor(closeMatch); 208 } 209 else if (matchRequiringConversion != null) { 210 if (multipleOptions) { 211 throw new SpelEvaluationException(SpelMessage.MULTIPLE_POSSIBLE_METHODS, name); 212 } 213 return new ReflectiveMethodExecutor(matchRequiringConversion); 214 } 215 else { 216 return null; 217 } 218 } 219 catch (EvaluationException ex) { 220 throw new AccessException("Failed to resolve method", ex); 221 } 222 } 223 224 private Set<Method> getMethods(Class<?> type, Object targetObject) { 225 if (targetObject instanceof Class) { 226 Set<Method> result = new LinkedHashSet<>(); 227 // Add these so that static methods are invocable on the type: e.g. Float.valueOf(..) 228 Method[] methods = getMethods(type); 229 for (Method method : methods) { 230 if (Modifier.isStatic(method.getModifiers())) { 231 result.add(method); 232 } 233 } 234 // Also expose methods from java.lang.Class itself 235 Collections.addAll(result, getMethods(Class.class)); 236 return result; 237 } 238 else if (Proxy.isProxyClass(type)) { 239 Set<Method> result = new LinkedHashSet<>(); 240 // Expose interface methods (not proxy-declared overrides) for proper vararg introspection 241 for (Class<?> ifc : type.getInterfaces()) { 242 Method[] methods = getMethods(ifc); 243 for (Method method : methods) { 244 if (isCandidateForInvocation(method, type)) { 245 result.add(method); 246 } 247 } 248 } 249 return result; 250 } 251 else { 252 Set<Method> result = new LinkedHashSet<>(); 253 Method[] methods = getMethods(type); 254 for (Method method : methods) { 255 if (isCandidateForInvocation(method, type)) { 256 result.add(method); 257 } 258 } 259 return result; 260 } 261 } 262 263 /** 264 * Return the set of methods for this type. The default implementation returns the 265 * result of {@link Class#getMethods()} for the given {@code type}, but subclasses 266 * may override in order to alter the results, e.g. specifying static methods 267 * declared elsewhere. 268 * @param type the class for which to return the methods 269 * @since 3.1.1 270 */ 271 protected Method[] getMethods(Class<?> type) { 272 return type.getMethods(); 273 } 274 275 /** 276 * Determine whether the given {@code Method} is a candidate for method resolution 277 * on an instance of the given target class. 278 * <p>The default implementation considers any method as a candidate, even for 279 * static methods sand non-user-declared methods on the {@link Object} base class. 280 * @param method the Method to evaluate 281 * @param targetClass the concrete target class that is being introspected 282 * @since 4.3.15 283 */ 284 protected boolean isCandidateForInvocation(Method method, Class<?> targetClass) { 285 return true; 286 } 287 288}