Skip to content

Commit 6e3fac8

Browse files
committed
AnnotationAwareOrderComparator uses DecoratingProxy interface for target class introspection
Issue: SPR-13884
1 parent 9ac9135 commit 6e3fac8

File tree

8 files changed

+168
-33
lines changed

8 files changed

+168
-33
lines changed

spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import org.springframework.aop.TargetSource;
2727
import org.springframework.aop.support.AopUtils;
2828
import org.springframework.aop.target.SingletonTargetSource;
29+
import org.springframework.core.DecoratingProxy;
2930
import org.springframework.util.Assert;
3031
import org.springframework.util.ObjectUtils;
3132

@@ -78,11 +79,29 @@ public static Class<?> ultimateTargetClass(Object candidate) {
7879
* <p>This will always add the {@link Advised} interface unless the AdvisedSupport's
7980
* {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the
8081
* {@link org.springframework.aop.SpringProxy} marker interface.
82+
* @param advised the proxy config
8183
* @return the complete set of interfaces to proxy
84+
* @see SpringProxy
8285
* @see Advised
83-
* @see org.springframework.aop.SpringProxy
8486
*/
8587
public static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised) {
88+
return completeProxiedInterfaces(advised, false);
89+
}
90+
91+
/**
92+
* Determine the complete set of interfaces to proxy for the given AOP configuration.
93+
* <p>This will always add the {@link Advised} interface unless the AdvisedSupport's
94+
* {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the
95+
* {@link org.springframework.aop.SpringProxy} marker interface.
96+
* @param advised the proxy config
97+
* @param decoratingProxy whether to expose the {@link DecoratingProxy} interface
98+
* @return the complete set of interfaces to proxy
99+
* @since 4.3
100+
* @see SpringProxy
101+
* @see Advised
102+
* @see DecoratingProxy
103+
*/
104+
static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
86105
Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
87106
if (specifiedInterfaces.length == 0) {
88107
// No user-specified interfaces: check whether target class is an interface.
@@ -99,20 +118,30 @@ else if (Proxy.isProxyClass(targetClass)) {
99118
}
100119
boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);
101120
boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);
121+
boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class));
102122
int nonUserIfcCount = 0;
103123
if (addSpringProxy) {
104124
nonUserIfcCount++;
105125
}
106126
if (addAdvised) {
107127
nonUserIfcCount++;
108128
}
129+
if (addDecoratingProxy) {
130+
nonUserIfcCount++;
131+
}
109132
Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];
110133
System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);
134+
int index = specifiedInterfaces.length;
111135
if (addSpringProxy) {
112-
proxiedInterfaces[specifiedInterfaces.length] = SpringProxy.class;
136+
proxiedInterfaces[index] = SpringProxy.class;
137+
index++;
113138
}
114139
if (addAdvised) {
115-
proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class;
140+
proxiedInterfaces[index] = Advised.class;
141+
index++;
142+
}
143+
if (addDecoratingProxy) {
144+
proxiedInterfaces[index] = DecoratingProxy.class;
116145
}
117146
return proxiedInterfaces;
118147
}
@@ -134,6 +163,9 @@ public static Class<?>[] proxiedUserInterfaces(Object proxy) {
134163
if (proxy instanceof Advised) {
135164
nonUserIfcCount++;
136165
}
166+
if (proxy instanceof DecoratingProxy) {
167+
nonUserIfcCount++;
168+
}
137169
Class<?>[] userInterfaces = new Class<?>[proxyInterfaces.length - nonUserIfcCount];
138170
System.arraycopy(proxyInterfaces, 0, userInterfaces, 0, userInterfaces.length);
139171
Assert.notEmpty(userInterfaces, "JDK proxy must implement one or more interfaces");

spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
import org.springframework.aop.RawTargetAccess;
3131
import org.springframework.aop.TargetSource;
3232
import org.springframework.aop.support.AopUtils;
33+
import org.springframework.core.DecoratingProxy;
3334
import org.springframework.util.Assert;
3435
import org.springframework.util.ClassUtils;
3536

@@ -116,7 +117,7 @@ public Object getProxy(ClassLoader classLoader) {
116117
if (logger.isDebugEnabled()) {
117118
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
118119
}
119-
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
120+
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
120121
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
121122
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
122123
}
@@ -164,11 +165,15 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
164165
// The target does not implement the equals(Object) method itself.
165166
return equals(args[0]);
166167
}
167-
if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
168+
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
168169
// The target does not implement the hashCode() method itself.
169170
return hashCode();
170171
}
171-
if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
172+
else if (method.getDeclaringClass() == DecoratingProxy.class) {
173+
// There is only getDecoratedClass() declared -> dispatch to proxy config.
174+
return AopProxyUtils.ultimateTargetClass(this.advised);
175+
}
176+
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
172177
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
173178
// Service invocations on ProxyConfig with the proxy config...
174179
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);

spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,9 +22,9 @@
2222
import org.springframework.util.ClassUtils;
2323

