diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 9d1455c814..d2e486c86d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -18,7 +18,7 @@ package org.springframework.beans.factory.support; import java.beans.ConstructorProperties; import java.lang.reflect.Constructor; -import java.lang.reflect.Member; +import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.AccessController; @@ -506,7 +506,7 @@ class ConstructorResolver { // and explicitly ignore overridden methods (with the same parameter signature). else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight && !mbd.isLenientConstructorResolution() && - paramTypes.length == factoryMethodToUse.getParameterTypes().length && + paramTypes.length == factoryMethodToUse.getParameterCount() && !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) { if (ambiguousFactoryMethods == null) { ambiguousFactoryMethods = new LinkedHashSet<>(); @@ -662,7 +662,7 @@ class ConstructorResolver { */ private ArgumentsHolder createArgumentArray( String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues, - BeanWrapper bw, Class>[] paramTypes, String[] paramNames, Object methodOrCtor, + BeanWrapper bw, Class>[] paramTypes, String[] paramNames, Executable executable, boolean autowiring) throws UnsatisfiedDependencyException { TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? @@ -699,7 +699,7 @@ class ConstructorResolver { ConstructorArgumentValues.ValueHolder sourceHolder = (ConstructorArgumentValues.ValueHolder) valueHolder.getSource(); Object sourceValue = sourceHolder.getValue(); - MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex); + MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); try { convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam); // TODO re-enable once race condition has been found (SPR-7423) @@ -727,7 +727,7 @@ class ConstructorResolver { args.rawArguments[paramIndex] = originalValue; } else { - MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex); + MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); // No explicit match found: we're either supposed to autowire or // have to fail creating an argument array for the given constructor. if (!autowiring) { @@ -755,7 +755,7 @@ class ConstructorResolver { this.beanFactory.registerDependentBean(autowiredBeanName, beanName); if (this.beanFactory.logger.isDebugEnabled()) { this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName + - "' via " + (methodOrCtor instanceof Constructor ? "constructor" : "factory method") + + "' via " + (executable instanceof Constructor ? "constructor" : "factory method") + " to bean named '" + autowiredBeanName + "'"); } } @@ -767,10 +767,9 @@ class ConstructorResolver { * Resolve the prepared arguments stored in the given bean definition. */ private Object[] resolvePreparedArguments( - String beanName, RootBeanDefinition mbd, BeanWrapper bw, Member methodOrCtor, Object[] argsToResolve) { + String beanName, RootBeanDefinition mbd, BeanWrapper bw, Executable executable, Object[] argsToResolve) { - Class>[] paramTypes = (methodOrCtor instanceof Method ? - ((Method) methodOrCtor).getParameterTypes() : ((Constructor>) methodOrCtor).getParameterTypes()); + Class>[] paramTypes = executable.getParameterTypes(); TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? this.beanFactory.getCustomTypeConverter() : bw); BeanDefinitionValueResolver valueResolver = @@ -778,8 +777,8 @@ class ConstructorResolver { Object[] resolvedArgs = new Object[argsToResolve.length]; for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) { Object argValue = argsToResolve[argIndex]; - MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, argIndex); - GenericTypeResolver.resolveParameterType(methodParam, methodOrCtor.getDeclaringClass()); + MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex); + GenericTypeResolver.resolveParameterType(methodParam, executable.getDeclaringClass()); if (argValue instanceof AutowiredArgumentMarker) { argValue = resolveAutowiredArgument(methodParam, beanName, null, converter); } diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index 223a48ff4a..150f8cfeb8 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -19,8 +19,10 @@ package org.springframework.core; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashMap; @@ -54,6 +56,8 @@ public class MethodParameter { private final int parameterIndex; + private volatile Parameter parameter; + private int nestingLevel = 1; /** Map from Integer level to Integer type index */ @@ -98,7 +102,7 @@ public class MethodParameter { public MethodParameter(Method method, int parameterIndex, int nestingLevel) { Assert.notNull(method, "Method must not be null"); this.method = method; - this.parameterIndex = parameterIndex; + this.parameterIndex = validateIndex(method, parameterIndex); this.nestingLevel = nestingLevel; this.constructor = null; } @@ -123,7 +127,7 @@ public class MethodParameter { public MethodParameter(Constructor> constructor, int parameterIndex, int nestingLevel) { Assert.notNull(constructor, "Constructor must not be null"); this.constructor = constructor; - this.parameterIndex = parameterIndex; + this.parameterIndex = validateIndex(constructor, parameterIndex); this.nestingLevel = nestingLevel; this.method = null; } @@ -138,6 +142,7 @@ public class MethodParameter { this.method = original.method; this.constructor = original.constructor; this.parameterIndex = original.parameterIndex; + this.parameter = original.parameter; this.nestingLevel = original.nestingLevel; this.typeIndexesPerLevel = original.typeIndexesPerLevel; this.containingClass = original.containingClass; @@ -179,15 +184,7 @@ public class MethodParameter { * @return the Method or Constructor as Member */ public Member getMember() { - // NOTE: no ternary expression to retain JDK <8 compatibility even when using - // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable - // as common type, with that new base class not available on older JDKs) - if (this.method != null) { - return this.method; - } - else { - return this.constructor; - } + return getExecutable(); } /** @@ -197,15 +194,27 @@ public class MethodParameter { * @return the Method or Constructor as AnnotatedElement */ public AnnotatedElement getAnnotatedElement() { - // NOTE: no ternary expression to retain JDK <8 compatibility even when using - // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable - // as common type, with that new base class not available on older JDKs) - if (this.method != null) { - return this.method; - } - else { - return this.constructor; + return getExecutable(); + } + + /** + * Return the wrapped executable. + * @return the Method or Constructor as Executable + * @since 5.0 + */ + public Executable getExecutable() { + return (this.method != null ? this.method : this.constructor); + } + + /** + * Return the {@link Parameter} descriptor for method/constructor parameter. + * @since 5.0 + */ + public Parameter getParameter() { + if (this.parameter == null) { + this.parameter = getExecutable().getParameters()[this.parameterIndex]; } + return this.parameter; } /** @@ -570,11 +579,11 @@ public class MethodParameter { if (this == other) { return true; } - if (!(other instanceof MethodParameter)) { + if (other == null || getClass() != other.getClass()) { return false; } MethodParameter otherParam = (MethodParameter) other; - return (this.parameterIndex == otherParam.parameterIndex && getMember().equals(otherParam.getMember())); + return (this.parameterIndex == otherParam.parameterIndex && getExecutable().equals(otherParam.getExecutable())); } @Override @@ -596,23 +605,73 @@ public class MethodParameter { /** * Create a new MethodParameter for the given method or constructor. - *
This is a convenience constructor for scenarios where a + *
This is a convenience factory method for scenarios where a * Method or Constructor reference is treated in a generic fashion. * @param methodOrConstructor the Method or Constructor to specify a parameter for * @param parameterIndex the index of the parameter * @return the corresponding MethodParameter instance + * @deprecated as of 5.0, in favor of {@link #forExecutable} */ + @Deprecated public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) { - if (methodOrConstructor instanceof Method) { - return new MethodParameter((Method) methodOrConstructor, parameterIndex); - } - else if (methodOrConstructor instanceof Constructor) { - return new MethodParameter((Constructor>) methodOrConstructor, parameterIndex); - } - else { + if (!(methodOrConstructor instanceof Executable)) { throw new IllegalArgumentException( "Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor"); } + return forExecutable((Executable) methodOrConstructor, parameterIndex); + } + + /** + * Create a new MethodParameter for the given method or constructor. + *
This is a convenience factory method for scenarios where a + * Method or Constructor reference is treated in a generic fashion. + * @param executable the Method or Constructor to specify a parameter for + * @param parameterIndex the index of the parameter + * @return the corresponding MethodParameter instance + * @since 5.0 + */ + public static MethodParameter forExecutable(Executable executable, int parameterIndex) { + if (executable instanceof Method) { + return new MethodParameter((Method) executable, parameterIndex); + } + else if (executable instanceof Constructor) { + return new MethodParameter((Constructor>) executable, parameterIndex); + } + else { + throw new IllegalArgumentException("Not a Method/Constructor: " + executable); + } + } + + /** + * Create a new MethodParameter for the given parameter descriptor. + *
This is a convenience factory method for scenarios where a + * Java 8 {@link Parameter} descriptor is already available. + * @param parameter the parameter descriptor + * @return the corresponding MethodParameter instance + * @since 5.0 + */ + public static MethodParameter forParameter(Parameter parameter) { + return forExecutable(parameter.getDeclaringExecutable(), findParameterIndex(parameter)); + } + + protected static int findParameterIndex(Parameter parameter) { + Executable executable = parameter.getDeclaringExecutable(); + Parameter[] allParams = executable.getParameters(); + for (int i = 0; i < allParams.length; i++) { + if (parameter == allParams[i]) { + return i; + } + } + throw new IllegalArgumentException("Given parameter [" + parameter + + "] does not match any parameter in the declaring executable"); + } + + private static int validateIndex(Executable executable, int parameterIndex) { + int count = executable.getParameterCount(); + if (parameterIndex >= count) { + throw new IllegalArgumentException("Parameter index needs to be between 0 and " + (count - 1)); + } + return parameterIndex; } } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java index eb287c478d..9f76491246 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java @@ -18,7 +18,9 @@ package org.springframework.core.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import org.springframework.core.MethodParameter; @@ -108,4 +110,38 @@ public class SynthesizingMethodParameter extends MethodParameter { return new SynthesizingMethodParameter(this); } + + /** + * Create a new SynthesizingMethodParameter for the given method or constructor. + *
This is a convenience factory method for scenarios where a + * Method or Constructor reference is treated in a generic fashion. + * @param executable the Method or Constructor to specify a parameter for + * @param parameterIndex the index of the parameter + * @return the corresponding SynthesizingMethodParameter instance + * @since 5.0 + */ + public static SynthesizingMethodParameter forExecutable(Executable executable, int parameterIndex) { + if (executable instanceof Method) { + return new SynthesizingMethodParameter((Method) executable, parameterIndex); + } + else if (executable instanceof Constructor) { + return new SynthesizingMethodParameter((Constructor>) executable, parameterIndex); + } + else { + throw new IllegalArgumentException("Not a Method/Constructor: " + executable); + } + } + + /** + * Create a new SynthesizingMethodParameter for the given parameter descriptor. + *
This is a convenience factory method for scenarios where a
+ * Java 8 {@link Parameter} descriptor is already available.
+ * @param parameter the parameter descriptor
+ * @return the corresponding SynthesizingMethodParameter instance
+ * @since 5.0
+ */
+ public static SynthesizingMethodParameter forParameter(Parameter parameter) {
+ return forExecutable(parameter.getDeclaringExecutable(), findParameterIndex(parameter));
+ }
+
}
diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java
index 78e5eb8487..364bffb623 100644
--- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java
+++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java
@@ -158,7 +158,7 @@ public class GenericTypeResolverTests {
@Test
public void getGenericsOnArrayFromParamCannotBeResolved() throws Exception {
// SPR-11044
- MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(
+ MethodParameter methodParameter = MethodParameter.forExecutable(
WithArrayBase.class.getDeclaredMethod("array", Object[].class), 0);
Class> resolved = GenericTypeResolver.resolveParameterType(methodParameter, WithArray.class);
assertThat(resolved, equalTo((Class>) Object[].class));
diff --git a/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java b/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java
index c3c3e7d139..6d5dc18f27 100644
--- a/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java
+++ b/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -25,9 +25,12 @@ import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
+ * @author Juergen Hoeller
*/
public class MethodParameterTests {
+ private Method method;
+
private MethodParameter stringParameter;
private MethodParameter longParameter;
@@ -37,12 +40,13 @@ public class MethodParameterTests {
@Before
public void setUp() throws NoSuchMethodException {
- Method method = getClass().getMethod("method", String.class, Long.TYPE);
+ method = getClass().getMethod("method", String.class, Long.TYPE);
stringParameter = new MethodParameter(method, 0);
longParameter = new MethodParameter(method, 1);
intReturnType = new MethodParameter(method, -1);
}
+
@Test
public void testEquals() throws NoSuchMethodException {
assertEquals(stringParameter, stringParameter);
@@ -60,8 +64,8 @@ public class MethodParameterTests {
MethodParameter methodParameter = new MethodParameter(method, 0);
assertEquals(stringParameter, methodParameter);
assertEquals(methodParameter, stringParameter);
- assertFalse(longParameter.equals(methodParameter));
- assertFalse(methodParameter.equals(longParameter));
+ assertNotEquals(longParameter, methodParameter);
+ assertNotEquals(methodParameter, longParameter);
}
@Test
@@ -73,7 +77,25 @@ public class MethodParameterTests {
Method method = getClass().getMethod("method", String.class, Long.TYPE);
MethodParameter methodParameter = new MethodParameter(method, 0);
assertEquals(stringParameter.hashCode(), methodParameter.hashCode());
- assertTrue(longParameter.hashCode() != methodParameter.hashCode());
+ assertNotEquals(longParameter.hashCode(), methodParameter.hashCode());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testFactoryMethods() {
+ assertEquals(stringParameter, MethodParameter.forMethodOrConstructor(method, 0));
+ assertEquals(longParameter, MethodParameter.forMethodOrConstructor(method, 1));
+
+ assertEquals(stringParameter, MethodParameter.forExecutable(method, 0));
+ assertEquals(longParameter, MethodParameter.forExecutable(method, 1));
+
+ assertEquals(stringParameter, MethodParameter.forParameter(method.getParameters()[0]));
+ assertEquals(longParameter, MethodParameter.forParameter(method.getParameters()[1]));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIndexValidation() {
+ new MethodParameter(method, 2);
}
diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java
index f84d523e36..3dfaeee065 100644
--- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java
+++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java
@@ -214,7 +214,7 @@ public class ResolvableTypeTests {
@Test
public void forMethodParameter() throws Exception {
Method method = Methods.class.getMethod("charSequenceParameter", List.class);
- MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
assertThat(type.getType(), equalTo(method.getGenericParameterTypes()[0]));
}
@@ -222,7 +222,7 @@ public class ResolvableTypeTests {
@Test
public void forMethodParameterWithNesting() throws Exception {
Method method = Methods.class.getMethod("nested", Map.class);
- MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
methodParameter.increaseNestingLevel();
ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
assertThat(type.resolve(), equalTo((Class) Map.class));
@@ -233,7 +233,7 @@ public class ResolvableTypeTests {
@Test
public void forMethodParameterWithNestingAndLevels() throws Exception {
Method method = Methods.class.getMethod("nested", Map.class);
- MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
methodParameter.increaseNestingLevel();
methodParameter.setTypeIndexForCurrentLevel(0);
ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
@@ -782,7 +782,7 @@ public class ResolvableTypeTests {
@Test
public void resolveTypeVariableFromMethodParameterType() throws Exception {
Method method = Methods.class.getMethod("typedParameter", Object.class);
- MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
assertThat(type.resolve(), nullValue());
assertThat(type.getType().toString(), equalTo("T"));
@@ -791,7 +791,7 @@ public class ResolvableTypeTests {
@Test
public void resolveTypeVariableFromMethodParameterTypeWithImplementsClass() throws Exception {
Method method = Methods.class.getMethod("typedParameter", Object.class);
- MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
methodParameter.setContainingClass(TypedMethods.class);
ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
assertThat(type.resolve(), equalTo((Class) String.class));
@@ -801,7 +801,7 @@ public class ResolvableTypeTests {
@Test
public void resolveTypeVariableFromMethodParameterTypeWithImplementsType() throws Exception {
Method method = Methods.class.getMethod("typedParameter", Object.class);
- MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
ResolvableType implementationType = ResolvableType.forClassWithGenerics(Methods.class, Integer.class);
ResolvableType type = ResolvableType.forMethodParameter(methodParameter, implementationType);
assertThat(type.resolve(), equalTo((Class) Integer.class));
@@ -893,7 +893,7 @@ public class ResolvableTypeTests {
Field basicField = Fields.class.getField("classType");
Field field = Fields.class.getField("charSequenceList");
Method method = Methods.class.getMethod("charSequenceParameter", List.class);
- MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
assertThat(ResolvableType.forField(basicField).getSource(), equalTo((Object) basicField));
assertThat(ResolvableType.forField(field).getSource(), equalTo((Object) field));
assertThat(ResolvableType.forMethodParameter(methodParameter).getSource(), equalTo((Object) methodParameter));
diff --git a/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java b/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java
index ac68dacec6..a9853c3894 100644
--- a/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java
+++ b/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java
@@ -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");
* you may not use this file except in compliance with the License.
@@ -46,78 +46,78 @@ public class SerializableTypeWrapperTests {
public void forField() throws Exception {
Type type = SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType"));
assertThat(type.toString(), equalTo("java.util.List