From 6e3fac85f3e2dca255abbac6d578189e331fac10 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 18 Mar 2016 22:12:10 +0100 Subject: [PATCH] AnnotationAwareOrderComparator uses DecoratingProxy interface for target class introspection Issue: SPR-13884 --- .../aop/framework/AopProxyUtils.java | 40 ++++++++++++-- .../aop/framework/JdkDynamicAopProxy.java | 13 +++-- .../aop/framework/ProxyFactory.java | 8 +-- .../aop/framework/AopProxyUtilsTests.java | 15 ++---- .../aop/framework/ProxyFactoryTests.java | 54 ++++++++++++++++++- .../aop/framework/JdkDynamicProxyTests.java | 4 +- .../springframework/core/DecoratingProxy.java | 47 ++++++++++++++++ .../AnnotationAwareOrderComparator.java | 20 ++++--- 8 files changed, 168 insertions(+), 33 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/core/DecoratingProxy.java diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java index 4e063d2bc06..15535a5e270 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.springframework.aop.TargetClassAware; import org.springframework.aop.TargetSource; import org.springframework.aop.support.AopUtils; import org.springframework.aop.target.SingletonTargetSource; +import org.springframework.core.DecoratingProxy; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -78,11 +79,29 @@ public abstract class AopProxyUtils { *

This will always add the {@link Advised} interface unless the AdvisedSupport's * {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the * {@link org.springframework.aop.SpringProxy} marker interface. + * @param advised the proxy config * @return the complete set of interfaces to proxy + * @see SpringProxy * @see Advised - * @see org.springframework.aop.SpringProxy */ public static Class[] completeProxiedInterfaces(AdvisedSupport advised) { + return completeProxiedInterfaces(advised, false); + } + + /** + * Determine the complete set of interfaces to proxy for the given AOP configuration. + *

This will always add the {@link Advised} interface unless the AdvisedSupport's + * {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the + * {@link org.springframework.aop.SpringProxy} marker interface. + * @param advised the proxy config + * @param decoratingProxy whether to expose the {@link DecoratingProxy} interface + * @return the complete set of interfaces to proxy + * @since 4.3 + * @see SpringProxy + * @see Advised + * @see DecoratingProxy + */ + static Class[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) { Class[] specifiedInterfaces = advised.getProxiedInterfaces(); if (specifiedInterfaces.length == 0) { // No user-specified interfaces: check whether target class is an interface. @@ -99,6 +118,7 @@ public abstract class AopProxyUtils { } boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class); boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class); + boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)); int nonUserIfcCount = 0; if (addSpringProxy) { nonUserIfcCount++; @@ -106,13 +126,22 @@ public abstract class AopProxyUtils { if (addAdvised) { nonUserIfcCount++; } + if (addDecoratingProxy) { + nonUserIfcCount++; + } Class[] proxiedInterfaces = new Class[specifiedInterfaces.length + nonUserIfcCount]; System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); + int index = specifiedInterfaces.length; if (addSpringProxy) { - proxiedInterfaces[specifiedInterfaces.length] = SpringProxy.class; + proxiedInterfaces[index] = SpringProxy.class; + index++; } if (addAdvised) { - proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class; + proxiedInterfaces[index] = Advised.class; + index++; + } + if (addDecoratingProxy) { + proxiedInterfaces[index] = DecoratingProxy.class; } return proxiedInterfaces; } @@ -134,6 +163,9 @@ public abstract class AopProxyUtils { if (proxy instanceof Advised) { nonUserIfcCount++; } + if (proxy instanceof DecoratingProxy) { + nonUserIfcCount++; + } Class[] userInterfaces = new Class[proxyInterfaces.length - nonUserIfcCount]; System.arraycopy(proxyInterfaces, 0, userInterfaces, 0, userInterfaces.length); Assert.notEmpty(userInterfaces, "JDK proxy must implement one or more interfaces"); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java index e98c3085c22..1f90b0b5732 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.springframework.aop.AopInvocationException; import org.springframework.aop.RawTargetAccess; import org.springframework.aop.TargetSource; import org.springframework.aop.support.AopUtils; +import org.springframework.core.DecoratingProxy; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -116,7 +117,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } - Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); + Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } @@ -164,11 +165,15 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa // The target does not implement the equals(Object) method itself. return equals(args[0]); } - if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { + else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { // The target does not implement the hashCode() method itself. return hashCode(); } - if (!this.advised.opaque && method.getDeclaringClass().isInterface() && + else if (method.getDeclaringClass() == DecoratingProxy.class) { + // There is only getDecoratedClass() declared -> dispatch to proxy config. + return AopProxyUtils.ultimateTargetClass(this.advised); + } + else 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); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java index db1e19a359c..854f9122f65 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,9 @@ import org.springframework.aop.TargetSource; import org.springframework.util.ClassUtils; /** - * Factory for AOP proxies for programmatic use, rather than via a bean - * factory. This class provides a simple way of obtaining and configuring - * AOP proxies in code. + * Factory for AOP proxies for programmatic use, rather than via declarative + * setup in a bean factory. This class provides a simple way of obtaining + * and configuring AOP proxy instances in custom user code. * * @author Rod Johnson * @author Juergen Hoeller diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java index ad7ad9dc7b3..42b12f6649d 100644 --- a/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.aop.framework; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.List; @@ -34,7 +32,7 @@ import static org.junit.Assert.*; * @author Rod Johnson * @author Chris Beams */ -public final class AopProxyUtilsTests { +public class AopProxyUtilsTests { @Test public void testCompleteProxiedInterfacesWorksWithNull() { @@ -125,15 +123,10 @@ public final class AopProxyUtilsTests { assertEquals(Comparable.class, userInterfaces[1]); } - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testProxiedUserInterfacesWithNoInterface() { Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[0], - new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - return null; - } - }); + (proxy1, method, args) -> null); AopProxyUtils.proxiedUserInterfaces(proxy); } diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java index 7b509ba6e78..b01b9b80666 100644 --- a/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.aop.framework; +import java.util.ArrayList; +import java.util.List; import javax.accessibility.Accessible; import javax.swing.JFrame; import javax.swing.RootPaneContainer; @@ -31,6 +33,8 @@ import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.DefaultIntroductionAdvisor; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.DelegatingIntroductionInterceptor; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.Order; import org.springframework.tests.TimeStamped; import org.springframework.tests.aop.advice.CountingBeforeAdvice; import org.springframework.tests.aop.interceptor.NopInterceptor; @@ -49,7 +53,7 @@ import static org.junit.Assert.*; * @author Chris Beams * @since 14.05.2003 */ -public final class ProxyFactoryTests { +public class ProxyFactoryTests { @Test public void testIndexOfMethods() { @@ -337,6 +341,34 @@ public final class ProxyFactoryTests { assertTrue(proxy instanceof Accessible); } + @Test + public void testInterfaceProxiesCanBeOrderedThroughAnnotations() { + Object proxy1 = new ProxyFactory(new A()).getProxy(); + Object proxy2 = new ProxyFactory(new B()).getProxy(); + List list = new ArrayList(2); + list.add(proxy1); + list.add(proxy2); + AnnotationAwareOrderComparator.sort(list); + assertSame(proxy2, list.get(0)); + assertSame(proxy1, list.get(1)); + } + + @Test + public void testTargetClassProxiesCanBeOrderedThroughAnnotations() { + ProxyFactory pf1 = new ProxyFactory(new A()); + pf1.setProxyTargetClass(true); + ProxyFactory pf2 = new ProxyFactory(new B()); + pf2.setProxyTargetClass(true); + Object proxy1 = pf1.getProxy(); + Object proxy2 = pf2.getProxy(); + List list = new ArrayList(2); + list.add(proxy1); + list.add(proxy2); + AnnotationAwareOrderComparator.sort(list); + assertSame(proxy2, list.get(0)); + assertSame(proxy1, list.get(1)); + } + @SuppressWarnings("serial") private static class TimestampIntroductionInterceptor extends DelegatingIntroductionInterceptor @@ -361,4 +393,22 @@ public final class ProxyFactoryTests { } } + + @Order(2) + public static class A implements Runnable { + + @Override + public void run() { + } + } + + + @Order(1) + public static class B implements Runnable{ + + @Override + public void run() { + } + } + } diff --git a/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java b/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java index dd8d1f3a452..384be563f67 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ import org.springframework.tests.sample.beans.TestBean; import static org.junit.Assert.*; /** - * @since 13.03.2003 * @author Rod Johnson * @author Juergen Hoeller * @author Chris Beams + * @since 13.03.2003 */ @SuppressWarnings("serial") public class JdkDynamicProxyTests extends AbstractAopProxyTests implements Serializable { diff --git a/spring-core/src/main/java/org/springframework/core/DecoratingProxy.java b/spring-core/src/main/java/org/springframework/core/DecoratingProxy.java new file mode 100644 index 00000000000..b3ae9fceb0a --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/DecoratingProxy.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core; + +/** + * Interface to be implemented by decorating proxies, in particular Spring AOP + * proxies but potentially also custom proxies with decorator semantics. + * + *

Note that this interface should just be implemented if the decorated class + * is not within the hierarchy of the proxy class to begin with. In particular, + * a "target-class" proxy such as a Spring AOP CGLIB proxy should not implement + * it since any lookup on the target class can simply be performed on the proxy + * class there anyway. + * + *

Defined in the core module in order to allow + * #{@link org.springframework.core.annotation.AnnotationAwareOrderComparator} + * (and potential other candidates without spring-aop dependencies) to use it + * for introspection purposes, in particular annotation lookups. + * + * @author Juergen Hoeller + * @since 4.3 + */ +public interface DecoratingProxy { + + /** + * Return the (ultimate) decorated class behind this proxy. + *

In case of an AOP proxy, this will be the ultimate target class, + * not just the immediate target (in case of multiple nested proxies). + * @return the decorated class (never {@code null}) + */ + Class getDecoratedClass(); + +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java index beecd919dec..23b70254ec5 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import org.springframework.core.OrderComparator; +import org.springframework.core.DecoratingProxy; /** * {@code AnnotationAwareOrderComparator} is an extension of @@ -81,10 +82,13 @@ public class AnnotationAwareOrderComparator extends OrderComparator { } } else if (obj != null) { - return OrderUtils.getOrder(obj.getClass()); + order = OrderUtils.getOrder(obj.getClass()); + if (order == null && obj instanceof DecoratingProxy) { + order = OrderUtils.getOrder(((DecoratingProxy) obj).getDecoratedClass()); + } } - return null; + return order; } /** @@ -94,13 +98,17 @@ public class AnnotationAwareOrderComparator extends OrderComparator { * multiple matches but only one object to be returned. */ public Integer getPriority(Object obj) { + Integer priority = null; if (obj instanceof Class) { - return OrderUtils.getPriority((Class) obj); + priority = OrderUtils.getPriority((Class) obj); } else if (obj != null) { - return OrderUtils.getPriority(obj.getClass()); + priority = OrderUtils.getPriority(obj.getClass()); + if (priority == null && obj instanceof DecoratingProxy) { + priority = OrderUtils.getOrder(((DecoratingProxy) obj).getDecoratedClass()); + } } - return null; + return priority; }