Open Source Repository

Home /spring/spring-aop-3.0.5 | Repository Home



org/springframework/aop/framework/Cglib2AopProxy.java
/*
 * Copyright 2002-2010 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
 *
 *      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 org.springframework.aop.framework;

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;

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.aop.support.AopUtils;
import org.springframework.core.SmartClassLoader;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
 * CGLIB2-based {@link AopProxy} implementation for the Spring AOP framework.
 *
 <p><i>Requires CGLIB 2.1+ on the classpath.</i>.
 * As of Spring 2.0, earlier CGLIB versions are not supported anymore.
 *
 <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>{@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>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 configthrows AopConfigException {
    Assert.notNull(config, "AdvisedSupport must not be null");
    if (config.getAdvisors().length == && 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 &&
            ((SmartClassLoaderclassLoader).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 Enhancer}. Subclasses may wish to override this to return a custom
   {@link 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 rootClassthrows 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);

      // TODO: 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[xnew 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, ((Cglib2AopProxyother).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 methodProxythrows 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 methodProxythrows 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 methodProxythrows 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 methodProxythrows 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 = ((Factoryother).getCallback(INVOKE_EQUALS);
        if (!(callback instanceof EqualsInterceptor)) {
          return false;
        }
        AdvisedSupport otherAdvised = ((EqualsInterceptorcallback).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 methodProxythrows 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 methodProxythrows 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(((DynamicAdvisedInterceptorother).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 targetthrows 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 = (ProxyCallbackFilterother;
      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 (!(instanceof PointcutAdvisor||
          (instanceof PointcutAdvisor &&
              ObjectUtils.nullSafeEquals(((PointcutAdvisora).getPointcut()((PointcutAdvisorb).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() 0);
      hashCode = 13 * hashCode + (this.advised.isExposeProxy() 0);
      hashCode = 13 * hashCode + (this.advised.isOptimize() 0);
      hashCode = 13 * hashCode + (this.advised.isOpaque() 0);
      return hashCode;
    }
  }

}