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}