001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.aop.framework;
018:
019: import java.io.Serializable;
020: import java.lang.reflect.InvocationHandler;
021: import java.lang.reflect.Method;
022: import java.lang.reflect.Proxy;
023: import java.util.List;
024:
025: import org.aopalliance.intercept.MethodInvocation;
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028:
029: import org.springframework.aop.RawTargetAccess;
030: import org.springframework.aop.TargetSource;
031: import org.springframework.aop.support.AopUtils;
032: import org.springframework.util.Assert;
033: import org.springframework.util.ClassUtils;
034:
035: /**
036: * JDK-based {@link AopProxy} implementation for the Spring AOP framework,
037: * based on JDK {@link java.lang.reflect.Proxy dynamic proxies}.
038: *
039: * <p>Creates a dynamic proxy, implementing the interfaces exposed by
040: * the AopProxy. Dynamic proxies <i>cannot</i> be used to proxy methods
041: * defined in classes, rather than interfaces.
042: *
043: * <p>Objects of this type should be obtained through proxy factories,
044: * configured by an {@link AdvisedSupport} class. This class is internal
045: * to Spring's AOP framework and need not be used directly by client code.
046: *
047: * <p>Proxies created using this class will be thread-safe if the
048: * underlying (target) class is thread-safe.
049: *
050: * <p>Proxies are serializable so long as all Advisors (including Advices
051: * and Pointcuts) and the TargetSource are serializable.
052: *
053: * @author Rod Johnson
054: * @author Juergen Hoeller
055: * @author Rob Harrop
056: * @see java.lang.reflect.Proxy
057: * @see AdvisedSupport
058: * @see ProxyFactory
059: */
060: final class JdkDynamicAopProxy implements AopProxy, InvocationHandler,
061: Serializable {
062:
063: /** use serialVersionUID from Spring 1.2 for interoperability */
064: private static final long serialVersionUID = 5531744639992436476L;
065:
066: /*
067: * NOTE: We could avoid the code duplication between this class and the CGLIB
068: * proxies by refactoring "invoke" into a template method. However, this approach
069: * adds at least 10% performance overhead versus a copy-paste solution, so we sacrifice
070: * elegance for performance. (We have a good test suite to ensure that the different
071: * proxies behave the same :-)
072: * This way, we can also more easily take advantage of minor optimizations in each class.
073: */
074:
075: /** We use a static Log to avoid serialization issues */
076: private static Log logger = LogFactory
077: .getLog(JdkDynamicAopProxy.class);
078:
079: /** Config used to configure this proxy */
080: private final AdvisedSupport advised;
081:
082: /**
083: * Is the {@link #equals} method defined on the proxied interfaces?
084: */
085: private boolean equalsDefined;
086:
087: /**
088: * Is the {@link #hashCode} method defined on the proxied interfaces?
089: */
090: private boolean hashCodeDefined;
091:
092: /**
093: * Construct a new JdkDynamicAopProxy for the given AOP configuration.
094: * @param config the AOP configuration as AdvisedSupport object
095: * @throws AopConfigException if the config is invalid. We try to throw an informative
096: * exception in this case, rather than let a mysterious failure happen later.
097: */
098: public JdkDynamicAopProxy(AdvisedSupport config)
099: throws AopConfigException {
100: Assert.notNull(config, "AdvisedSupport must not be null");
101: if (config.getAdvisors().length == 0
102: && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
103: throw new AopConfigException(
104: "No advisors and no TargetSource specified");
105: }
106: this .advised = config;
107: }
108:
109: public Object getProxy() {
110: return getProxy(ClassUtils.getDefaultClassLoader());
111: }
112:
113: public Object getProxy(ClassLoader classLoader) {
114: if (logger.isDebugEnabled()) {
115: logger
116: .debug("Creating JDK dynamic proxy: target source is "
117: + this .advised.getTargetSource());
118: }
119: Class[] proxiedInterfaces = AopProxyUtils
120: .completeProxiedInterfaces(this .advised);
121: findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
122: return Proxy.newProxyInstance(classLoader, proxiedInterfaces,
123: this );
124: }
125:
126: /**
127: * Finds any {@link #equals} or {@link #hashCode} method that may be defined
128: * on the supplied set of interfaces.
129: * @param proxiedInterfaces the interfaces to introspect
130: */
131: private void findDefinedEqualsAndHashCodeMethods(
132: Class[] proxiedInterfaces) {
133: for (int i = 0; i < proxiedInterfaces.length; i++) {
134: Class proxiedInterface = proxiedInterfaces[i];
135: Method[] methods = proxiedInterface.getDeclaredMethods();
136: for (int j = 0; j < methods.length; j++) {
137: Method method = methods[j];
138: if (AopUtils.isEqualsMethod(method)) {
139: this .equalsDefined = true;
140: }
141: if (AopUtils.isHashCodeMethod(method)) {
142: this .hashCodeDefined = true;
143: }
144: if (this .equalsDefined && this .hashCodeDefined) {
145: return;
146: }
147: }
148: }
149: }
150:
151: /**
152: * Implementation of <code>InvocationHandler.invoke</code>.
153: * <p>Callers will see exactly the exception thrown by the target,
154: * unless a hook method throws an exception.
155: */
156: public Object invoke(Object proxy, Method method, Object[] args)
157: throws Throwable {
158: MethodInvocation invocation = null;
159: Object oldProxy = null;
160: boolean setProxyContext = false;
161:
162: TargetSource targetSource = this .advised.targetSource;
163: Class targetClass = null;
164: Object target = null;
165:
166: try {
167: if (!this .equalsDefined && AopUtils.isEqualsMethod(method)) {
168: // The target does not implement the equals(Object) method itself.
169: return (equals(args[0]) ? Boolean.TRUE : Boolean.FALSE);
170: }
171: if (!this .hashCodeDefined
172: && AopUtils.isHashCodeMethod(method)) {
173: // The target does not implement the hashCode() method itself.
174: return new Integer(hashCode());
175: }
176: if (!this .advised.opaque
177: && method.getDeclaringClass().isInterface()
178: && method.getDeclaringClass().isAssignableFrom(
179: Advised.class)) {
180: // Service invocations on ProxyConfig with the proxy config...
181: return AopUtils.invokeJoinpointUsingReflection(
182: this .advised, method, args);
183: }
184:
185: Object retVal = null;
186:
187: if (this .advised.exposeProxy) {
188: // Make invocation available if necessary.
189: oldProxy = AopContext.setCurrentProxy(proxy);
190: setProxyContext = true;
191: }
192:
193: // May be <code>null</code>. Get as late as possible to minimize the time we "own" the target,
194: // in case it comes from a pool.
195: target = targetSource.getTarget();
196: if (target != null) {
197: targetClass = target.getClass();
198: }
199:
200: // Get the interception chain for this method.
201: List chain = this .advised
202: .getInterceptorsAndDynamicInterceptionAdvice(
203: method, targetClass);
204:
205: // Check whether we have any advice. If we don't, we can fallback on direct
206: // reflective invocation of the target, and avoid creating a MethodInvocation.
207: if (chain.isEmpty()) {
208: // We can skip creating a MethodInvocation: just invoke the target directly
209: // Note that the final invoker must be an InvokerInterceptor so we know it does
210: // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
211: retVal = AopUtils.invokeJoinpointUsingReflection(
212: target, method, args);
213: } else {
214: // We need to create a method invocation...
215: invocation = new ReflectiveMethodInvocation(proxy,
216: target, method, args, targetClass, chain);
217: // Proceed to the joinpoint through the interceptor chain.
218: retVal = invocation.proceed();
219: }
220:
221: // Massage return value if necessary.
222: if (retVal != null
223: && retVal == target
224: && method.getReturnType().isInstance(proxy)
225: && !RawTargetAccess.class.isAssignableFrom(method
226: .getDeclaringClass())) {
227: // Special case: it returned "this" and the return type of the method
228: // is type-compatible. Note that we can't help if the target sets
229: // a reference to itself in another returned object.
230: retVal = proxy;
231: }
232: return retVal;
233: } finally {
234: if (target != null && !targetSource.isStatic()) {
235: // Must have come from TargetSource.
236: targetSource.releaseTarget(target);
237: }
238: if (setProxyContext) {
239: // Restore old proxy.
240: AopContext.setCurrentProxy(oldProxy);
241: }
242: }
243: }
244:
245: /**
246: * Equality means interfaces, advisors and TargetSource are equal.
247: * <p>The compared object may be a JdkDynamicAopProxy instance itself
248: * or a dynamic proxy wrapping a JdkDynamicAopProxy instance.
249: */
250: public boolean equals(Object other) {
251: if (other == this ) {
252: return true;
253: }
254: if (other == null) {
255: return false;
256: }
257:
258: JdkDynamicAopProxy otherProxy = null;
259: if (other instanceof JdkDynamicAopProxy) {
260: otherProxy = (JdkDynamicAopProxy) other;
261: } else if (Proxy.isProxyClass(other.getClass())) {
262: InvocationHandler ih = Proxy.getInvocationHandler(other);
263: if (!(ih instanceof JdkDynamicAopProxy)) {
264: return false;
265: }
266: otherProxy = (JdkDynamicAopProxy) ih;
267: } else {
268: // Not a valid comparison...
269: return false;
270: }
271:
272: // If we get here, aopr2 is the other AopProxy.
273: return AopProxyUtils.equalsInProxy(this .advised,
274: otherProxy.advised);
275: }
276:
277: /**
278: * Proxy uses the hash code of the TargetSource.
279: */
280: public int hashCode() {
281: return JdkDynamicAopProxy.class.hashCode() * 13
282: + this.advised.getTargetSource().hashCode();
283: }
284:
285: }
|