AnnotationAwareOrderComparator uses DecoratingProxy interface for target class introspection

Issue: SPR-13884
This commit is contained in:
Juergen Hoeller
2016-03-18 22:12:10 +01:00
parent 9ac9135c24
commit 6e3fac85f3
8 changed files with 168 additions and 33 deletions

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.TargetSource;
import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.AopUtils;
import org.springframework.aop.target.SingletonTargetSource; import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.core.DecoratingProxy;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@@ -78,11 +79,29 @@ public abstract class AopProxyUtils {
* <p>This will always add the {@link Advised} interface unless the AdvisedSupport's * <p>This will always add the {@link Advised} interface unless the AdvisedSupport's
* {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the * {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the
* {@link org.springframework.aop.SpringProxy} marker interface. * {@link org.springframework.aop.SpringProxy} marker interface.
* @param advised the proxy config
* @return the complete set of interfaces to proxy * @return the complete set of interfaces to proxy
* @see SpringProxy
* @see Advised * @see Advised
* @see org.springframework.aop.SpringProxy
*/ */
public static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised) { public static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised) {
return completeProxiedInterfaces(advised, false);
}
/**
* Determine the complete set of interfaces to proxy for the given AOP configuration.
* <p>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(); Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
if (specifiedInterfaces.length == 0) { if (specifiedInterfaces.length == 0) {
// No user-specified interfaces: check whether target class is an interface. // 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 addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);
boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class); boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);
boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class));
int nonUserIfcCount = 0; int nonUserIfcCount = 0;
if (addSpringProxy) { if (addSpringProxy) {
nonUserIfcCount++; nonUserIfcCount++;
@@ -106,13 +126,22 @@ public abstract class AopProxyUtils {
if (addAdvised) { if (addAdvised) {
nonUserIfcCount++; nonUserIfcCount++;
} }
if (addDecoratingProxy) {
nonUserIfcCount++;
}
Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount]; Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];
System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);
int index = specifiedInterfaces.length;
if (addSpringProxy) { if (addSpringProxy) {
proxiedInterfaces[specifiedInterfaces.length] = SpringProxy.class; proxiedInterfaces[index] = SpringProxy.class;
index++;
} }
if (addAdvised) { if (addAdvised) {
proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class; proxiedInterfaces[index] = Advised.class;
index++;
}
if (addDecoratingProxy) {
proxiedInterfaces[index] = DecoratingProxy.class;
} }
return proxiedInterfaces; return proxiedInterfaces;
} }
@@ -134,6 +163,9 @@ public abstract class AopProxyUtils {
if (proxy instanceof Advised) { if (proxy instanceof Advised) {
nonUserIfcCount++; nonUserIfcCount++;
} }
if (proxy instanceof DecoratingProxy) {
nonUserIfcCount++;
}
Class<?>[] userInterfaces = new Class<?>[proxyInterfaces.length - nonUserIfcCount]; Class<?>[] userInterfaces = new Class<?>[proxyInterfaces.length - nonUserIfcCount];
System.arraycopy(proxyInterfaces, 0, userInterfaces, 0, userInterfaces.length); System.arraycopy(proxyInterfaces, 0, userInterfaces, 0, userInterfaces.length);
Assert.notEmpty(userInterfaces, "JDK proxy must implement one or more interfaces"); Assert.notEmpty(userInterfaces, "JDK proxy must implement one or more interfaces");

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.RawTargetAccess;
import org.springframework.aop.TargetSource; import org.springframework.aop.TargetSource;
import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.AopUtils;
import org.springframework.core.DecoratingProxy;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@@ -116,7 +117,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); 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); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); 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. // The target does not implement the equals(Object) method itself.
return equals(args[0]); 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. // The target does not implement the hashCode() method itself.
return hashCode(); 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)) { method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config... // Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; import org.springframework.util.ClassUtils;
/** /**
* Factory for AOP proxies for programmatic use, rather than via a bean * Factory for AOP proxies for programmatic use, rather than via declarative
* factory. This class provides a simple way of obtaining and configuring * setup in a bean factory. This class provides a simple way of obtaining
* AOP proxies in code. * and configuring AOP proxy instances in custom user code.
* *
* @author Rod Johnson * @author Rod Johnson
* @author Juergen Hoeller * @author Juergen Hoeller

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
package org.springframework.aop.framework; package org.springframework.aop.framework;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -34,7 +32,7 @@ import static org.junit.Assert.*;
* @author Rod Johnson * @author Rod Johnson
* @author Chris Beams * @author Chris Beams
*/ */
public final class AopProxyUtilsTests { public class AopProxyUtilsTests {
@Test @Test
public void testCompleteProxiedInterfacesWorksWithNull() { public void testCompleteProxiedInterfacesWorksWithNull() {
@@ -125,15 +123,10 @@ public final class AopProxyUtilsTests {
assertEquals(Comparable.class, userInterfaces[1]); assertEquals(Comparable.class, userInterfaces[1]);
} }
@Test(expected=IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testProxiedUserInterfacesWithNoInterface() { public void testProxiedUserInterfacesWithNoInterface() {
Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[0], Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[0],
new InvocationHandler() { (proxy1, method, args) -> null);
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
AopProxyUtils.proxiedUserInterfaces(proxy); AopProxyUtils.proxiedUserInterfaces(proxy);
} }

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.aop.framework; package org.springframework.aop.framework;
import java.util.ArrayList;
import java.util.List;
import javax.accessibility.Accessible; import javax.accessibility.Accessible;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.RootPaneContainer; 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.DefaultIntroductionAdvisor;
import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.DelegatingIntroductionInterceptor; 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.TimeStamped;
import org.springframework.tests.aop.advice.CountingBeforeAdvice; import org.springframework.tests.aop.advice.CountingBeforeAdvice;
import org.springframework.tests.aop.interceptor.NopInterceptor; import org.springframework.tests.aop.interceptor.NopInterceptor;
@@ -49,7 +53,7 @@ import static org.junit.Assert.*;
* @author Chris Beams * @author Chris Beams
* @since 14.05.2003 * @since 14.05.2003
*/ */
public final class ProxyFactoryTests { public class ProxyFactoryTests {
@Test @Test
public void testIndexOfMethods() { public void testIndexOfMethods() {
@@ -337,6 +341,34 @@ public final class ProxyFactoryTests {
assertTrue(proxy instanceof Accessible); assertTrue(proxy instanceof Accessible);
} }
@Test
public void testInterfaceProxiesCanBeOrderedThroughAnnotations() {
Object proxy1 = new ProxyFactory(new A()).getProxy();
Object proxy2 = new ProxyFactory(new B()).getProxy();
List<Object> list = new ArrayList<Object>(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<Object> list = new ArrayList<Object>(2);
list.add(proxy1);
list.add(proxy2);
AnnotationAwareOrderComparator.sort(list);
assertSame(proxy2, list.get(0));
assertSame(proxy1, list.get(1));
}
@SuppressWarnings("serial") @SuppressWarnings("serial")
private static class TimestampIntroductionInterceptor extends DelegatingIntroductionInterceptor 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() {
}
}
} }

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.*; import static org.junit.Assert.*;
/** /**
* @since 13.03.2003
* @author Rod Johnson * @author Rod Johnson
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Chris Beams * @author Chris Beams
* @since 13.03.2003
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class JdkDynamicProxyTests extends AbstractAopProxyTests implements Serializable { public class JdkDynamicProxyTests extends AbstractAopProxyTests implements Serializable {

View File

@@ -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.
*
* <p>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.
*
* <p>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.
* <p>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();
}

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.util.List;
import org.springframework.core.OrderComparator; import org.springframework.core.OrderComparator;
import org.springframework.core.DecoratingProxy;
/** /**
* {@code AnnotationAwareOrderComparator} is an extension of * {@code AnnotationAwareOrderComparator} is an extension of
@@ -81,10 +82,13 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
} }
} }
else if (obj != null) { 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. * multiple matches but only one object to be returned.
*/ */
public Integer getPriority(Object obj) { public Integer getPriority(Object obj) {
Integer priority = null;
if (obj instanceof Class) { if (obj instanceof Class) {
return OrderUtils.getPriority((Class<?>) obj); priority = OrderUtils.getPriority((Class<?>) obj);
} }
else if (obj != null) { 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;
} }