/** * 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 CallbackFilter.accept() to return the index of the callback we need. * * <p>The callbacks for each proxy are built up of a set of fixed callbacks for general use and * then a set of callbacks that are specific to a method for use on static targets with a fixed * advice chain. * * <p>The callback used is determined thus: * * <dl> * <dt>For exposed proxies * <dd>Exposing the proxy requires code to execute before and after the method/chain * invocation. This means we must use DynamicAdvisedInterceptor, since all other * interceptors can avoid the need for a try/catch block * <dt>For Object.finalize(): * <dd>No override for this method is used. * <dt>For equals(): * <dd>The EqualsInterceptor is used to redirect equals() calls to a special handler to this * proxy. * <dt>For methods on the Advised class: * <dd>the AdvisedDispatcher is used to dispatch the call directly to the target * <dt>For advised methods: * <dd>If the target is static and the advice chain is frozen then a * FixedChainStaticTargetInterceptor specific to the method is used to invoke the advice * chain. Otherwise a DyanmicAdvisedInterceptor is used. * <dt>For non-advised methods: * <dd>Where it can be determined that the method will not return {@code this} or when {@code * ProxyFactory.getExposeProxy()} returns {@code false}, then a Dispatcher is used. For * static targets, the StaticDispatcher is used; and for dynamic targets, a * DynamicUnadvisedInterceptor is used. If it possible for the method to return {@code * this} then a StaticUnadvisedInterceptor is used for static targets - the * DynamicUnadvisedInterceptor already considers this. * </dl> */ @Override public int accept(Method method) { if (AopUtils.isFinalizeMethod(method)) { logger.debug("Found finalize() method - using NO_OVERRIDE"); return NO_OVERRIDE; } if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { if (logger.isDebugEnabled()) { logger.debug("Method is declared on Advised interface: " + method); } return DISPATCH_ADVISED; } // We must always proxy equals, to direct calls to this. if (AopUtils.isEqualsMethod(method)) { logger.debug("Found 'equals' method: " + method); return INVOKE_EQUALS; } // We must always calculate hashCode based on the proxy. if (AopUtils.isHashCodeMethod(method)) { logger.debug("Found 'hashCode' method: " + method); return INVOKE_HASHCODE; } Class<?> targetClass = this.advised.getTargetClass(); // Proxy is not yet available, but that shouldn't matter. List<?> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); boolean haveAdvice = !chain.isEmpty(); boolean exposeProxy = this.advised.isExposeProxy(); boolean isStatic = this.advised.getTargetSource().isStatic(); boolean isFrozen = this.advised.isFrozen(); if (haveAdvice || !isFrozen) { // If exposing the proxy, then AOP_PROXY must be used. if (exposeProxy) { if (logger.isDebugEnabled()) { logger.debug("Must expose proxy on advised method: " + method); } return AOP_PROXY; } String key = method.toString(); // Check to see if we have fixed interceptor to serve this method. // Else use the AOP_PROXY. if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(key)) { if (logger.isDebugEnabled()) { logger.debug("Method has advice and optimisations are enabled: " + method); } // We know that we are optimising so we can use the FixedStaticChainInterceptors. int index = this.fixedInterceptorMap.get(key); return (index + this.fixedInterceptorOffset); } else { if (logger.isDebugEnabled()) { logger.debug("Unable to apply any optimisations to advised method: " + method); } return AOP_PROXY; } } else { // See if the return type of the method is outside the class hierarchy // of the target type. If so we know it never needs to have return type // massage and can use a dispatcher. // If the proxy is being exposed, then must use the interceptor the // correct one is already configured. If the target is not static, then // cannot use a dispatcher because the target cannot be released. if (exposeProxy || !isStatic) { return INVOKE_TARGET; } Class<?> returnType = method.getReturnType(); if (targetClass == returnType) { if (logger.isDebugEnabled()) { logger.debug( "Method " + method + "has return type same as target type (may return this) - using INVOKE_TARGET"); } return INVOKE_TARGET; } else if (returnType.isPrimitive() || !returnType.isAssignableFrom(targetClass)) { if (logger.isDebugEnabled()) { logger.debug( "Method " + method + " has return type that ensures this cannot be returned- using DISPATCH_TARGET"); } return DISPATCH_TARGET; } else { if (logger.isDebugEnabled()) { logger.debug( "Method " + method + "has return type that is assignable from the target type (may return this) - " + "using INVOKE_TARGET"); } return INVOKE_TARGET; } } }
/** * 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); } } }