Skip to content

Commit 8c4b8d2

Browse files
committed
Auto-adapt reflective arguments in case of vararg array type mismatch
Issue: SPR-13328
1 parent 1c382be commit 8c4b8d2

File tree

8 files changed

+136
-29
lines changed

8 files changed

+136
-29
lines changed

spring-aop/src/main/java/org/springframework/aop/ProxyMethodInvocation.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -57,14 +57,14 @@ public interface ProxyMethodInvocation extends MethodInvocation {
5757
* @return an invocable clone of this invocation.
5858
* {@code proceed()} can be called once per clone.
5959
*/
60-
MethodInvocation invocableClone(Object[] arguments);
60+
MethodInvocation invocableClone(Object... arguments);
6161

6262
/**
6363
* Set the arguments to be used on subsequent invocations in the any advice
6464
* in this chain.
6565
* @param arguments the argument array
6666
*/
67-
void setArguments(Object[] arguments);
67+
void setArguments(Object... arguments);
6868

6969
/**
7070
* Add the specified user attribute with the given value to this invocation.

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.aop.framework;
1818

19+
import java.lang.reflect.Array;
20+
import java.lang.reflect.Method;
1921
import java.lang.reflect.Proxy;
2022
import java.util.Arrays;
2123

@@ -25,6 +27,7 @@
2527
import org.springframework.aop.support.AopUtils;
2628
import org.springframework.aop.target.SingletonTargetSource;
2729
import org.springframework.util.Assert;
30+
import org.springframework.util.ObjectUtils;
2831

2932
/**
3033
* Utility methods for AOP proxy factories.
@@ -161,4 +164,38 @@ public static boolean equalsAdvisors(AdvisedSupport a, AdvisedSupport b) {
161164
return Arrays.equals(a.getAdvisors(), b.getAdvisors());
162165
}
163166

167+
168+
/**
169+
* Adapt the given arguments to the target signature in the given method,
170+
* if necessary: in particular, if a given vararg argument array does not
171+
* match the array type of the declared vararg parameter in the method.
172+
* @param method the target method
173+
* @param arguments the given arguments
174+
* @return a cloned argument array, or the original if no adaptation is needed
175+
* @since 4.2.3
176+
*/
177+
static Object[] adaptArgumentsIfNecessary(Method method, Object... arguments) {
178+
if (method.isVarArgs() && !ObjectUtils.isEmpty(arguments)) {
179+
Class<?>[] paramTypes = method.getParameterTypes();
180+
if (paramTypes.length == arguments.length) {
181+
int varargIndex = paramTypes.length - 1;
182+
Class<?> varargType = paramTypes[varargIndex];
183+
if (varargType.isArray()) {
184+
Object varargArray = arguments[varargIndex];
185+
if (varargArray instanceof Object[] && !varargType.isInstance(varargArray)) {
186+
Object[] newArguments = new Object[arguments.length];
187+
System.arraycopy(arguments, 0, newArguments, 0, varargIndex);
188+
Class<?> targetElementType = varargType.getComponentType();
189+
int varargLength = Array.getLength(varargArray);
190+
Object newVarargArray = Array.newInstance(targetElementType, varargLength);
191+
System.arraycopy(varargArray, 0, newVarargArray, 0, varargLength);
192+
newArguments[varargIndex] = newVarargArray;
193+
return newArguments;
194+
}
195+
}
196+
}
197+
}
198+
return arguments;
199+
}
200+
164201
}

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -304,13 +304,13 @@ private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
304304
Callback targetDispatcher = isStatic ?
305305
new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp();
306306

