Skip to content

Commit 39e3f2e

Browse files
committed
MethodParameter supports Java 8 Executable/Parameter and validates parameter indexes
Also, equals insists on the same class now, differentiating from SynthesizingMethodParameter. Issue: SPR-14055 Issue: SPR-13456 Issue: SPR-14438
1 parent da9c24c commit 39e3f2e

File tree

9 files changed

+312
-86
lines changed

9 files changed

+312
-86
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import java.beans.ConstructorProperties;
2020
import java.lang.reflect.Constructor;
21-
import java.lang.reflect.Member;
21+
import java.lang.reflect.Executable;
2222
import java.lang.reflect.Method;
2323
import java.lang.reflect.Modifier;
2424
import java.security.AccessController;
@@ -506,7 +506,7 @@ public BeanWrapper instantiateUsingFactoryMethod(
506506
// and explicitly ignore overridden methods (with the same parameter signature).
507507
else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight &&
508508
!mbd.isLenientConstructorResolution() &&
509-
paramTypes.length == factoryMethodToUse.getParameterTypes().length &&
509+
paramTypes.length == factoryMethodToUse.getParameterCount() &&
510510
!Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {
511511
if (ambiguousFactoryMethods == null) {
512512
ambiguousFactoryMethods = new LinkedHashSet<>();
@@ -662,7 +662,7 @@ private int resolveConstructorArguments(String beanName, RootBeanDefinition mbd,
662662
*/
663663
private ArgumentsHolder createArgumentArray(
664664
String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues,
665-
BeanWrapper bw, Class<?>[] paramTypes, String[] paramNames, Object methodOrCtor,
665+
BeanWrapper bw, Class<?>[] paramTypes, String[] paramNames, Executable executable,
666666
boolean autowiring) throws UnsatisfiedDependencyException {
667667

668668
TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?
@@ -699,7 +699,7 @@ private ArgumentsHolder createArgumentArray(
699699
ConstructorArgumentValues.ValueHolder sourceHolder =
700700
(ConstructorArgumentValues.ValueHolder) valueHolder.getSource();
701701
Object sourceValue = sourceHolder.getValue();
702-
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
702+
MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
703703
try {
704704
convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam);
705705
// TODO re-enable once race condition has been found (SPR-7423)
@@ -727,7 +727,7 @@ private ArgumentsHolder createArgumentArray(
727727
args.rawArguments[paramIndex] = originalValue;
728728
}
729729
else {
730-
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
730+
MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
731731
// No explicit match found: we're either supposed to autowire or
732732
// have to fail creating an argument array for the given constructor.
733733
if (!autowiring) {
@@ -755,7 +755,7 @@ private ArgumentsHolder createArgumentArray(
755755
this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
756756
if (this.beanFactory.logger.isDebugEnabled()) {
757757
this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName +
758-
"' via " + (methodOrCtor instanceof Constructor ? "constructor" : "factory method") +
758+
"' via " + (executable instanceof Constructor ? "constructor" : "factory method") +
759759
" to bean named '" + autowiredBeanName + "'");
760760
}
761761
}
@@ -767,19 +767,18 @@ private ArgumentsHolder createArgumentArray(
767767
* Resolve the prepared arguments stored in the given bean definition.
768768
*/
769769
private Object[] resolvePreparedArguments(
770-
String beanName, RootBeanDefinition mbd, BeanWrapper bw, Member methodOrCtor, Object[] argsToResolve) {
770+
String beanName, RootBeanDefinition mbd, BeanWrapper bw, Executable executable, Object[] argsToResolve) {
771771

772-
Class<?>[] paramTypes = (methodOrCtor instanceof Method ?
773-
((Method) methodOrCtor).getParameterTypes() : ((Constructor<?>) methodOrCtor).getParameterTypes());
772+
Class<?>[] paramTypes = executable.getParameterTypes();
774773
TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?
775774
this.beanFactory.getCustomTypeConverter() : bw);
776775
BeanDefinitionValueResolver valueResolver =
777776
new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter);
778777
Object[] resolvedArgs = new Object[argsToResolve.length];
779778
for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) {
780779
Object argValue = argsToResolve[argIndex];
781-
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, argIndex);
782-
GenericTypeResolver.resolveParameterType(methodParam, methodOrCtor.getDeclaringClass());
780+
MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
781+
GenericTypeResolver.resolveParameterType(methodParam, executable.getDeclaringClass());
783782
if (argValue instanceof AutowiredArgumentMarker) {
784783
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter);
785784
}

spring-core/src/main/java/org/springframework/core/MethodParameter.java

Lines changed: 87 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import java.lang.annotation.Annotation;
2020
import java.lang.reflect.AnnotatedElement;
2121
import java.lang.reflect.Constructor;
22+
import java.lang.reflect.Executable;
2223
import java.lang.reflect.Member;
2324
import java.lang.reflect.Method;
25+
import java.lang.reflect.Parameter;
2426
import java.lang.reflect.ParameterizedType;
2527
import java.lang.reflect.Type;
2628
import java.util.HashMap;
@@ -54,6 +56,8 @@ public class MethodParameter {
5456

5557
private final int parameterIndex;
5658

59+
private volatile Parameter parameter;
60+
5761
private int nestingLevel = 1;
5862

5963
/** Map from Integer level to Integer type index */
@@ -98,7 +102,7 @@ public MethodParameter(Method method, int parameterIndex) {
98102
public MethodParameter(Method method, int parameterIndex, int nestingLevel) {
99103
Assert.notNull(method, "Method must not be null");
100104
this.method = method;
101-
this.parameterIndex = parameterIndex;
105+
this.parameterIndex = validateIndex(method, parameterIndex);
102106
this.nestingLevel = nestingLevel;
103107
this.constructor = null;
104108
}
@@ -123,7 +127,7 @@ public MethodParameter(Constructor<?> constructor, int parameterIndex) {
123127
public MethodParameter(Constructor<?> constructor, int parameterIndex, int nestingLevel) {
124128
Assert.notNull(constructor, "Constructor must not be null");
125129
this.constructor = constructor;
126-
this.parameterIndex = parameterIndex;
130+
this.parameterIndex = validateIndex(constructor, parameterIndex);
127131
this.nestingLevel = nestingLevel;
128132
this.method = null;
129133
}
@@ -138,6 +142,7 @@ public MethodParameter(MethodParameter original) {
138142
this.method = original.method;
139143
this.constructor = original.constructor;
140144
this.parameterIndex = original.parameterIndex;
145+
this.parameter = original.parameter;
141146
this.nestingLevel = original.nestingLevel;
142147
this.typeIndexesPerLevel = original.typeIndexesPerLevel;
143148
this.containingClass = original.containingClass;
@@ -179,15 +184,7 @@ public Class<?> getDeclaringClass() {
179184
* @return the Method or Constructor as Member
180185
*/
181186
public Member getMember() {
182-
// NOTE: no ternary expression to retain JDK <8 compatibility even when using
183-
// the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
184-
// as common type, with that new base class not available on older JDKs)
185-
if (this.method != null) {
186-
return this.method;
187-
}
188-
else {
189-
return this.constructor;
190-
}
187+
return getExecutable();
191188
}
192189

193190
/**
@@ -197,15 +194,27 @@ public Member getMember() {
197194
* @return the Method or Constructor as AnnotatedElement
198195
*/
199196
public AnnotatedElement getAnnotatedElement() {
200-
// NOTE: no ternary expression to retain JDK <8 compatibility even when using
201-
// the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
202-
// as common type, with that new base class not available on older JDKs)
203-
if (this.method != null) {
204-
return this.method;
205-
}
206-
else {
207-
return this.constructor;
197+
return getExecutable();
198+
}
199+
200+
/**
201+
* Return the wrapped executable.
202+
* @return the Method or Constructor as Executable
203+
* @since 5.0
204+
*/
205+
public Executable getExecutable() {
206+
return (this.method != null ? this.method : this.constructor);
207+
}
208+
209+
/**
210+
* Return the {@link Parameter} descriptor for method/constructor parameter.
211+
* @since 5.0
212+
*/
213+
public Parameter getParameter() {
214+
if (this.parameter == null) {
215+
this.parameter = getExecutable().getParameters()[this.parameterIndex];
208216
}
217+
return this.parameter;
209218
}
210219

211220
/**
@@ -570,11 +579,11 @@ public boolean equals(Object other) {
570579
if (this == other) {
571580
return true;
572581
}
573-
if (!(other instanceof MethodParameter)) {
582+
if (other == null || getClass() != other.getClass()) {
574583
return false;
575584
}
576585
MethodParameter otherParam = (MethodParameter) other;
577-
return (this.parameterIndex == otherParam.parameterIndex && getMember().equals(otherParam.getMember()));
586+
return (this.parameterIndex == otherParam.parameterIndex && getExecutable().equals(otherParam.getExecutable()));
578587
}
579588

580589
@Override
@@ -596,23 +605,73 @@ public MethodParameter clone() {
596605

597606
/**
598607
* Create a new MethodParameter for the given method or constructor.
599-
* <p>This is a convenience constructor for scenarios where a
608+
* <p>This is a convenience factory method for scenarios where a
600609
* Method or Constructor reference is treated in a generic fashion.
601610
* @param methodOrConstructor the Method or Constructor to specify a parameter for
602611
* @param parameterIndex the index of the parameter
603612
* @return the corresponding MethodParameter instance
613+
* @deprecated as of 5.0, in favor of {@link #forExecutable}
604614
*/
615+
@Deprecated
605616
public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) {
606-
if (methodOrConstructor instanceof Method) {
607-
return new MethodParameter((Method) methodOrConstructor, parameterIndex);
617+
if (!(methodOrConstructor instanceof Executable)) {
618+
throw new IllegalArgumentException(
619+
"Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor");
608620
}
609-
else if (methodOrConstructor instanceof Constructor) {
610-
return new MethodParameter((Constructor<?>) methodOrConstructor, parameterIndex);
621+
return forExecutable((Executable) methodOrConstructor, parameterIndex);
622+
}
623+
624+
/**
625+
* Create a new MethodParameter for the given method or constructor.
626+
* <p>This is a convenience factory method for scenarios where a
627+
* Method or Constructor reference is treated in a generic fashion.
628+
* @param executable the Method or Constructor to specify a parameter for
629+
* @param parameterIndex the index of the parameter
630+
* @return the corresponding MethodParameter instance
631+
* @since 5.0
632+
*/
633+
public static MethodParameter forExecutable(Executable executable, int parameterIndex) {
634+
if (executable instanceof Method) {
635+
return new MethodParameter((Method) executable, parameterIndex);
636+
}
637+
else if (executable instanceof Constructor) {
638+
return new MethodParameter((Constructor<?>) executable, parameterIndex);
611639
}
612640
else {
613-
throw new IllegalArgumentException(
614-
"Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor");
641+
throw new IllegalArgumentException("Not a Method/Constructor: " + executable);
642+
}
643+
}
644+
645+
/**
646+
* Create a new MethodParameter for the given parameter descriptor.
647+
* <p>This is a convenience factory method for scenarios where a
648+
* Java 8 {@link Parameter} descriptor is already available.
649+
* @param parameter the parameter descriptor
650+
* @return the corresponding MethodParameter instance
651+
* @since 5.0
652+
*/
653+
public static MethodParameter forParameter(Parameter parameter) {
654+
return forExecutable(parameter.getDeclaringExecutable(), findParameterIndex(parameter));
655+
}
656+
657+
protected static int findParameterIndex(Parameter parameter) {
658+
Executable executable = parameter.getDeclaringExecutable();
659+
Parameter[] allParams = executable.getParameters();
660+
for (int i = 0; i < allParams.length; i++) {
661+
if (parameter == allParams[i]) {
662+
return i;
663+
}
664+
}
665+
throw new IllegalArgumentException("Given parameter [" + parameter +
666+
"] does not match any parameter in the declaring executable");
667+
}
668+
669+
private static int validateIndex(Executable executable, int parameterIndex) {
670+
int count = executable.getParameterCount();
671+
if (parameterIndex >= count) {
672+
throw new IllegalArgumentException("Parameter index needs to be between 0 and " + (count - 1));
615673
}
674+
return parameterIndex;
616675
}
617676

618677
}

spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import java.lang.annotation.Annotation;
2020
import java.lang.reflect.Constructor;
21+
import java.lang.reflect.Executable;
2122
import java.lang.reflect.Method;
23+
import java.lang.reflect.Parameter;
2224

2325
import org.springframework.core.MethodParameter;
2426

@@ -108,4 +110,38 @@ public SynthesizingMethodParameter clone() {
108110
return new SynthesizingMethodParameter(this);
109111
}
110112

113+
114+
/**
115+
* Create a new SynthesizingMethodParameter for the given method or constructor.
116+
* <p>This is a convenience factory method for scenarios where a
117+
* Method or Constructor reference is treated in a generic fashion.
118+
* @param executable the Method or Constructor to specify a parameter for
119+
* @param parameterIndex the index of the parameter
120+
* @return the corresponding SynthesizingMethodParameter instance
121+
* @since 5.0
122+
*/
123+
public static SynthesizingMethodParameter forExecutable(Executable executable, int parameterIndex) {
124+
if (executable instanceof Method) {
125+
return new SynthesizingMethodParameter((Method) executable, parameterIndex);
126+
}
127+
else if (executable instanceof Constructor) {
128+
return new SynthesizingMethodParameter((Constructor<?>) executable, parameterIndex);
129+
}
130+
else {
131+
throw new IllegalArgumentException("Not a Method/Constructor: " + executable);
132+
}
133+
}
134+
135+
/**
136+
* Create a new SynthesizingMethodParameter for the given parameter descriptor.
137+
* <p>This is a convenience factory method for scenarios where a
138+
* Java 8 {@link Parameter} descriptor is already available.
139+
* @param parameter the parameter descriptor
140+
* @return the corresponding SynthesizingMethodParameter instance
141+
* @since 5.0
142+
*/
143+
public static SynthesizingMethodParameter forParameter(Parameter parameter) {
144+
return forExecutable(parameter.getDeclaringExecutable(), findParameterIndex(parameter));
145+
}
146+
111147
}

spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ public void getRawMapTypeCannotBeResolved() throws Exception {
158158
@Test
159159
public void getGenericsOnArrayFromParamCannotBeResolved() throws Exception {
160160
// SPR-11044
161-
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(
161+
MethodParameter methodParameter = MethodParameter.forExecutable(
162162
WithArrayBase.class.getDeclaredMethod("array", Object[].class), 0);
163163
Class<?> resolved = GenericTypeResolver.resolveParameterType(methodParameter, WithArray.class);
164164
assertThat(resolved, equalTo((Class<?>) Object[].class));

0 commit comments

Comments
 (0)