2424
/**
25-
* Factory for AOP proxies for programmatic use, rather than via a bean
26-
* factory. This class provides a simple way of obtaining and configuring
27-
* AOP proxies in code.
25+
* Factory for AOP proxies for programmatic use, rather than via declarative
26+
* setup in a bean factory. This class provides a simple way of obtaining
27+
* and configuring AOP proxy instances in custom user code.
2828
*
2929
* @author Rod Johnson
3030
* @author Juergen Hoeller

spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.aop.framework;
1818

19-
import java.lang.reflect.InvocationHandler;
20-
import java.lang.reflect.Method;
2119
import java.lang.reflect.Proxy;
2220
import java.util.Arrays;
2321
import java.util.List;
@@ -34,7 +32,7 @@
3432
* @author Rod Johnson
3533
* @author Chris Beams
3634
*/
37-
public final class AopProxyUtilsTests {
35+
public class AopProxyUtilsTests {
3836

3937
@Test
4038
public void testCompleteProxiedInterfacesWorksWithNull() {
@@ -125,15 +123,10 @@ public void testProxiedUserInterfacesWithMultipleInterfaces() {
125123
assertEquals(Comparable.class, userInterfaces[1]);
126124
}
127125

128-
@Test(expected=IllegalArgumentException.class)
126+
@Test(expected = IllegalArgumentException.class)
129127
public void testProxiedUserInterfacesWithNoInterface() {
130128
Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[0],
131-
new InvocationHandler() {
132-
@Override
133-
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
134-
return null;
135-
}
136-
});
129+
(proxy1, method, args) -> null);
137130
AopProxyUtils.proxiedUserInterfaces(proxy);
138131
}
139132

spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.aop.framework;
1818

19+
import java.util.ArrayList;
20+
import java.util.List;
1921
import javax.accessibility.Accessible;
2022
import javax.swing.JFrame;
2123
import javax.swing.RootPaneContainer;
@@ -31,6 +33,8 @@
3133
import org.springframework.aop.support.DefaultIntroductionAdvisor;
3234
import org.springframework.aop.support.DefaultPointcutAdvisor;
3335
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
36+
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
37+
import org.springframework.core.annotation.Order;
3438
import org.springframework.tests.TimeStamped;
3539
import org.springframework.tests.aop.advice.CountingBeforeAdvice;
3640
import org.springframework.tests.aop.interceptor.NopInterceptor;
@@ -49,7 +53,7 @@
4953
* @author Chris Beams
5054
* @since 14.05.2003
5155
*/
52-
public final class ProxyFactoryTests {
56+
public class ProxyFactoryTests {
5357

5458
@Test
5559
public void testIndexOfMethods() {
@@ -337,6 +341,34 @@ public void testExclusionOfNonPublicInterfaces() {
337341
assertTrue(proxy instanceof Accessible);
338342
}
339343

344+
@Test
345+
public void testInterfaceProxiesCanBeOrderedThroughAnnotations() {
346+
Object proxy1 = new ProxyFactory(new A()).getProxy();
347+
Object proxy2 = new ProxyFactory(new B()).getProxy();
348+
List<Object> list = new ArrayList<Object>(2);
349+
list.add(proxy1);
350+
list.add(proxy2);
351+
AnnotationAwareOrderComparator.sort(list);
352+
assertSame(proxy2, list.get(0));
353+
assertSame(proxy1, list.get(1));
354+
}
355+
356+
@Test
357+
public void testTargetClassProxiesCanBeOrderedThroughAnnotations() {
358+
ProxyFactory pf1 = new ProxyFactory(new A());
359+
pf1.setProxyTargetClass(true);
360+
ProxyFactory pf2 = new ProxyFactory(new B());
361+
pf2.setProxyTargetClass(true);
362+
Object proxy1 = pf1.getProxy();
363+
Object proxy2 = pf2.getProxy();
364+
List<Object> list = new ArrayList<Object>(2);
365+
list.add(proxy1);
366+
list.add(proxy2);
367+
AnnotationAwareOrderComparator.sort(list);
368+
assertSame(proxy2, list.get(0));
369+
assertSame(proxy1, list.get(1));
370+
}
371+
340372

341373
@SuppressWarnings("serial")
342374
private static class TimestampIntroductionInterceptor extends DelegatingIntroductionInterceptor
@@ -361,4 +393,22 @@ public long getTimeStamp() {
361393
}
362394
}
363395

396+
397+
@Order(2)
398+
public static class A implements Runnable {
399+
400+
@Override
401+
public void run() {
402+
}
403+
}
404+
405+
406+
@Order(1)
407+
public static class B implements Runnable{
408+
409+
@Override
410+
public void run() {
411+
}
412+
}
413+
364414
}

spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,10 +32,10 @@
3232
import static org.junit.Assert.*;
3333

3434
/**
35-
* @since 13.03.2003
3635
* @author Rod Johnson
3736
* @author Juergen Hoeller
3837
* @author Chris Beams
38+
* @since 13.03.2003
3939
*/
4040
@SuppressWarnings("serial")
4141
public class JdkDynamicProxyTests extends AbstractAopProxyTests implements Serializable {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2002-2016 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.core;
18+
19+
/**
20+
* Interface to be implemented by decorating proxies, in particular Spring AOP
21+
* proxies but potentially also custom proxies with decorator semantics.
22+
*
23+
* <p>Note that this interface should just be implemented if the decorated class
24+
* is not within the hierarchy of the proxy class to begin with. In particular,
25+
* a "target-class" proxy such as a Spring AOP CGLIB proxy should not implement
26+
* it since any lookup on the target class can simply be performed on the proxy
27+
* class there anyway.
28+
*
29+
* <p>Defined in the core module in order to allow
30+
* #{@link org.springframework.core.annotation.AnnotationAwareOrderComparator}
31+
* (and potential other candidates without spring-aop dependencies) to use it
32+
* for introspection purposes, in particular annotation lookups.
33+
*
34+
* @author Juergen Hoeller
35+
* @since 4.3
36+
*/
37+
public interface DecoratingProxy {
38+
39+
/**
40+
* Return the (ultimate) decorated class behind this proxy.
41+
* <p>In case of an AOP proxy, this will be the ultimate target class,
42+
* not just the immediate target (in case of multiple nested proxies).
43+
* @return the decorated class (never {@code null})
44+
*/
45+
Class<?> getDecoratedClass();
46+
47+
}

0 commit comments

Comments
 (0)