/* * Copyright 2002-2007 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.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.RawTargetAccess; import org.springframework.aop.TargetSource; import org.springframework.aop.support.AopUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** * JDK-based {@link AopProxy} implementation for the Spring AOP framework, * based on JDK {@link java.lang.reflect.Proxy dynamic proxies}. * * <p>Creates a dynamic proxy, implementing the interfaces exposed by * the AopProxy. Dynamic proxies <i>cannot</i> be used to proxy methods * defined in classes, rather than interfaces. * * <p>Objects of this type should be obtained through proxy factories, * configured by an {@link AdvisedSupport} class. This class is internal * to Spring's AOP framework and need not be used directly by client code. * * <p>Proxies created using this class will be thread-safe if the * underlying (target) class is thread-safe. * * <p>Proxies are serializable so long as all Advisors (including Advices * and Pointcuts) and the TargetSource are serializable. * * @author Rod Johnson * @author Juergen Hoeller * @author Rob Harrop * @see java.lang.reflect.Proxy * @see AdvisedSupport * @see ProxyFactory */ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { /** use serialVersionUID from Spring 1.2 for interoperability */ private static final long serialVersionUID = 5531744639992436476L; /* * NOTE: We could avoid the code duplication between this class and the CGLIB * proxies by refactoring "invoke" into a template method. However, this approach * adds at least 10% performance overhead versus a copy-paste solution, so we sacrifice * elegance for performance. (We have a good test suite to ensure that the different * proxies behave the same :-) * This way, we can also more easily take advantage of minor optimizations in each class. */ /** We use a static Log to avoid serialization issues */ private static Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); /** Config used to configure this proxy */ private final AdvisedSupport advised; /** * Is the {@link #equals} method defined on the proxied interfaces? */ private boolean equalsDefined; /** * Is the {@link #hashCode} method defined on the proxied interfaces? */ private boolean hashCodeDefined; /** * Construct a new JdkDynamicAopProxy 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 JdkDynamicAopProxy(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; } public Object getProxy() { return getProxy(ClassUtils.getDefaultClassLoader()); } public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } /** * Finds any {@link #equals} or {@link #hashCode} method that may be defined * on the supplied set of interfaces. * @param proxiedInterfaces the interfaces to introspect */ private void findDefinedEqualsAndHashCodeMethods(Class[] proxiedInterfaces) { for (int i = 0; i < proxiedInterfaces.length; i++) { Class proxiedInterface = proxiedInterfaces[i]; Method[] methods = proxiedInterface.getDeclaredMethods(); for (int j = 0; j < methods.length; j++) { Method method = methods[j]; if (AopUtils.isEqualsMethod(method)) { this.equalsDefined = true; } if (AopUtils.isHashCodeMethod(method)) { this.hashCodeDefined = true; } if (this.equalsDefined && this.hashCodeDefined) { return; } } } } /** * Implementation of <code>InvocationHandler.invoke</code>. * <p>Callers will see exactly the exception thrown by the target, * unless a hook method throws an exception. */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation invocation = null; Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Class targetClass = null; Object target = null; try { if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { // The target does not implement the equals(Object) method itself. return (equals(args[0]) ? Boolean.TRUE : Boolean.FALSE); } if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { // The target does not implement the hashCode() method itself. return new Integer(hashCode()); } if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations on ProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal = null; 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 = targetSource.getTarget(); if (target != null) { targetClass = target.getClass(); } // Get the interception chain for this method. List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // Check whether we have any advice. If we don't, we can fallback on direct // reflective invocation of the target, and avoid creating a MethodInvocation. if (chain.isEmpty()) { // 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 = AopUtils.invokeJoinpointUsingReflection(target, method, args); } else { // We need to create a method invocation... invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed(); } // Massage return value if necessary. if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { // Special case: it returned "this" and the return type of the method // is type-compatible. Note that we can't help if the target sets // a reference to itself in another returned object. retVal = proxy; } return retVal; } finally { if (target != null && !targetSource.isStatic()) { // Must have come from TargetSource. targetSource.releaseTarget(target); } if (setProxyContext) { // Restore old proxy. AopContext.setCurrentProxy(oldProxy); } } } /** * Equality means interfaces, advisors and TargetSource are equal. * <p>The compared object may be a JdkDynamicAopProxy instance itself * or a dynamic proxy wrapping a JdkDynamicAopProxy instance. */ public boolean equals(Object other) { if (other == this) { return true; } if (other == null) { return false; } JdkDynamicAopProxy otherProxy = null; if (other instanceof JdkDynamicAopProxy) { otherProxy = (JdkDynamicAopProxy) other; } else if (Proxy.isProxyClass(other.getClass())) { InvocationHandler ih = Proxy.getInvocationHandler(other); if (!(ih instanceof JdkDynamicAopProxy)) { return false; } otherProxy = (JdkDynamicAopProxy) ih; } else { // Not a valid comparison... return false; } // If we get here, aopr2 is the other AopProxy. return AopProxyUtils.equalsInProxy(this.advised, otherProxy.advised); } /** * Proxy uses the hash code of the TargetSource. */ public int hashCode() { return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode(); } }