307-
Callback[] mainCallbacks = new Callback[]{
308-
aopInterceptor, // for normal advice
309-
targetInterceptor, // invoke target without considering advice, if optimized
310-
new SerializableNoOp(), // no override for methods mapped to this
311-
targetDispatcher, this.advisedDispatcher,
312-
new EqualsInterceptor(this.advised),
313-
new HashCodeInterceptor(this.advised)
307+
Callback[] mainCallbacks = new Callback[] {
308+
aopInterceptor, // for normal advice
309+
targetInterceptor, // invoke target without considering advice, if optimized
310+
new SerializableNoOp(), // no override for methods mapped to this
311+
targetDispatcher, this.advisedDispatcher,
312+
new EqualsInterceptor(this.advised),
313+
new HashCodeInterceptor(this.advised)
314314
};
315315

316316
Callback[] callbacks;
@@ -646,7 +646,8 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
646646
// Note that the final invoker must be an InvokerInterceptor, so we know
647647
// it does nothing but a reflective operation on the target, and no hot
648648
// swapping or fancy proxying.
649-
retVal = methodProxy.invoke(target, args);
649+
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
650+
retVal = methodProxy.invoke(target, argsToUse);
650651
}
651652
else {
652653
// We need to create a method invocation...

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
198198
// We can skip creating a MethodInvocation: just invoke the target directly
199199
// Note that the final invoker must be an InvokerInterceptor so we know it does
200200
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
201-
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
201+
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
202+
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
202203
}
203204
else {
204205
// We need to create a method invocation...

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -109,7 +109,7 @@ protected ReflectiveMethodInvocation(
109109
this.target = target;
110110
this.targetClass = targetClass;
111111
this.method = BridgeMethodResolver.findBridgedMethod(method);
112-
this.arguments = arguments;
112+
this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
113113
this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
114114
}
115115

@@ -145,7 +145,7 @@ public final Object[] getArguments() {
145145
}
146146

147147
@Override
148-
public void setArguments(Object[] arguments) {
148+
public void setArguments(Object... arguments) {
149149
this.arguments = arguments;
150150
}
151151

@@ -219,7 +219,7 @@ public MethodInvocation invocableClone() {
219219
* @see java.lang.Object#clone()
220220
*/
221221
@Override
222-
public MethodInvocation invocableClone(Object[] arguments) {
222+
public MethodInvocation invocableClone(Object... arguments) {
223223
// Force initialization of the user attributes Map,
224224
// for having a shared Map reference in the clone.
225225
if (this.userAttributes == null) {

spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectProxyFactoryTests.java

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,15 @@
1919
import java.util.Arrays;
2020

2121
import org.apache.commons.logging.LogFactory;
22-
2322
import org.aspectj.lang.ProceedingJoinPoint;
2423
import org.aspectj.lang.annotation.Around;
2524
import org.aspectj.lang.annotation.Aspect;
26-
2725
import org.junit.Ignore;
2826
import org.junit.Test;
27+
import test.aop.PerThisAspect;
2928

3029
import org.springframework.util.SerializationTestUtils;
3130

32-
import test.aop.PerThisAspect;
33-
3431
import static org.junit.Assert.*;
3532

3633
/**
@@ -109,18 +106,27 @@ public void testWithNonSingletonAspectInstance() throws Exception {
109106
}
110107

111108
@Test // SPR-13328
112-
public void testVarargsWithEnumArray() throws Exception {
109+
public void testProxiedVarargsWithEnumArray() throws Exception {
113110
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
114-
proxyFactory.addAspect(LoggingAspect.class);
115-
proxyFactory.setProxyTargetClass(true);
116-
TestBean proxy = proxyFactory.getProxy();
117-
assertTrue(proxy.doWithVarargs(MyEnum.A, MyEnum.B));
111+
proxyFactory.addAspect(LoggingAspectOnVarargs.class);
112+
ITestBean proxy = proxyFactory.getProxy();
113+
assertTrue(proxy.doWithVarargs(MyEnum.A, MyOtherEnum.C));
114+
}
115+
116+
@Test // SPR-13328
117+
public void testUnproxiedVarargsWithEnumArray() throws Exception {
118+
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
119+
proxyFactory.addAspect(LoggingAspectOnSetter.class);
120+
ITestBean proxy = proxyFactory.getProxy();
121+
assertTrue(proxy.doWithVarargs(MyEnum.A, MyOtherEnum.C));
118122
}
119123

120124

121125
public interface ITestBean {
122126

123127
int getAge();
128+
129+
<V extends MyInterface> boolean doWithVarargs(V... args);
124130
}
125131

126132

@@ -138,6 +144,7 @@ public void setAge(int age) {
138144
}
139145

140146
@SuppressWarnings("unchecked")
147+
@Override
141148
public <V extends MyInterface> boolean doWithVarargs(V... args) {
142149
return true;
143150
}
@@ -154,12 +161,29 @@ public enum MyEnum implements MyInterface {
154161
}
155162

156163

164+
public enum MyOtherEnum implements MyInterface {
165+
166+
C, D;
167+
}
168+
169+
157170
@Aspect
158-
public static class LoggingAspect {
171+
public static class LoggingAspectOnVarargs {
159172

160173
@Around("execution(* doWithVarargs(*))")
161174
public Object doLog(ProceedingJoinPoint pjp) throws Throwable {
162-
LogFactory.getLog(LoggingAspect.class).debug(Arrays.asList(pjp.getArgs()));
175+
LogFactory.getLog(LoggingAspectOnVarargs.class).debug(Arrays.asList(pjp.getArgs()));
176+
return pjp.proceed();
177+
}
178+
}
179+
180+
181+
@Aspect
182+
public static class LoggingAspectOnSetter {
183+
184+
@Around("execution(* setAge(*))")
185+
public Object doLog(ProceedingJoinPoint pjp) throws Throwable {
186+
LogFactory.getLog(LoggingAspectOnSetter.class).debug(Arrays.asList(pjp.getArgs()));
163187
return pjp.proceed();
164188
}
165189
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import org.aopalliance.intercept.MethodInterceptor;
2222
import org.aopalliance.intercept.MethodInvocation;
2323
import org.junit.Test;
24-
2524
import test.mixin.LockMixinAdvisor;
2625

2726
import org.springframework.aop.ClassFilter;
@@ -395,7 +394,7 @@ public void testProxyTargetClassInCaseOfNoInterfaces() throws Exception {
395394
public void testVarargsWithEnumArray() throws Exception {
396395
ProxyFactory proxyFactory = new ProxyFactory(new MyBean());
397396
MyBean proxy = (MyBean) proxyFactory.getProxy();
398-
assertTrue(proxy.doWithVarargs(MyEnum.A, MyEnum.B));
397+
assertTrue(proxy.doWithVarargs(MyEnum.A, MyOtherEnum.C));
399398
}
400399

401400

@@ -432,6 +431,12 @@ public enum MyEnum implements MyInterface {
432431
}
433432

434433

434+
public enum MyOtherEnum implements MyInterface {
435+
436+
C, D;
437+
}
438+
439+
435440
public static class ExceptionThrower {
436441

437442
private boolean catchInvoked;

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ public void testEqualsAndHashCodeDefined() throws Exception {
138138
assertEquals("hashCode()", proxy.hashCode(), named.hashCode());
139139
}
140140

141+
@Test // SPR-13328
142+
public void testVarargsWithEnumArray() throws Exception {
143+
ProxyFactory proxyFactory = new ProxyFactory(new VarargTestBean());
144+
VarargTestInterface proxy = (VarargTestInterface) proxyFactory.getProxy();
145+
assertTrue(proxy.doWithVarargs(MyEnum.A, MyOtherEnum.C));
146+
}
147+
141148

142149
public interface Foo {
143150

@@ -201,4 +208,36 @@ public int hashCode() {
201208
}
202209
}
203210

211+
212+
public interface VarargTestInterface {
213+
214+
<V extends MyInterface> boolean doWithVarargs(V... args);
215+
}
216+
217+
218+
public static class VarargTestBean implements VarargTestInterface {
219+
220+
@SuppressWarnings("unchecked")
221+
@Override
222+
public <V extends MyInterface> boolean doWithVarargs(V... args) {
223+
return true;
224+
}
225+
}
226+
227+
228+
public interface MyInterface {
229+
}
230+
231+
232+
public enum MyEnum implements MyInterface {
233+
234+
A, B;
235+
}
236+
237+
238+
public enum MyOtherEnum implements MyInterface {
239+
240+
C, D;
241+
}
242+
204243
}

0 commit comments

Comments
 (0)