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}