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