/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.support;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.aop.Advisor;
import org.springframework.aop.AopInvocationException;
import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.IntroductionAwareMethodMatcher;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.TargetClassAware;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.MethodIntrospector;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
Utility methods for AOP support code.
Mainly for internal use within Spring's AOP support.
See AopProxyUtils
for a collection of framework-specific AOP utility methods which depend on internals of Spring's AOP framework implementation.
Author: Rod Johnson, Juergen Hoeller, Rob Harrop See Also:
/**
* Utility methods for AOP support code.
*
* <p>Mainly for internal use within Spring's AOP support.
*
* <p>See {@link org.springframework.aop.framework.AopProxyUtils} for a
* collection of framework-specific AOP utility methods which depend
* on internals of Spring's AOP framework implementation.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @see org.springframework.aop.framework.AopProxyUtils
*/
public abstract class AopUtils {
Check whether the given object is a JDK dynamic proxy or a CGLIB proxy.
This method additionally checks if the given object is an instance of SpringProxy
.
Params: - object – the object to check
See Also:
/**
* Check whether the given object is a JDK dynamic proxy or a CGLIB proxy.
* <p>This method additionally checks if the given object is an instance
* of {@link SpringProxy}.
* @param object the object to check
* @see #isJdkDynamicProxy
* @see #isCglibProxy
*/
public static boolean isAopProxy(@Nullable Object object) {
return (object instanceof SpringProxy && (Proxy.isProxyClass(object.getClass()) ||
object.getClass().getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)));
}
Check whether the given object is a JDK dynamic proxy.
This method goes beyond the implementation of Proxy.isProxyClass(Class<?>)
by additionally checking if the given object is an instance of SpringProxy
.
Params: - object – the object to check
See Also:
/**
* Check whether the given object is a JDK dynamic proxy.
* <p>This method goes beyond the implementation of
* {@link Proxy#isProxyClass(Class)} by additionally checking if the
* given object is an instance of {@link SpringProxy}.
* @param object the object to check
* @see java.lang.reflect.Proxy#isProxyClass
*/
public static boolean isJdkDynamicProxy(@Nullable Object object) {
return (object instanceof SpringProxy && Proxy.isProxyClass(object.getClass()));
}
Check whether the given object is a CGLIB proxy.
This method goes beyond the implementation of ClassUtils.isCglibProxy(Object)
by additionally checking if the given object is an instance of SpringProxy
.
Params: - object – the object to check
See Also:
/**
* Check whether the given object is a CGLIB proxy.
* <p>This method goes beyond the implementation of
* {@link ClassUtils#isCglibProxy(Object)} by additionally checking if
* the given object is an instance of {@link SpringProxy}.
* @param object the object to check
* @see ClassUtils#isCglibProxy(Object)
*/
public static boolean isCglibProxy(@Nullable Object object) {
return (object instanceof SpringProxy &&
object.getClass().getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR));
}
Determine the target class of the given bean instance which might be an AOP proxy.
Returns the target class for an AOP proxy or the plain class otherwise.
Params: - candidate – the instance to check (might be an AOP proxy)
See Also: Returns: the target class (or the plain class of the given object as fallback; never null
)
/**
* Determine the target class of the given bean instance which might be an AOP proxy.
* <p>Returns the target class for an AOP proxy or the plain class otherwise.
* @param candidate the instance to check (might be an AOP proxy)
* @return the target class (or the plain class of the given object as fallback;
* never {@code null})
* @see org.springframework.aop.TargetClassAware#getTargetClass()
* @see org.springframework.aop.framework.AopProxyUtils#ultimateTargetClass(Object)
*/
public static Class<?> getTargetClass(Object candidate) {
Assert.notNull(candidate, "Candidate object must not be null");
Class<?> result = null;
if (candidate instanceof TargetClassAware) {
result = ((TargetClassAware) candidate).getTargetClass();
}
if (result == null) {
result = (isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());
}
return result;
}
Select an invocable method on the target type: either the given method itself
if actually exposed on the target type, or otherwise a corresponding method
on one of the target type's interfaces or on the target type itself.
Params: - method – the method to check
- targetType – the target type to search methods on (typically an AOP proxy)
Throws: - IllegalStateException – if the given method is not invocable on the given
target type (typically due to a proxy mismatch)
See Also: Returns: a corresponding invocable method on the target type Since: 4.3
/**
* Select an invocable method on the target type: either the given method itself
* if actually exposed on the target type, or otherwise a corresponding method
* on one of the target type's interfaces or on the target type itself.
* @param method the method to check
* @param targetType the target type to search methods on (typically an AOP proxy)
* @return a corresponding invocable method on the target type
* @throws IllegalStateException if the given method is not invocable on the given
* target type (typically due to a proxy mismatch)
* @since 4.3
* @see MethodIntrospector#selectInvocableMethod(Method, Class)
*/
public static Method selectInvocableMethod(Method method, @Nullable Class<?> targetType) {
if (targetType == null) {
return method;
}
Method methodToUse = MethodIntrospector.selectInvocableMethod(method, targetType);
if (Modifier.isPrivate(methodToUse.getModifiers()) && !Modifier.isStatic(methodToUse.getModifiers()) &&
SpringProxy.class.isAssignableFrom(targetType)) {
throw new IllegalStateException(String.format(
"Need to invoke method '%s' found on proxy for target class '%s' but cannot " +
"be delegated to target bean. Switch its visibility to package or protected.",
method.getName(), method.getDeclaringClass().getSimpleName()));
}
return methodToUse;
}
Determine whether the given method is an "equals" method.
See Also: - equals.equals
/**
* Determine whether the given method is an "equals" method.
* @see java.lang.Object#equals
*/
public static boolean isEqualsMethod(@Nullable Method method) {
return ReflectionUtils.isEqualsMethod(method);
}
Determine whether the given method is a "hashCode" method.
See Also: - hashCode.hashCode
/**
* Determine whether the given method is a "hashCode" method.
* @see java.lang.Object#hashCode
*/
public static boolean isHashCodeMethod(@Nullable Method method) {
return ReflectionUtils.isHashCodeMethod(method);
}
Determine whether the given method is a "toString" method.
See Also: - toString.toString()
/**
* Determine whether the given method is a "toString" method.
* @see java.lang.Object#toString()
*/
public static boolean isToStringMethod(@Nullable Method method) {
return ReflectionUtils.isToStringMethod(method);
}
Determine whether the given method is a "finalize" method.
See Also: - finalize.finalize()
/**
* Determine whether the given method is a "finalize" method.
* @see java.lang.Object#finalize()
*/
public static boolean isFinalizeMethod(@Nullable Method method) {
return (method != null && method.getName().equals("finalize") &&
method.getParameterCount() == 0);
}
Given a method, which may come from an interface, and a target class used in the current AOP invocation, find the corresponding target method if there is one. E.g. the method may be IFoo.bar()
and the target class may be DefaultFoo
. In this case, the method may be DefaultFoo.bar()
. This enables attributes on that method to be found. NOTE: In contrast to ClassUtils.getMostSpecificMethod
, this method resolves Java 5 bridge methods in order to retrieve attributes from the original method definition.
Params: - method – the method to be invoked, which may come from an interface
- targetClass – the target class for the current invocation. May be
null
or may not even implement the method.
See Also: Returns: the specific target method, or the original method if the targetClass
doesn't implement it or is null
/**
* Given a method, which may come from an interface, and a target class used
* in the current AOP invocation, find the corresponding target method if there
* is one. E.g. the method may be {@code IFoo.bar()} and the target class
* may be {@code DefaultFoo}. In this case, the method may be
* {@code DefaultFoo.bar()}. This enables attributes on that method to be found.
* <p><b>NOTE:</b> In contrast to {@link org.springframework.util.ClassUtils#getMostSpecificMethod},
* this method resolves Java 5 bridge methods in order to retrieve attributes
* from the <i>original</i> method definition.
* @param method the method to be invoked, which may come from an interface
* @param targetClass the target class for the current invocation.
* May be {@code null} or may not even implement the method.
* @return the specific target method, or the original method if the
* {@code targetClass} doesn't implement it or is {@code null}
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
*/
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
Class<?> specificTargetClass = (targetClass != null ? ClassUtils.getUserClass(targetClass) : null);
Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, specificTargetClass);
// If we are dealing with method with generic parameters, find the original method.
return BridgeMethodResolver.findBridgedMethod(resolvedMethod);
}
Can the given pointcut apply at all on the given class?
This is an important test as it can be used to optimize
out a pointcut for a class.
Params: - pc – the static or dynamic pointcut to check
- targetClass – the class to test
Returns: whether the pointcut can apply on any method
/**
* Can the given pointcut apply at all on the given class?
* <p>This is an important test as it can be used to optimize
* out a pointcut for a class.
* @param pc the static or dynamic pointcut to check
* @param targetClass the class to test
* @return whether the pointcut can apply on any method
*/
public static boolean canApply(Pointcut pc, Class<?> targetClass) {
return canApply(pc, targetClass, false);
}
Can the given pointcut apply at all on the given class?
This is an important test as it can be used to optimize
out a pointcut for a class.
Params: - pc – the static or dynamic pointcut to check
- targetClass – the class to test
- hasIntroductions – whether or not the advisor chain
for this bean includes any introductions
Returns: whether the pointcut can apply on any method
/**
* Can the given pointcut apply at all on the given class?
* <p>This is an important test as it can be used to optimize
* out a pointcut for a class.
* @param pc the static or dynamic pointcut to check
* @param targetClass the class to test
* @param hasIntroductions whether or not the advisor chain
* for this bean includes any introductions
* @return whether the pointcut can apply on any method
*/
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
Set<Class<?>> classes = new LinkedHashSet<>();
if (!Proxy.isProxyClass(targetClass)) {
classes.add(ClassUtils.getUserClass(targetClass));
}
classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
for (Class<?> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if (introductionAwareMethodMatcher != null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
Can the given advisor apply at all on the given class?
This is an important test as it can be used to optimize
out a advisor for a class.
Params: - advisor – the advisor to check
- targetClass – class we're testing
Returns: whether the pointcut can apply on any method
/**
* Can the given advisor apply at all on the given class?
* This is an important test as it can be used to optimize
* out a advisor for a class.
* @param advisor the advisor to check
* @param targetClass class we're testing
* @return whether the pointcut can apply on any method
*/
public static boolean canApply(Advisor advisor, Class<?> targetClass) {
return canApply(advisor, targetClass, false);
}
Can the given advisor apply at all on the given class?
This is an important test as it can be used to optimize out a advisor for a class.
This version also takes into account introductions (for IntroductionAwareMethodMatchers).
Params: - advisor – the advisor to check
- targetClass – class we're testing
- hasIntroductions – whether or not the advisor chain for this bean includes
any introductions
Returns: whether the pointcut can apply on any method
/**
* Can the given advisor apply at all on the given class?
* <p>This is an important test as it can be used to optimize out a advisor for a class.
* This version also takes into account introductions (for IntroductionAwareMethodMatchers).
* @param advisor the advisor to check
* @param targetClass class we're testing
* @param hasIntroductions whether or not the advisor chain for this bean includes
* any introductions
* @return whether the pointcut can apply on any method
*/
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}
Determine the sublist of the candidateAdvisors
list that is applicable to the given class. Params: - candidateAdvisors – the Advisors to evaluate
- clazz – the target class
Returns: sublist of Advisors that can apply to an object of the given class
(may be the incoming List as-is)
/**
* Determine the sublist of the {@code candidateAdvisors} list
* that is applicable to the given class.
* @param candidateAdvisors the Advisors to evaluate
* @param clazz the target class
* @return sublist of Advisors that can apply to an object of the given class
* (may be the incoming List as-is)
*/
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new ArrayList<>();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
}
Invoke the given target via reflection, as part of an AOP method invocation.
Params: - target – the target object
- method – the method to invoke
- args – the arguments for the method
Throws: - Throwable – if thrown by the target method
- AopInvocationException – in case of a reflection error
Returns: the invocation result, if any
/**
* Invoke the given target via reflection, as part of an AOP method invocation.
* @param target the target object
* @param method the method to invoke
* @param args the arguments for the method
* @return the invocation result, if any
* @throws Throwable if thrown by the target method
* @throws org.springframework.aop.AopInvocationException in case of a reflection error
*/
@Nullable
public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
throws Throwable {
// Use reflection to invoke the method.
try {
ReflectionUtils.makeAccessible(method);
return method.invoke(target, args);
}
catch (InvocationTargetException ex) {
// Invoked method threw a checked exception.
// We must rethrow it. The client won't see the interceptor.
throw ex.getTargetException();
}
catch (IllegalArgumentException ex) {
throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
method + "] on target [" + target + "]", ex);
}
catch (IllegalAccessException ex) {
throw new AopInvocationException("Could not access method [" + method + "]", ex);
}
}
}