com.mycila.plugin.Cglib2AopProxy.java Source code

Java tutorial

Introduction

Here is the source code for com.mycila.plugin.Cglib2AopProxy.java

Source

/**
 * Copyright (C) 2010 Mathieu Carbou <mathieu.carbou@gmail.com>
 *
 * 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
 *
 *         http://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 com.mycila.plugin;

import com.mycila.plugin.spi.aop.AopUtils;
import net.sf.cglib.core.CodeGenerationException;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Dispatcher;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import net.sf.cglib.transform.impl.UndeclaredThrowableStrategy;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.Advisor;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.RawTargetAccess;
import org.springframework.aop.TargetSource;
import org.springframework.core.SmartClassLoader;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * CGLIB2-based {@link AopProxy} implementation for the Spring AOP framework.
 * <p/>
 * <p><i>Requires CGLIB 2.1+ on the classpath.</i>.
 * As of Spring 2.0, earlier CGLIB versions are not supported anymore.
 * <p/>
 * <p>Objects of this type should be obtained through proxy factories,
 * configured by an {@link AdvisedSupport} object. This class is internal
 * to Spring's AOP framework and need not be used directly by client code.
 * <p/>
 * <p>{@link DefaultAopProxyFactory} will automatically create CGLIB2-based
 * proxies if necessary, for example in case of proxying a target class
 * (see the {@link DefaultAopProxyFactory attendant javadoc} for details).
 * <p/>
 * <p>Proxies created using this class are thread-safe if the underlying
 * (target) class is thread-safe.
 *
 * @author Rod Johnson
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @author Ramnivas Laddad
 * @see net.sf.cglib.proxy.Enhancer
 * @see AdvisedSupport#setProxyTargetClass
 * @see DefaultAopProxyFactory
 */
final class Cglib2AopProxy implements AopProxy, Serializable {

    // Constants for CGLIB callback array indices
    private static final int AOP_PROXY = 0;
    private static final int INVOKE_TARGET = 1;
    private static final int NO_OVERRIDE = 2;
    private static final int DISPATCH_TARGET = 3;
    private static final int DISPATCH_ADVISED = 4;
    private static final int INVOKE_EQUALS = 5;
    private static final int INVOKE_HASHCODE = 6;

    /**
     * Logger available to subclasses; static to optimize serialization
     */
    protected final static Log logger = LogFactory.getLog(Cglib2AopProxy.class);

    /**
     * Keeps track of the Classes that we have validated for final methods
     */
    private static final Map<Class, Boolean> validatedClasses = new WeakHashMap<Class, Boolean>();

    /**
     * The configuration used to configure this proxy
     */
    protected final AdvisedSupport advised;

    private Object[] constructorArgs;

    private Class[] constructorArgTypes;

    /**
     * Dispatcher used for methods on Advised
     */
    private final transient AdvisedDispatcher advisedDispatcher;

    private transient Map<String, Integer> fixedInterceptorMap;

    private transient int fixedInterceptorOffset;

