1 /* 2 * Copyright 2002-2010 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.aop.framework; 18 19 import java.io.Serializable; 20 import java.lang.reflect.InvocationHandler; 21 import java.lang.reflect.Method; 22 import java.lang.reflect.Proxy; 23 import java.util.List; 24 25 import org.aopalliance.intercept.MethodInvocation; 26 import org.apache.commons.logging.Log; 27 import org.apache.commons.logging.LogFactory; 28 29 import org.springframework.aop.RawTargetAccess; 30 import org.springframework.aop.TargetSource; 31 import org.springframework.aop.support.AopUtils; 32 import org.springframework.util.Assert; 33 import org.springframework.util.ClassUtils; 34 35 /** 36 * JDK-based {@link AopProxy} implementation for the Spring AOP framework, 37 * based on JDK {@link java.lang.reflect.Proxy dynamic proxies}. 38 * 39 * <p>Creates a dynamic proxy, implementing the interfaces exposed by 40 * the AopProxy. Dynamic proxies <i>cannot</i> be used to proxy methods 41 * defined in classes, rather than interfaces. 42 * 43 * <p>Objects of this type should be obtained through proxy factories, 44 * configured by an {@link AdvisedSupport} class. This class is internal 45 * to Spring's AOP framework and need not be used directly by client code. 46 * 47 * <p>Proxies created using this class will be thread-safe if the 48 * underlying (target) class is thread-safe. 49 * 50 * <p>Proxies are serializable so long as all Advisors (including Advices 51 * and Pointcuts) and the TargetSource are serializable. 52 * 53 * @author Rod Johnson 54 * @author Juergen Hoeller 55 * @author Rob Harrop 56 * @see java.lang.reflect.Proxy 57 * @see AdvisedSupport 58 * @see ProxyFactory 59 */ 60 final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { 61 62 /** use serialVersionUID from Spring 1.2 for interoperability */ 63 private static final long serialVersionUID = 5531744639992436476L; 64 65 66 /* 67 * NOTE: We could avoid the code duplication between this class and the CGLIB 68 * proxies by refactoring "invoke" into a template method. However, this approach 69 * adds at least 10% performance overhead versus a copy-paste solution, so we sacrifice 70 * elegance for performance. (We have a good test suite to ensure that the different 71 * proxies behave the same :-) 72 * This way, we can also more easily take advantage of minor optimizations in each class. 73 */ 74 75 /** We use a static Log to avoid serialization issues */ 76 private static Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); 77 78 /** Config used to configure this proxy */ 79 private final AdvisedSupport advised; 80 81 /** 82 * Is the {@link #equals} method defined on the proxied interfaces? 83 */ 84 private boolean equalsDefined; 85 86 /** 87 * Is the {@link #hashCode} method defined on the proxied interfaces? 88 */ 89 private boolean hashCodeDefined; 90 91 92 /** 93 * Construct a new JdkDynamicAopProxy for the given AOP configuration. 94 * @param config the AOP configuration as AdvisedSupport object 95 * @throws AopConfigException if the config is invalid. We try to throw an informative 96 * exception in this case, rather than let a mysterious failure happen later. 97 */ JdkDynamicAopProxy(AdvisedSupport config)98 public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException { 99 Assert.notNull(config, "AdvisedSupport must not be null"); 100 if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) { 101 throw new AopConfigException("No advisors and no TargetSource specified"); 102 } 103 this.advised = config; 104 } 105 106 getProxy()107 public Object getProxy() { 108 return getProxy(ClassUtils.getDefaultClassLoader()); 109 } 110 getProxy(ClassLoader classLoader)111 public Object getProxy(ClassLoader classLoader) { 112 if (logger.isDebugEnabled()) { 113 logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); 114 } 115 Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); 116 findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); 117 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); 118 } 119 120 /** 121 * Finds any {@link #equals} or {@link #hashCode} method that may be defined 122 * on the supplied set of interfaces. 123 * @param proxiedInterfaces the interfaces to introspect 124 */ findDefinedEqualsAndHashCodeMethods(Class[] proxiedInterfaces)125 private void findDefinedEqualsAndHashCodeMethods(Class[] proxiedInterfaces) { 126 for (Class proxiedInterface : proxiedInterfaces) { 127 Method[] methods = proxiedInterface.getDeclaredMethods(); 128 for (Method method : methods) { 129 if (AopUtils.isEqualsMethod(method)) { 130 this.equalsDefined = true; 131 } 132 if (AopUtils.isHashCodeMethod(method)) { 133 this.hashCodeDefined = true; 134 } 135 if (this.equalsDefined && this.hashCodeDefined) { 136 return; 137 } 138 } 139 } 140 } 141 142 143 /** 144 * Implementation of <code>InvocationHandler.invoke</code>. 145 * <p>Callers will see exactly the exception thrown by the target, 146 * unless a hook method throws an exception. 147 */ invoke(Object proxy, Method method, Object[] args)148 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 149 MethodInvocation invocation; 150 Object oldProxy = null; 151 boolean setProxyContext = false; 152 153 TargetSource targetSource = this.advised.targetSource; 154 Class targetClass = null; 155 Object target = null; 156 157 try { 158 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { 159 // The target does not implement the equals(Object) method itself. 160 return equals(args[0]); 161 } 162 if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { 163 // The target does not implement the hashCode() method itself. 164 return hashCode(); 165 } 166 if (!this.advised.opaque && method.getDeclaringClass().isInterface() && 167 method.getDeclaringClass().isAssignableFrom(Advised.class)) { 168 // Service invocations on ProxyConfig with the proxy config... 169 return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); 170 } 171 172 Object retVal; 173 174 if (this.advised.exposeProxy) { 175 // Make invocation available if necessary. 176 oldProxy = AopContext.setCurrentProxy(proxy); 177 setProxyContext = true; 178 } 179 180 // May be null. Get as late as possible to minimize the time we "own" the target, 181 // in case it comes from a pool. 182 target = targetSource.getTarget(); 183 if (target != null) { 184 targetClass = target.getClass(); 185 } 186 187 // Get the interception chain for this method. 188 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); 189 190 // Check whether we have any advice. If we don't, we can fallback on direct 191 // reflective invocation of the target, and avoid creating a MethodInvocation. 192 if (chain.isEmpty()) { 193 // We can skip creating a MethodInvocation: just invoke the target directly 194 // Note that the final invoker must be an InvokerInterceptor so we know it does 195 // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. 196 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); 197 } 198 else { 199 // We need to create a method invocation... 200 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); 201 // Proceed to the joinpoint through the interceptor chain. 202 retVal = invocation.proceed(); 203 } 204 205 // Massage return value if necessary. 206 if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) && 207 !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { 208 // Special case: it returned "this" and the return type of the method 209 // is type-compatible. Note that we can't help if the target sets 210 // a reference to itself in another returned object. 211 retVal = proxy; 212 } 213 return retVal; 214 } 215 finally { 216 if (target != null && !targetSource.isStatic()) { 217 // Must have come from TargetSource. 218 targetSource.releaseTarget(target); 219 } 220 if (setProxyContext) { 221 // Restore old proxy. 222 AopContext.setCurrentProxy(oldProxy); 223 } 224 } 225 } 226 227 228 /** 229 * Equality means interfaces, advisors and TargetSource are equal. 230 * <p>The compared object may be a JdkDynamicAopProxy instance itself 231 * or a dynamic proxy wrapping a JdkDynamicAopProxy instance. 232 */ 233 @Override equals(Object other)234 public boolean equals(Object other) { 235 if (other == this) { 236 return true; 237 } 238 if (other == null) { 239 return false; 240 } 241 242 JdkDynamicAopProxy otherProxy; 243 if (other instanceof JdkDynamicAopProxy) { 244 otherProxy = (JdkDynamicAopProxy) other; 245 } 246 else if (Proxy.isProxyClass(other.getClass())) { 247 InvocationHandler ih = Proxy.getInvocationHandler(other); 248 if (!(ih instanceof JdkDynamicAopProxy)) { 249 return false; 250 } 251 otherProxy = (JdkDynamicAopProxy) ih; 252 } 253 else { 254 // Not a valid comparison... 255 return false; 256 } 257 258 // If we get here, otherProxy is the other AopProxy. 259 return AopProxyUtils.equalsInProxy(this.advised, otherProxy.advised); 260 } 261 262 /** 263 * Proxy uses the hash code of the TargetSource. 264 */ 265 @Override hashCode()266 public int hashCode() { 267 return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode(); 268 } 269 270 } 271