Auto-adapt reflective arguments in case of vararg array type mismatch

Issue: SPR-13328
This commit is contained in:
Juergen Hoeller 2015-10-26 22:43:37 +01:00
parent 1c382be00e
commit 8c4b8d253a
8 changed files with 136 additions and 29 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2015 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.
@ -57,14 +57,14 @@ public interface ProxyMethodInvocation extends MethodInvocation {
* @return an invocable clone of this invocation. * @return an invocable clone of this invocation.
* {@code proceed()} can be called once per clone. * {@code proceed()} can be called once per clone.
*/ */
MethodInvocation invocableClone(Object[] arguments); MethodInvocation invocableClone(Object... arguments);
/** /**
* Set the arguments to be used on subsequent invocations in the any advice * Set the arguments to be used on subsequent invocations in the any advice
* in this chain. * in this chain.
* @param arguments the argument array * @param arguments the argument array
*/ */
void setArguments(Object[] arguments); void setArguments(Object... arguments);
/** /**
* Add the specified user attribute with the given value to this invocation. * Add the specified user attribute with the given value to this invocation.

View File

@ -16,6 +16,8 @@
package org.springframework.aop.framework; package org.springframework.aop.framework;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.Arrays; import java.util.Arrays;
@ -25,6 +27,7 @@ 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.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/** /**
* Utility methods for AOP proxy factories. * Utility methods for AOP proxy factories.
@ -161,4 +164,38 @@ public abstract class AopProxyUtils {
return Arrays.equals(a.getAdvisors(), b.getAdvisors()); return Arrays.equals(a.getAdvisors(), b.getAdvisors());
} }
/**
* Adapt the given arguments to the target signature in the given method,
* if necessary: in particular, if a given vararg argument array does not
* match the array type of the declared vararg parameter in the method.
* @param method the target method
* @param arguments the given arguments
* @return a cloned argument array, or the original if no adaptation is needed
* @since 4.2.3
*/
static Object[] adaptArgumentsIfNecessary(Method method, Object... arguments) {
if (method.isVarArgs() && !ObjectUtils.isEmpty(arguments)) {
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length == arguments.length) {
int varargIndex = paramTypes.length - 1;
Class<?> varargType = paramTypes[varargIndex];
if (varargType.isArray()) {
Object varargArray = arguments[varargIndex];
if (varargArray instanceof Object[] && !varargType.isInstance(varargArray)) {
Object[] newArguments = new Object[arguments.length];
System.arraycopy(arguments, 0, newArguments, 0, varargIndex);
Class<?> targetElementType = varargType.getComponentType();
int varargLength = Array.getLength(varargArray);
Object newVarargArray = Array.newInstance(targetElementType, varargLength);
System.arraycopy(varargArray, 0, newVarargArray, 0, varargLength);
newArguments[varargIndex] = newVarargArray;
return newArguments;
}
}
}
}
return arguments;
}
} }

View File