    /**
     * Create a new Cglib2AopProxy for the given AOP configuration.
     *
     * @param config the AOP configuration as AdvisedSupport object
     * @throws AopConfigException if the config is invalid. We try to throw an informative
     *                            exception in this case, rather than let a mysterious failure happen later.
     */
    public Cglib2AopProxy(AdvisedSupport config) throws AopConfigException {
        Assert.notNull(config, "AdvisedSupport must not be null");
        if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
            throw new AopConfigException("No advisors and no TargetSource specified");
        }
        this.advised = config;
        this.advisedDispatcher = new AdvisedDispatcher(this.advised);
    }

    /**
     * Set constructor arguments to use for creating the proxy.
     *
     * @param constructorArgs     the constructor argument values
     * @param constructorArgTypes the constructor argument types
     */
    public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {
        if (constructorArgs == null || constructorArgTypes == null) {
            throw new IllegalArgumentException(
                    "Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
        }
        if (constructorArgs.length != constructorArgTypes.length) {
            throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length
                    + ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
        }
        this.constructorArgs = constructorArgs;
        this.constructorArgTypes = constructorArgTypes;
    }

    public Object getProxy() {
        return getProxy(null);
    }

    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating CGLIB2 proxy: target source is " + this.advised.getTargetSource());
        }

        try {
            Class rootClass = this.advised.getTargetClass();
            Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

            Class proxySuperClass = rootClass;
            if (AopUtils.isCglibProxyClass(rootClass)) {
                proxySuperClass = rootClass.getSuperclass();
                Class[] additionalInterfaces = rootClass.getInterfaces();
                for (Class additionalInterface : additionalInterfaces) {
                    this.advised.addInterface(additionalInterface);
                }
            }

            // Validate the class, writing log messages as necessary.
            validateClassIfNecessary(proxySuperClass);

            // Configure CGLIB Enhancer...
            Enhancer enhancer = createEnhancer();
            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);
                if (classLoader instanceof SmartClassLoader
                        && ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                    enhancer.setUseCache(false);
                }
            }
            enhancer.setSuperclass(proxySuperClass);
            enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class));
            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
            enhancer.setInterceptDuringConstruction(false);

            Callback[] callbacks = getCallbacks(rootClass);
            enhancer.setCallbacks(callbacks);
            enhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(),
                    this.fixedInterceptorMap, this.fixedInterceptorOffset));

            Class[] types = new Class[callbacks.length];
            for (int x = 0; x < types.length; x++) {
                types[x] = callbacks[x].getClass();
            }
            enhancer.setCallbackTypes(types);

            // Generate the proxy class and create a proxy instance.
            Object proxy;
            if (this.constructorArgs != null) {
                proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
            } else {
                proxy = enhancer.create();
            }

            return proxy;
        } catch (CodeGenerationException ex) {
            throw new AopConfigException(
                    "Could not generate CGLIB subclass of class [" + this.advised.getTargetClass() + "]: "
                            + "Common causes of this problem include using a final class or a non-visible class",
                    ex);
        } catch (IllegalArgumentException ex) {
            throw new AopConfigException(
                    "Could not generate CGLIB subclass of class [" + this.advised.getTargetClass() + "]: "
                            + "Common causes of this problem include using a final class or a non-visible class",
                    ex);
        } catch (Exception ex) {
            // TargetSource.getTarget() failed
            throw new AopConfigException("Unexpected AOP exception", ex);
        }
    }

    /**
     * Creates the CGLIB {@link net.sf.cglib.proxy.Enhancer}. Subclasses may wish to override this to return a custom
     * {@link net.sf.cglib.proxy.Enhancer} implementation.
     */
    protected Enhancer createEnhancer() {
        return new Enhancer();
    }

    /**
     * Checks to see whether the supplied <code>Class</code> has already been validated and
     * validates it if not.
     */
    private void validateClassIfNecessary(Class proxySuperClass) {
        if (logger.isWarnEnabled()) {
            synchronized (validatedClasses) {
                if (!validatedClasses.containsKey(proxySuperClass)) {
                    doValidateClass(proxySuperClass);
                    validatedClasses.put(proxySuperClass, Boolean.TRUE);
                }
            }
        }
    }

    /**
     * Checks for final methods on the <code>Class</code> and writes warnings to the log
     * for each one found.
     */
    private void doValidateClass(Class proxySuperClass) {
        Method[] methods = proxySuperClass.getMethods();
        for (Method method : methods) {
            if (!Object.class.equals(method.getDeclaringClass()) && Modifier.isFinal(method.getModifiers())) {
                logger.warn("Unable to proxy method [" + method + "] because it is final: "
                        + "All calls to this method via a proxy will be routed directly to the proxy.");
            }
        }
    }

    private Callback[] getCallbacks(Class rootClass) throws Exception {
        // Parameters used for optimisation choices...
        boolean exposeProxy = this.advised.isExposeProxy();
        boolean isFrozen = this.advised.isFrozen();
        boolean isStatic = this.advised.getTargetSource().isStatic();

        // Choose an "aop" interceptor (used for AOP calls).
        Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

        // Choose a "straight to target" interceptor. (used for calls that are
        // unadvised but can return this). May be required to expose the proxy.
        Callback targetInterceptor;
        if (exposeProxy) {
            targetInterceptor = isStatic
                    ? new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget())
                    : new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource());
        } else {
            targetInterceptor = isStatic
                    ? new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget())
                    : new DynamicUnadvisedInterceptor(this.advised.getTargetSource());
        }

        // Choose a "direct to target" dispatcher (used for
        // unadvised calls to static targets that cannot return this).
        Callback targetDispatcher = isStatic ? new StaticDispatcher(this.advised.getTargetSource().getTarget())
                : new SerializableNoOp();

        Callback[] mainCallbacks = new Callback[] { aopInterceptor, // for normal advice
                targetInterceptor, // invoke target without considering advice, if optimized
                new SerializableNoOp(), // no override for methods mapped to this
                targetDispatcher, this.advisedDispatcher, new EqualsInterceptor(this.advised),
                new HashCodeInterceptor(this.advised) };

        Callback[] callbacks;

        // If the target is a static one and the advice chain is frozen,
        // then we can make some optimisations by sending the AOP calls
        // direct to the target using the fixed chain for that method.
        if (isStatic && isFrozen) {
            Method[] methods = rootClass.getMethods();
            Callback[] fixedCallbacks = new Callback[methods.length];
            this.fixedInterceptorMap = new HashMap<String, Integer>(methods.length);

            // small memory optimisation here (can skip creation for methods with no advice)
            for (int x = 0; x < methods.length; x++) {
                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x],
                        rootClass);
                fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(chain,
                        this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
                this.fixedInterceptorMap.put(methods[x].toString(), x);
            }

            // Now copy both the callbacks from mainCallbacks
            // and fixedCallbacks into the callbacks array.
            callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
            System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
            System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
            this.fixedInterceptorOffset = mainCallbacks.length;
        } else {
            callbacks = mainCallbacks;
        }
        return callbacks;
    }

    /**
     * Wrap a return of this if necessary to be the proxy
     */
    private static Object massageReturnTypeIfNecessary(Object proxy, Object target, Method method, Object retVal) {
        // Massage return value if necessary
        if (retVal != null && retVal == target
                && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            // Special case: it returned "this".
            // Note that we can't help if the target sets a reference
            // to itself in another returned object.
            retVal = proxy;
        }
        return retVal;
    }

    @Override
    public boolean equals(Object other) {
        return (this == other || (other instanceof Cglib2AopProxy
                && AopProxyUtils.equalsInProxy(this.advised, ((Cglib2AopProxy) other).advised)));
    }

    @Override
    public int hashCode() {
        return Cglib2AopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
    }

    /**
     * Serializable replacement for CGLIB's NoOp interface.
     * Public to allow use elsewhere in the framework.
     */
    public static class SerializableNoOp implements NoOp, Serializable {
    }

    /**
     * Method interceptor used for static targets with no advice chain. The call
     * is passed directly back to the target. Used when the proxy needs to be
     * exposed and it can't be determined that the method won't return
     * <code>this</code>.
     */
    private static class StaticUnadvisedInterceptor implements MethodInterceptor, Serializable {

        private final Object target;

        public StaticUnadvisedInterceptor(Object target) {
            this.target = target;
        }

        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
                throws Throwable {
            Object retVal = methodProxy.invoke(this.target, args);
            return massageReturnTypeIfNecessary(proxy, this.target, method, retVal);
        }
    }

    /**
     * Method interceptor used for static targets with no advice chain, when the
     * proxy is to be exposed.
     */
    private static class StaticUnadvisedExposedInterceptor implements MethodInterceptor, Serializable {

        private final Object target;

        public StaticUnadvisedExposedInterceptor(Object target) {
            this.target = target;
        }

        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
                throws Throwable {
            Object oldProxy = null;
            try {
                oldProxy = AopContext.setCurrentProxy(proxy);
                Object retVal = methodProxy.invoke(this.target, args);
                return massageReturnTypeIfNecessary(proxy, this.target, method, retVal);
            } finally {
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }

    /**
     * Interceptor used to invoke a dynamic target without creating a method
     * invocation or evaluating an advice chain. (We know there was no advice
     * for this method.)
     */
    private static class DynamicUnadvisedInterceptor implements MethodInterceptor, Serializable {

        private final TargetSource targetSource;

        public DynamicUnadvisedInterceptor(TargetSource targetSource) {
            this.targetSource = targetSource;
        }

        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
                throws Throwable {
            Object target = this.targetSource.getTarget();
            try {
                Object retVal = methodProxy.invoke(target, args);
                return massageReturnTypeIfNecessary(proxy, target, method, retVal);
            } finally {
                this.targetSource.releaseTarget(target);
            }
        }
    }

    /**
     * Interceptor for unadvised dynamic targets when the proxy needs exposing.
     */
    private static class DynamicUnadvisedExposedInterceptor implements MethodInterceptor, Serializable {

        private final TargetSource targetSource;

        public DynamicUnadvisedExposedInterceptor(TargetSource targetSource) {
            this.targetSource = targetSource;
        }

        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
                throws Throwable {
            Object oldProxy = null;
            Object target = this.targetSource.getTarget();
            try {
                oldProxy = AopContext.setCurrentProxy(proxy);
                Object retVal = methodProxy.invoke(target, args);
                return massageReturnTypeIfNecessary(proxy, target, method, retVal);
            } finally {
                AopContext.setCurrentProxy(oldProxy);
                this.targetSource.releaseTarget(target);
            }
        }
    }

    /**
     * Dispatcher for a static target. Dispatcher is much faster than
     * interceptor. This will be used whenever it can be determined that a
     * method definitely does not return "this"
     */
    private static class StaticDispatcher implements Dispatcher, Serializable {

        private Object target;

        public StaticDispatcher(Object target) {
            this.target = target;
        }

        public Object loadObject() {
            return this.target;
        }
    }

    /**
     * Dispatcher for any methods declared on the Advised class.
     */
    private static class AdvisedDispatcher implements Dispatcher, Serializable {

        private final AdvisedSupport advised;

        public AdvisedDispatcher(AdvisedSupport advised) {
            this.advised = advised;
        }

        public Object loadObject() throws Exception {
            return this.advised;
        }
    }

    /**
     * Dispatcher for the <code>equals</code> method.
     * Ensures that the method call is always handled by this class.
     */
    private static class EqualsInterceptor implements MethodInterceptor, Serializable {

        private final AdvisedSupport advised;

        public EqualsInterceptor(AdvisedSupport advised) {
            this.advised = advised;
        }

        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {
            Object other = args[0];
            if (proxy == other) {
                return true;
            }
            if (other instanceof Factory) {
                Callback callback = ((Factory) other).getCallback(INVOKE_EQUALS);
                if (!(callback instanceof EqualsInterceptor)) {
                    return false;
                }
                AdvisedSupport otherAdvised = ((EqualsInterceptor) callback).advised;
                return AopProxyUtils.equalsInProxy(this.advised, otherAdvised);
            } else {
                return false;
            }
        }
    }

    /**
     * Dispatcher for the <code>hashCode</code> method.
     * Ensures that the method call is always handled by this class.
     */
    private static class HashCodeInterceptor implements MethodInterceptor, Serializable {

        private final AdvisedSupport advised;

        public HashCodeInterceptor(AdvisedSupport advised) {
            this.advised = advised;
        }

        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {
            return Cglib2AopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
        }
    }

    /**
     * Interceptor used specifically for advised methods on a frozen, static proxy.
     */
    private static class FixedChainStaticTargetInterceptor implements MethodInterceptor, Serializable {

        private final List<Object> adviceChain;

        private final Object target;

        private final Class targetClass;

        public FixedChainStaticTargetInterceptor(List<Object> adviceChain, Object target, Class targetClass) {
            this.adviceChain = adviceChain;
            this.target = target;
            this.targetClass = targetClass;
        }

        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
                throws Throwable {
            MethodInvocation invocation = new CglibMethodInvocation(proxy, this.target, method, args,
                    this.targetClass, this.adviceChain, methodProxy);
            // If we get here, we need to create a MethodInvocation.
            Object retVal = invocation.proceed();
            retVal = massageReturnTypeIfNecessary(proxy, this.target, method, retVal);
            return retVal;
        }
    }

    /**
     * General purpose AOP callback. Used when the target is dynamic or when the
     * proxy is not frozen.
     */
    private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {

        private AdvisedSupport advised;

        public DynamicAdvisedInterceptor(AdvisedSupport advised) {
            this.advised = advised;
        }

        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
                throws Throwable {
            Object oldProxy = null;
            boolean setProxyContext = false;
            Class targetClass = null;
            Object target = null;
            try {
                if (this.advised.exposeProxy) {
                    // Make invocation available if necessary.
                    oldProxy = AopContext.setCurrentProxy(proxy);
                    setProxyContext = true;
                }
                // May be <code>null</code>. Get as late as possible to minimize the time we
                // "own" the target, in case it comes from a pool.
                target = getTarget();
                if (target != null) {
                    targetClass = target.getClass();
                }
                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
                Object retVal;
                // Check whether we only have one InvokerInterceptor: that is,
                // no real advice, but just reflective invocation of the target.
                if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
                    // We can skip creating a MethodInvocation: just invoke the target directly.
                    // Note that the final invoker must be an InvokerInterceptor, so we know
                    // it does nothing but a reflective operation on the target, and no hot
                    // swapping or fancy proxying.
                    retVal = methodProxy.invoke(target, args);
                } else {
                    // We need to create a method invocation...
                    retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)
                            .proceed();
                }
                retVal = massageReturnTypeIfNecessary(proxy, target, method, retVal);
                return retVal;
            } finally {
                if (target != null) {
                    releaseTarget(target);
                }
                if (setProxyContext) {
                    // Restore old proxy.
                    AopContext.setCurrentProxy(oldProxy);
                }
            }
        }

        @Override
        public boolean equals(Object other) {
            return (this == other || (other instanceof DynamicAdvisedInterceptor
                    && this.advised.equals(((DynamicAdvisedInterceptor) other).advised)));
        }

        /**
         * CGLIB uses this to drive proxy creation.
         */
        @Override
        public int hashCode() {
            return this.advised.hashCode();
        }

        protected Object getTarget() throws Exception {
            return this.advised.getTargetSource().getTarget();
        }

        protected void releaseTarget(Object target) throws Exception {
            this.advised.getTargetSource().releaseTarget(target);
        }
    }

    /**
     * Implementation of AOP Alliance MethodInvocation used by this AOP proxy.
     */
    private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

        private final MethodProxy methodProxy;

        private boolean protectedMethod;

        public CglibMethodInvocation(Object proxy, Object target, Method method, Object[] arguments,
                Class targetClass, List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {
            super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
            this.methodProxy = methodProxy;
            this.protectedMethod = Modifier.isProtected(method.getModifiers());
        }

        /**
         * Gives a marginal performance improvement versus using reflection to
         * invoke the target when invoking public methods.
         */
        @Override
        protected Object invokeJoinpoint() throws Throwable {
            if (this.protectedMethod) {
                return super.invokeJoinpoint();
            } else {
                return this.methodProxy.invoke(this.target, this.arguments);
            }
        }
    }

    /**
     * CallbackFilter to assign Callbacks to methods.
     */
    private static class ProxyCallbackFilter implements CallbackFilter {

        private final AdvisedSupport advised;

        private final Map<String, Integer> fixedInterceptorMap;

        private final int fixedInterceptorOffset;

        public ProxyCallbackFilter(AdvisedSupport advised, Map<String, Integer> fixedInterceptorMap,
                int fixedInterceptorOffset) {
            this.advised = advised;
            this.fixedInterceptorMap = fixedInterceptorMap;
            this.fixedInterceptorOffset = fixedInterceptorOffset;
        }

        /**
         * Implementation of CallbackFilter.accept() to return the index of the
         * callback we need.
         * <p>The callbacks for each proxy are built up of a set of fixed callbacks
         * for general use and then a set of callbacks that are specific to a method
         * for use on static targets with a fixed advice chain.
         * <p>The callback used is determined thus:
         * <dl>
         * <dt>For exposed proxies</dt>
         * <dd>Exposing the proxy requires code to execute before and after the
         * method/chain invocation. This means we must use
         * DynamicAdvisedInterceptor, since all other interceptors can avoid the
         * need for a try/catch block</dd>
         * <dt>For Object.finalize():</dt>
         * <dd>No override for this method is used.</dd>
         * <dt>For equals():</dt>
         * <dd>The EqualsInterceptor is used to redirect equals() calls to a
         * special handler to this proxy.</dd>
         * <dt>For methods on the Advised class:</dt>
         * <dd>the AdvisedDispatcher is used to dispatch the call directly to
         * the target</dd>
         * <dt>For advised methods:</dt>
         * <dd>If the target is static and the advice chain is frozen then a
         * FixedChainStaticTargetInterceptor specific to the method is used to
         * invoke the advice chain. Otherwise a DyanmicAdvisedInterceptor is
         * used.</dd>
         * <dt>For non-advised methods:</dt>
         * <dd>Where it can be determined that the method will not return <code>this</code>
         * or when <code>ProxyFactory.getExposeProxy()</code> returns <code>false</code>,
         * then a Dispatcher is used. For static targets, the StaticDispatcher is used;
         * and for dynamic targets, a DynamicUnadvisedInterceptor is used.
         * If it possible for the method to return <code>this</code> then a
         * StaticUnadvisedInterceptor is used for static targets - the
         * DynamicUnadvisedInterceptor already considers this.</dd>
         * </dl>
         */
        public int accept(Method method) {
            if (AopUtils.isFinalizeMethod(method)) {
                logger.debug("Found finalize() method - using NO_OVERRIDE");
                return NO_OVERRIDE;
            }
            if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface()
                    && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Method is declared on Advised interface: " + method);
                }
                return DISPATCH_ADVISED;
            }
            // We must always proxy equals, to direct calls to this.
            if (AopUtils.isEqualsMethod(method)) {
                logger.debug("Found 'equals' method: " + method);
                return INVOKE_EQUALS;
            }
            // We must always calculate hashCode based on the proxy.
            if (AopUtils.isHashCodeMethod(method)) {
                logger.debug("Found 'hashCode' method: " + method);
                return INVOKE_HASHCODE;
            }
            Class targetClass = this.advised.getTargetClass();
            // Proxy is not yet available, but that shouldn't matter.
            List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            boolean haveAdvice = !chain.isEmpty();
            boolean exposeProxy = this.advised.isExposeProxy();
            boolean isStatic = this.advised.getTargetSource().isStatic();
            boolean isFrozen = this.advised.isFrozen();
            if (haveAdvice || !isFrozen) {
                // If exposing the proxy, then AOP_PROXY must be used.
                if (exposeProxy) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Must expose proxy on advised method: " + method);
                    }
                    return AOP_PROXY;
                }
                String key = method.toString();
                // Check to see if we have fixed interceptor to serve this method.
                // Else use the AOP_PROXY.
                if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(key)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Method has advice and optimisations are enabled: " + method);
                    }
                    // We know that we are optimising so we can use the
                    // FixedStaticChainInterceptors.
                    int index = this.fixedInterceptorMap.get(key);
                    return (index + this.fixedInterceptorOffset);
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Unable to apply any optimisations to advised method: " + method);
                    }
                    return AOP_PROXY;
                }
            } else {
                // See if the return type of the method is outside the class hierarchy
                // of the target type. If so we know it never needs to have return type
                // massage and can use a dispatcher.
                // If the proxy is being exposed, then must use the interceptor the
                // correct one is already configured. If the target is not static cannot
                // use a Dispatcher because the target can not then be released.
                if (exposeProxy || !isStatic) {
                    return INVOKE_TARGET;
                }
                Class returnType = method.getReturnType();
                if (targetClass == returnType) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Method " + method
                                + "has return type same as target type (may return this) - using INVOKE_TARGET");
                    }
                    return INVOKE_TARGET;
                } else if (returnType.isPrimitive() || !returnType.isAssignableFrom(targetClass)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Method " + method
                                + " has return type that ensures this cannot be returned- using DISPATCH_TARGET");
                    }
                    return DISPATCH_TARGET;
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Method " + method
                                + "has return type that is assignable from the target type (may return this) - "
                                + "using INVOKE_TARGET");
                    }
                    return INVOKE_TARGET;
                }
            }
        }

        @Override
        public boolean equals(Object other) {
            if (other == this) {
                return true;
            }
            if (!(other instanceof ProxyCallbackFilter)) {
                return false;
            }
            ProxyCallbackFilter otherCallbackFilter = (ProxyCallbackFilter) other;
            AdvisedSupport otherAdvised = otherCallbackFilter.advised;
            if (this.advised == null || otherAdvised == null) {
                return false;
            }
            if (this.advised.isFrozen() != otherAdvised.isFrozen()) {
                return false;
            }
            if (this.advised.isExposeProxy() != otherAdvised.isExposeProxy()) {
                return false;
            }
            if (this.advised.getTargetSource().isStatic() != otherAdvised.getTargetSource().isStatic()) {
                return false;
            }
            if (!AopProxyUtils.equalsProxiedInterfaces(this.advised, otherAdvised)) {
                return false;
            }
            // Advice instance identity is unimportant to the proxy class:
            // All that matters is type and ordering.
            Advisor[] thisAdvisors = this.advised.getAdvisors();
            Advisor[] thatAdvisors = otherAdvised.getAdvisors();
            if (thisAdvisors.length != thatAdvisors.length) {
                return false;
            }
            for (int i = 0; i < thisAdvisors.length; i++) {
                Advisor thisAdvisor = thisAdvisors[i];
                Advisor thatAdvisor = thatAdvisors[i];
                if (!equalsAdviceClasses(thisAdvisor, thatAdvisor)) {
                    return false;
                }
                if (!equalsPointcuts(thisAdvisor, thatAdvisor)) {
                    return false;
                }
            }
            return true;
        }

        private boolean equalsAdviceClasses(Advisor a, Advisor b) {
            Advice aa = a.getAdvice();
            Advice ba = b.getAdvice();
            if (aa == null || ba == null) {
                return (aa == ba);
            }
            return aa.getClass().equals(ba.getClass());
        }

        private boolean equalsPointcuts(Advisor a, Advisor b) {
            // If only one of the advisor (but not both) is PointcutAdvisor, then it is a mismatch.
            // Takes care of the situations where an IntroductionAdvisor is used (see SPR-3959).
            return (!(a instanceof PointcutAdvisor) || (b instanceof PointcutAdvisor && ObjectUtils
                    .nullSafeEquals(((PointcutAdvisor) a).getPointcut(), ((PointcutAdvisor) b).getPointcut())));
        }

        @Override
        public int hashCode() {
            int hashCode = 0;
            Advisor[] advisors = this.advised.getAdvisors();
            for (Advisor advisor : advisors) {
                Advice advice = advisor.getAdvice();
                if (advice != null) {
                    hashCode = 13 * hashCode + advice.getClass().hashCode();
                }
            }
            hashCode = 13 * hashCode + (this.advised.isFrozen() ? 1 : 0);
            hashCode = 13 * hashCode + (this.advised.isExposeProxy() ? 1 : 0);
            hashCode = 13 * hashCode + (this.advised.isOptimize() ? 1 : 0);
            hashCode = 13 * hashCode + (this.advised.isOpaque() ? 1 : 0);
            return hashCode;
        }
    }

}