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