@ -646,7 +646,8 @@ class CglibAopProxy implements AopProxy, Serializable {
// Note that the final invoker must be an InvokerInterceptor, so we know // 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 // it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying. // swapping or fancy proxying.
retVal = methodProxy.invoke(target, args); Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
} }
else { else {
// We need to create a method invocation... // We need to create a method invocation...

View File

@ -198,7 +198,8 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
// We can skip creating a MethodInvocation: just invoke the target directly // 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 // 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. // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
} }
else { else {
// We need to create a method invocation... // We need to create a method invocation...

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2015 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.
@ -109,7 +109,7 @@ public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Clonea
this.target = target; this.target = target;
this.targetClass = targetClass; this.targetClass = targetClass;
this.method = BridgeMethodResolver.findBridgedMethod(method); this.method = BridgeMethodResolver.findBridgedMethod(method);
this.arguments = arguments; this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
} }
@ -145,7 +145,7 @@ public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Clonea
} }
@Override @Override
public void setArguments(Object[] arguments) { public void setArguments(Object... arguments) {
this.arguments = arguments; this.arguments = arguments;
} }
@ -219,7 +219,7 @@ public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Clonea
* @see java.lang.Object#clone() * @see java.lang.Object#clone()
*/ */
@Override @Override
public MethodInvocation invocableClone(Object[] arguments) { public MethodInvocation invocableClone(Object... arguments) {
// Force initialization of the user attributes Map, // Force initialization of the user attributes Map,
// for having a shared Map reference in the clone. // for having a shared Map reference in the clone.
if (this.userAttributes == null) { if (this.userAttributes == null) {

View File

@ -19,18 +19,15 @@ package org.springframework.aop.aspectj.annotation;
import java.util.Arrays; import java.util.Arrays;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import test.aop.PerThisAspect;
import org.springframework.util.SerializationTestUtils; import org.springframework.util.SerializationTestUtils;
import test.aop.PerThisAspect;
import static org.junit.Assert.*; import static org.junit.Assert.*;
/** /**
@ -109,18 +106,27 @@ public class AspectProxyFactoryTests {
} }
@Test // SPR-13328 @Test // SPR-13328
public void testVarargsWithEnumArray() throws Exception { public void testProxiedVarargsWithEnumArray() throws Exception {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean()); AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
proxyFactory.addAspect(LoggingAspect.class); proxyFactory.addAspect(LoggingAspectOnVarargs.class);
proxyFactory.setProxyTargetClass(true); ITestBean proxy = proxyFactory.getProxy();
TestBean proxy = proxyFactory.getProxy(); assertTrue(proxy.doWithVarargs(MyEnum.A, MyOtherEnum.C));
assertTrue(proxy.doWithVarargs(MyEnum.A, MyEnum.B)); }
@Test // SPR-13328
public void testUnproxiedVarargsWithEnumArray() throws Exception {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
proxyFactory.addAspect(LoggingAspectOnSetter.class);
ITestBean proxy = proxyFactory.getProxy();
assertTrue(proxy.doWithVarargs(MyEnum.A, MyOtherEnum.C));
} }
public interface ITestBean { public interface ITestBean {
int getAge(); int getAge();
<V extends MyInterface> boolean doWithVarargs(V... args);
} }
@ -138,6 +144,7 @@ public class AspectProxyFactoryTests {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override
public <V extends MyInterface> boolean doWithVarargs(V... args) { public <V extends MyInterface> boolean doWithVarargs(V... args) {
return true; return true;
} }
@ -154,12 +161,29 @@ public class AspectProxyFactoryTests {
} }
public enum MyOtherEnum implements MyInterface {
C, D;
}
@Aspect @Aspect
public static class LoggingAspect { public static class LoggingAspectOnVarargs {
@Around("execution(* doWithVarargs(*))") @Around("execution(* doWithVarargs(*))")
public Object doLog(ProceedingJoinPoint pjp) throws Throwable { public Object doLog(ProceedingJoinPoint pjp) throws Throwable {
LogFactory.getLog(LoggingAspect.class).debug(Arrays.asList(pjp.getArgs())); LogFactory.getLog(LoggingAspectOnVarargs.class).debug(Arrays.asList(pjp.getArgs()));
return pjp.proceed();
}
}
@Aspect
public static class LoggingAspectOnSetter {
@Around("execution(* setAge(*))")
public Object doLog(ProceedingJoinPoint pjp) throws Throwable {
LogFactory.getLog(LoggingAspectOnSetter.class).debug(Arrays.asList(pjp.getArgs()));
return pjp.proceed(); return pjp.proceed();
} }
} }

View File

@ -21,7 +21,6 @@ import java.io.Serializable;
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test; import org.junit.Test;
import test.mixin.LockMixinAdvisor; import test.mixin.LockMixinAdvisor;
import org.springframework.aop.ClassFilter; import org.springframework.aop.ClassFilter;
@ -395,7 +394,7 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
public void testVarargsWithEnumArray() throws Exception { public void testVarargsWithEnumArray() throws Exception {
ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); ProxyFactory proxyFactory = new ProxyFactory(new MyBean());
MyBean proxy = (MyBean) proxyFactory.getProxy(); MyBean proxy = (MyBean) proxyFactory.getProxy();
assertTrue(proxy.doWithVarargs(MyEnum.A, MyEnum.B)); assertTrue(proxy.doWithVarargs(MyEnum.A, MyOtherEnum.C));
} }
@ -432,6 +431,12 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
} }
public enum MyOtherEnum implements MyInterface {
C, D;
}
public static class ExceptionThrower { public static class ExceptionThrower {
private boolean catchInvoked; private boolean catchInvoked;

View File

@ -138,6 +138,13 @@ public class JdkDynamicProxyTests extends AbstractAopProxyTests implements Seria
assertEquals("hashCode()", proxy.hashCode(), named.hashCode()); assertEquals("hashCode()", proxy.hashCode(), named.hashCode());
} }
@Test // SPR-13328
public void testVarargsWithEnumArray() throws Exception {
ProxyFactory proxyFactory = new ProxyFactory(new VarargTestBean());
VarargTestInterface proxy = (VarargTestInterface) proxyFactory.getProxy();
assertTrue(proxy.doWithVarargs(MyEnum.A, MyOtherEnum.C));
}
public interface Foo { public interface Foo {
@ -201,4 +208,36 @@ public class JdkDynamicProxyTests extends AbstractAopProxyTests implements Seria
} }
} }
public interface VarargTestInterface {
<V extends MyInterface> boolean doWithVarargs(V... args);
}
public static class VarargTestBean implements VarargTestInterface {
@SuppressWarnings("unchecked")
@Override
public <V extends MyInterface> boolean doWithVarargs(V... args) {
return true;
}
}
public interface MyInterface {
}
public enum MyEnum implements MyInterface {
A, B;
}
public enum MyOtherEnum implements MyInterface {
C, D;
}
} }