1 /*
2 * Copyright 2002-2007 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 */
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
107 public Object getProxy() {
108 return getProxy(ClassUtils.getDefaultClassLoader());
109 }
110
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 */
125 private void findDefinedEqualsAndHashCodeMethods(Class[] proxiedInterfaces) {
126 for (int i = 0; i < proxiedInterfaces.length; i++) {
127 Class proxiedInterface = proxiedInterfaces[i];
128 Method[] methods = proxiedInterface.getDeclaredMethods();
129 for (int j = 0; j < methods.length; j++) {
130 Method method = methods[j];
131 if (AopUtils.isEqualsMethod(method)) {
132 this.equalsDefined = true;
133 }
134 if (AopUtils.isHashCodeMethod(method)) {
135 this.hashCodeDefined = true;
136 }
137 if (this.equalsDefined && this.hashCodeDefined) {
138 return;
139 }
140 }
141 }
142 }
143
144
145 /**
146 * Implementation of <code>InvocationHandler.invoke</code>.
147 * <p>Callers will see exactly the exception thrown by the target,
148 * unless a hook method throws an exception.
149 */
150 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
151 MethodInvocation invocation = null;
152 Object oldProxy = null;
153 boolean setProxyContext = false;
154
155 TargetSource targetSource = this.advised.targetSource;
156 Class targetClass = null;
157 Object target = null;
158
159 try {
160 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
161 // The target does not implement the equals(Object) method itself.
162 return (equals(args[0]) ? Boolean.TRUE : Boolean.FALSE);
163 }
164 if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
165 // The target does not implement the hashCode() method itself.
166 return new Integer(hashCode());
167 }
168 if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
169 method.getDeclaringClass().isAssignableFrom(Advised.class)) {
170 // Service invocations on ProxyConfig with the proxy config...
171 return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
172 }
173
174 Object retVal = null;
175
176 if (this.advised.exposeProxy) {
177 // Make invocation available if necessary.
178 oldProxy = AopContext.setCurrentProxy(proxy);
179 setProxyContext = true;
180 }
181
182 // May be <code>null</code>. Get as late as possible to minimize the time we "own" the target,
183 // in case it comes from a pool.
184 target = targetSource.getTarget();
185 if (target != null) {
186 targetClass = target.getClass();
187 }
188
189 // Get the interception chain for this method.
190 List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
191
192 // Check whether we have any advice. If we don't, we can fallback on direct
193 // reflective invocation of the target, and avoid creating a MethodInvocation.
194 if (chain.isEmpty()) {
195 // We can skip creating a MethodInvocation: just invoke the target directly
196 // Note that the final invoker must be an InvokerInterceptor so we know it does
197 // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
198 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
199 }
200 else {
201 // We need to create a method invocation...
202 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
203 // Proceed to the joinpoint through the interceptor chain.
204 retVal = invocation.proceed();
205 }
206
207 // Massage return value if necessary.
208 if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) &&
209 !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
210 // Special case: it returned "this" and the return type of the method
211 // is type-compatible. Note that we can't help if the target sets
212 // a reference to itself in another returned object.
213 retVal = proxy;
214 }
215 return retVal;
216 }
217 finally {
218 if (target != null && !targetSource.isStatic()) {
219 // Must have come from TargetSource.
220 targetSource.releaseTarget(target);
221 }
222 if (setProxyContext) {
223 // Restore old proxy.
224 AopContext.setCurrentProxy(oldProxy);
225 }
226 }
227 }
228
229
230 /**
231 * Equality means interfaces, advisors and TargetSource are equal.
232 * <p>The compared object may be a JdkDynamicAopProxy instance itself
233 * or a dynamic proxy wrapping a JdkDynamicAopProxy instance.
234 */
235 public boolean equals(Object other) {
236 if (other == this) {
237 return true;
238 }
239 if (other == null) {
240 return false;
241 }
242
243 JdkDynamicAopProxy otherProxy = null;
244 if (other instanceof JdkDynamicAopProxy) {
245 otherProxy = (JdkDynamicAopProxy) other;
246 }
247 else if (Proxy.isProxyClass(other.getClass())) {
248 InvocationHandler ih = Proxy.getInvocationHandler(other);
249 if (!(ih instanceof JdkDynamicAopProxy)) {
250 return false;
251 }
252 otherProxy = (JdkDynamicAopProxy) ih;
253 }
254 else {
255 // Not a valid comparison...
256 return false;
257 }
258
259 // If we get here, aopr2 is the other AopProxy.
260 return AopProxyUtils.equalsInProxy(this.advised, otherProxy.advised);
261 }
262
263 /**
264 * Proxy uses the hash code of the TargetSource.
265 */
266 public int hashCode() {
267 return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
268 }
269
270 }