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.core; 018 019import java.lang.reflect.Method; 020import java.lang.reflect.Proxy; 021import java.util.LinkedHashMap; 022import java.util.LinkedHashSet; 023import java.util.Map; 024import java.util.Set; 025 026import org.springframework.lang.Nullable; 027import org.springframework.util.ClassUtils; 028import org.springframework.util.ReflectionUtils; 029 030/** 031 * Defines the algorithm for searching for metadata-associated methods exhaustively 032 * including interfaces and parent classes while also dealing with parameterized methods 033 * as well as common scenarios encountered with interface and class-based proxies. 034 * 035 * <p>Typically, but not necessarily, used for finding annotated handler methods. 036 * 037 * @author Juergen Hoeller 038 * @author Rossen Stoyanchev 039 * @since 4.2.3 040 */ 041public final class MethodIntrospector { 042 043 private MethodIntrospector() { 044 } 045 046 047 /** 048 * Select methods on the given target type based on the lookup of associated metadata. 049 * <p>Callers define methods of interest through the {@link MetadataLookup} parameter, 050 * allowing to collect the associated metadata into the result map. 051 * @param targetType the target type to search methods on 052 * @param metadataLookup a {@link MetadataLookup} callback to inspect methods of interest, 053 * returning non-null metadata to be associated with a given method if there is a match, 054 * or {@code null} for no match 055 * @return the selected methods associated with their metadata (in the order of retrieval), 056 * or an empty map in case of no match 057 */ 058 public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) { 059 final Map<Method, T> methodMap = new LinkedHashMap<>(); 060 Set<Class<?>> handlerTypes = new LinkedHashSet<>(); 061 Class<?> specificHandlerType = null; 062 063 if (!Proxy.isProxyClass(targetType)) { 064 specificHandlerType = ClassUtils.getUserClass(targetType); 065 handlerTypes.add(specificHandlerType); 066 } 067 handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType)); 068 069 for (Class<?> currentHandlerType : handlerTypes) { 070 final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType); 071 072 ReflectionUtils.doWithMethods(currentHandlerType, method -> { 073 Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); 074 T result = metadataLookup.inspect(specificMethod); 075 if (result != null) { 076 Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); 077 if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) { 078 methodMap.put(specificMethod, result); 079 } 080 } 081 }, ReflectionUtils.USER_DECLARED_METHODS); 082 } 083 084 return methodMap; 085 } 086 087 /** 088 * Select methods on the given target type based on a filter. 089 * <p>Callers define methods of interest through the {@code MethodFilter} parameter. 090 * @param targetType the target type to search methods on 091 * @param methodFilter a {@code MethodFilter} to help 092 * recognize handler methods of interest 093 * @return the selected methods, or an empty set in case of no match 094 */ 095 public static Set<Method> selectMethods(Class<?> targetType, final ReflectionUtils.MethodFilter methodFilter) { 096 return selectMethods(targetType, 097 (MetadataLookup<Boolean>) method -> (methodFilter.matches(method) ? Boolean.TRUE : null)).keySet(); 098 } 099 100 /** 101 * Select an invocable method on the target type: either the given method itself 102 * if actually exposed on the target type, or otherwise a corresponding method 103 * on one of the target type's interfaces or on the target type itself. 104 * <p>Matches on user-declared interfaces will be preferred since they are likely 105 * to contain relevant metadata that corresponds to the method on the target class. 106 * @param method the method to check 107 * @param targetType the target type to search methods on 108 * (typically an interface-based JDK proxy) 109 * @return a corresponding invocable method on the target type 110 * @throws IllegalStateException if the given method is not invocable on the given 111 * target type (typically due to a proxy mismatch) 112 */ 113 public static Method selectInvocableMethod(Method method, Class<?> targetType) { 114 if (method.getDeclaringClass().isAssignableFrom(targetType)) { 115 return method; 116 } 117 try { 118 String methodName = method.getName(); 119 Class<?>[] parameterTypes = method.getParameterTypes(); 120 for (Class<?> ifc : targetType.getInterfaces()) { 121 try { 122 return ifc.getMethod(methodName, parameterTypes); 123 } 124 catch (NoSuchMethodException ex) { 125 // Alright, not on this interface then... 126 } 127 } 128 // A final desperate attempt on the proxy class itself... 129 return targetType.getMethod(methodName, parameterTypes); 130 } 131 catch (NoSuchMethodException ex) { 132 throw new IllegalStateException(String.format( 133 "Need to invoke method '%s' declared on target class '%s', " + 134 "but not found in any interface(s) of the exposed proxy type. " + 135 "Either pull the method up to an interface or switch to CGLIB " + 136 "proxies by enforcing proxy-target-class mode in your configuration.", 137 method.getName(), method.getDeclaringClass().getSimpleName())); 138 } 139 } 140 141 142 /** 143 * A callback interface for metadata lookup on a given method. 144 * @param <T> the type of metadata returned 145 */ 146 @FunctionalInterface 147 public interface MetadataLookup<T> { 148 149 /** 150 * Perform a lookup on the given method and return associated metadata, if any. 151 * @param method the method to inspect 152 * @return non-null metadata to be associated with a method if there is a match, 153 * or {@code null} for no match 154 */ 155 @Nullable 156 T inspect(Method method); 157 } 158 159}