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