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.aop.support; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.lang.reflect.Modifier; 022import java.lang.reflect.Proxy; 023import java.util.LinkedHashSet; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Set; 027 028import org.springframework.aop.Advisor; 029import org.springframework.aop.AopInvocationException; 030import org.springframework.aop.IntroductionAdvisor; 031import org.springframework.aop.IntroductionAwareMethodMatcher; 032import org.springframework.aop.MethodMatcher; 033import org.springframework.aop.Pointcut; 034import org.springframework.aop.PointcutAdvisor; 035import org.springframework.aop.SpringProxy; 036import org.springframework.aop.TargetClassAware; 037import org.springframework.core.BridgeMethodResolver; 038import org.springframework.core.MethodIntrospector; 039import org.springframework.util.Assert; 040import org.springframework.util.ClassUtils; 041import org.springframework.util.ReflectionUtils; 042 043/** 044 * Utility methods for AOP support code. 045 * 046 * <p>Mainly for internal use within Spring's AOP support. 047 * 048 * <p>See {@link org.springframework.aop.framework.AopProxyUtils} for a 049 * collection of framework-specific AOP utility methods which depend 050 * on internals of Spring's AOP framework implementation. 051 * 052 * @author Rod Johnson 053 * @author Juergen Hoeller 054 * @author Rob Harrop 055 * @see org.springframework.aop.framework.AopProxyUtils 056 */ 057public abstract class AopUtils { 058 059 /** 060 * Check whether the given object is a JDK dynamic proxy or a CGLIB proxy. 061 * <p>This method additionally checks if the given object is an instance 062 * of {@link SpringProxy}. 063 * @param object the object to check 064 * @see #isJdkDynamicProxy 065 * @see #isCglibProxy 066 */ 067 public static boolean isAopProxy(Object object) { 068 return (object instanceof SpringProxy && 069 (Proxy.isProxyClass(object.getClass()) || ClassUtils.isCglibProxyClass(object.getClass()))); 070 } 071 072 /** 073 * Check whether the given object is a JDK dynamic proxy. 074 * <p>This method goes beyond the implementation of 075 * {@link Proxy#isProxyClass(Class)} by additionally checking if the 076 * given object is an instance of {@link SpringProxy}. 077 * @param object the object to check 078 * @see java.lang.reflect.Proxy#isProxyClass 079 */ 080 public static boolean isJdkDynamicProxy(Object object) { 081 return (object instanceof SpringProxy && Proxy.isProxyClass(object.getClass())); 082 } 083 084 /** 085 * Check whether the given object is a CGLIB proxy. 086 * <p>This method goes beyond the implementation of 087 * {@link ClassUtils#isCglibProxy(Object)} by additionally checking if 088 * the given object is an instance of {@link SpringProxy}. 089 * @param object the object to check 090 * @see ClassUtils#isCglibProxy(Object) 091 */ 092 public static boolean isCglibProxy(Object object) { 093 return (object instanceof SpringProxy && ClassUtils.isCglibProxy(object)); 094 } 095 096 /** 097 * Determine the target class of the given bean instance which might be an AOP proxy. 098 * <p>Returns the target class for an AOP proxy or the plain class otherwise. 099 * @param candidate the instance to check (might be an AOP proxy) 100 * @return the target class (or the plain class of the given object as fallback; 101 * never {@code null}) 102 * @see org.springframework.aop.TargetClassAware#getTargetClass() 103 * @see org.springframework.aop.framework.AopProxyUtils#ultimateTargetClass(Object) 104 */ 105 public static Class<?> getTargetClass(Object candidate) { 106 Assert.notNull(candidate, "Candidate object must not be null"); 107 Class<?> result = null; 108 if (candidate instanceof TargetClassAware) { 109 result = ((TargetClassAware) candidate).getTargetClass(); 110 } 111 if (result == null) { 112 result = (isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass()); 113 } 114 return result; 115 } 116 117 /** 118 * Select an invocable method on the target type: either the given method itself 119 * if actually exposed on the target type, or otherwise a corresponding method 120 * on one of the target type's interfaces or on the target type itself. 121 * @param method the method to check 122 * @param targetType the target type to search methods on (typically an AOP proxy) 123 * @return a corresponding invocable method on the target type 124 * @throws IllegalStateException if the given method is not invocable on the given 125 * target type (typically due to a proxy mismatch) 126 * @since 4.3 127 * @see MethodIntrospector#selectInvocableMethod(Method, Class) 128 */ 129 public static Method selectInvocableMethod(Method method, Class<?> targetType) { 130 Method methodToUse = MethodIntrospector.selectInvocableMethod(method, targetType); 131 if (Modifier.isPrivate(methodToUse.getModifiers()) && !Modifier.isStatic(methodToUse.getModifiers()) && 132 SpringProxy.class.isAssignableFrom(targetType)) { 133 throw new IllegalStateException(String.format( 134 "Need to invoke method '%s' found on proxy for target class '%s' but cannot " + 135 "be delegated to target bean. Switch its visibility to package or protected.", 136 method.getName(), method.getDeclaringClass().getSimpleName())); 137 } 138 return methodToUse; 139 } 140 141 /** 142 * Determine whether the given method is an "equals" method. 143 * @see java.lang.Object#equals 144 */ 145 public static boolean isEqualsMethod(Method method) { 146 return ReflectionUtils.isEqualsMethod(method); 147 } 148 149 /** 150 * Determine whether the given method is a "hashCode" method. 151 * @see java.lang.Object#hashCode 152 */ 153 public static boolean isHashCodeMethod(Method method) { 154 return ReflectionUtils.isHashCodeMethod(method); 155 } 156 157 /** 158 * Determine whether the given method is a "toString" method. 159 * @see java.lang.Object#toString() 160 */ 161 public static boolean isToStringMethod(Method method) { 162 return ReflectionUtils.isToStringMethod(method); 163 } 164 165 /** 166 * Determine whether the given method is a "finalize" method. 167 * @see java.lang.Object#finalize() 168 */ 169 public static boolean isFinalizeMethod(Method method) { 170 return (method != null && method.getName().equals("finalize") && 171 method.getParameterTypes().length == 0); 172 } 173 174 /** 175 * Given a method, which may come from an interface, and a target class used 176 * in the current AOP invocation, find the corresponding target method if there 177 * is one. E.g. the method may be {@code IFoo.bar()} and the target class 178 * may be {@code DefaultFoo}. In this case, the method may be 179 * {@code DefaultFoo.bar()}. This enables attributes on that method to be found. 180 * <p><b>NOTE:</b> In contrast to {@link org.springframework.util.ClassUtils#getMostSpecificMethod}, 181 * this method resolves Java 5 bridge methods in order to retrieve attributes 182 * from the <i>original</i> method definition. 183 * @param method the method to be invoked, which may come from an interface 184 * @param targetClass the target class for the current invocation. 185 * May be {@code null} or may not even implement the method. 186 * @return the specific target method, or the original method if the 187 * {@code targetClass} doesn't implement it or is {@code null} 188 * @see org.springframework.util.ClassUtils#getMostSpecificMethod 189 */ 190 public static Method getMostSpecificMethod(Method method, Class<?> targetClass) { 191 Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, targetClass); 192 // If we are dealing with method with generic parameters, find the original method. 193 return BridgeMethodResolver.findBridgedMethod(resolvedMethod); 194 } 195 196 /** 197 * Can the given pointcut apply at all on the given class? 198 * <p>This is an important test as it can be used to optimize 199 * out a pointcut for a class. 200 * @param pc the static or dynamic pointcut to check 201 * @param targetClass the class to test 202 * @return whether the pointcut can apply on any method 203 */ 204 public static boolean canApply(Pointcut pc, Class<?> targetClass) { 205 return canApply(pc, targetClass, false); 206 } 207 208 /** 209 * Can the given pointcut apply at all on the given class? 210 * <p>This is an important test as it can be used to optimize 211 * out a pointcut for a class. 212 * @param pc the static or dynamic pointcut to check 213 * @param targetClass the class to test 214 * @param hasIntroductions whether or not the advisor chain 215 * for this bean includes any introductions 216 * @return whether the pointcut can apply on any method 217 */ 218 public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) { 219 Assert.notNull(pc, "Pointcut must not be null"); 220 if (!pc.getClassFilter().matches(targetClass)) { 221 return false; 222 } 223 224 MethodMatcher methodMatcher = pc.getMethodMatcher(); 225 if (methodMatcher == MethodMatcher.TRUE) { 226 // No need to iterate the methods if we're matching any method anyway... 227 return true; 228 } 229 230 IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; 231 if (methodMatcher instanceof IntroductionAwareMethodMatcher) { 232 introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; 233 } 234 235 Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); 236 classes.add(targetClass); 237 for (Class<?> clazz : classes) { 238 Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz); 239 for (Method method : methods) { 240 if ((introductionAwareMethodMatcher != null && 241 introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) || 242 methodMatcher.matches(method, targetClass)) { 243 return true; 244 } 245 } 246 } 247 248 return false; 249 } 250 251 /** 252 * Can the given advisor apply at all on the given class? 253 * This is an important test as it can be used to optimize 254 * out a advisor for a class. 255 * @param advisor the advisor to check 256 * @param targetClass class we're testing 257 * @return whether the pointcut can apply on any method 258 */ 259 public static boolean canApply(Advisor advisor, Class<?> targetClass) { 260 return canApply(advisor, targetClass, false); 261 } 262 263 /** 264 * Can the given advisor apply at all on the given class? 265 * <p>This is an important test as it can be used to optimize out a advisor for a class. 266 * This version also takes into account introductions (for IntroductionAwareMethodMatchers). 267 * @param advisor the advisor to check 268 * @param targetClass class we're testing 269 * @param hasIntroductions whether or not the advisor chain for this bean includes 270 * any introductions 271 * @return whether the pointcut can apply on any method 272 */ 273 public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) { 274 if (advisor instanceof IntroductionAdvisor) { 275 return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass); 276 } 277 else if (advisor instanceof PointcutAdvisor) { 278 PointcutAdvisor pca = (PointcutAdvisor) advisor; 279 return canApply(pca.getPointcut(), targetClass, hasIntroductions); 280 } 281 else { 282 // It doesn't have a pointcut so we assume it applies. 283 return true; 284 } 285 } 286 287 /** 288 * Determine the sublist of the {@code candidateAdvisors} list 289 * that is applicable to the given class. 290 * @param candidateAdvisors the Advisors to evaluate 291 * @param clazz the target class 292 * @return sublist of Advisors that can apply to an object of the given class 293 * (may be the incoming List as-is) 294 */ 295 public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) { 296 if (candidateAdvisors.isEmpty()) { 297 return candidateAdvisors; 298 } 299 List<Advisor> eligibleAdvisors = new LinkedList<Advisor>(); 300 for (Advisor candidate : candidateAdvisors) { 301 if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) { 302 eligibleAdvisors.add(candidate); 303 } 304 } 305 boolean hasIntroductions = !eligibleAdvisors.isEmpty(); 306 for (Advisor candidate : candidateAdvisors) { 307 if (candidate instanceof IntroductionAdvisor) { 308 // already processed 309 continue; 310 } 311 if (canApply(candidate, clazz, hasIntroductions)) { 312 eligibleAdvisors.add(candidate); 313 } 314 } 315 return eligibleAdvisors; 316 } 317 318 /** 319 * Invoke the given target via reflection, as part of an AOP method invocation. 320 * @param target the target object 321 * @param method the method to invoke 322 * @param args the arguments for the method 323 * @return the invocation result, if any 324 * @throws Throwable if thrown by the target method 325 * @throws org.springframework.aop.AopInvocationException in case of a reflection error 326 */ 327 public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args) 328 throws Throwable { 329 330 // Use reflection to invoke the method. 331 try { 332 ReflectionUtils.makeAccessible(method); 333 return method.invoke(target, args); 334 } 335 catch (InvocationTargetException ex) { 336 // Invoked method threw a checked exception. 337 // We must rethrow it. The client won't see the interceptor. 338 throw ex.getTargetException(); 339 } 340 catch (IllegalArgumentException ex) { 341 throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" + 342 method + "] on target [" + target + "]", ex); 343 } 344 catch (IllegalAccessException ex) { 345 throw new AopInvocationException("Could not access method [" + method + "]", ex); 346 } 347 } 348 349}