diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/ParameterResolutionDelegate.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/ParameterResolutionDelegate.java index 4f4dc58622d..31635d5a5f1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/ParameterResolutionDelegate.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/ParameterResolutionDelegate.java @@ -89,6 +89,31 @@ public final class ParameterResolutionDelegate { AnnotatedElementUtils.hasAnnotation(annotatedParameter, Value.class)); } + /** + * Resolve the dependency for the supplied {@link Parameter} from the + * supplied {@link AutowireCapableBeanFactory}. + *
See {@link #resolveDependency(Parameter, int, String, Class, AutowireCapableBeanFactory)} + * for details. + * @param parameter the parameter whose dependency should be resolved (must not be + * {@code null}) + * @param parameterIndex the index of the parameter in the constructor or method + * that declares the parameter + * @param containingClass the concrete class that contains the parameter; this may + * differ from the class that declares the parameter in that it may be a subclass + * thereof, potentially substituting type variables (must not be {@code null}) + * @param beanFactory the {@code AutowireCapableBeanFactory} from which to resolve + * the dependency (must not be {@code null}) + * @return the resolved object, or {@code null} if none found + * @throws BeansException if dependency resolution failed + * @see #resolveDependency(Parameter, int, String, Class, AutowireCapableBeanFactory) + */ + public static @Nullable Object resolveDependency( + Parameter parameter, int parameterIndex, Class> containingClass, AutowireCapableBeanFactory beanFactory) + throws BeansException { + + return resolveDependency(parameter, parameterIndex, null, containingClass, beanFactory); + } + /** * Resolve the dependency for the supplied {@link Parameter} from the * supplied {@link AutowireCapableBeanFactory}. @@ -101,11 +126,13 @@ public final class ParameterResolutionDelegate { * with {@link Autowired @Autowired} with the {@link Autowired#required required} * flag set to {@code false}. *
If an explicit qualifier is not declared, the name of the parameter - * will be used as the qualifier for resolving ambiguities. + * (or a supplied custom name) will be used as the qualifier for resolving ambiguities. * @param parameter the parameter whose dependency should be resolved (must not be * {@code null}) * @param parameterIndex the index of the parameter in the constructor or method * that declares the parameter + * @param parameterName a custom name for the parameter; or {@code null} to use + * the default parameter name discovery logic * @param containingClass the concrete class that contains the parameter; this may * differ from the class that declares the parameter in that it may be a subclass * thereof, potentially substituting type variables (must not be {@code null}) @@ -113,13 +140,14 @@ public final class ParameterResolutionDelegate { * the dependency (must not be {@code null}) * @return the resolved object, or {@code null} if none found * @throws BeansException if dependency resolution failed + * @since 7.1 * @see #isAutowirable * @see Autowired#required * @see SynthesizingMethodParameter#forExecutable(Executable, int) * @see AutowireCapableBeanFactory#resolveDependency(DependencyDescriptor, String) */ - public static @Nullable Object resolveDependency( - Parameter parameter, int parameterIndex, Class> containingClass, AutowireCapableBeanFactory beanFactory) + public static @Nullable Object resolveDependency(Parameter parameter, int parameterIndex, + @Nullable String parameterName, Class> containingClass, AutowireCapableBeanFactory beanFactory) throws BeansException { Assert.notNull(parameter, "Parameter must not be null"); @@ -132,7 +160,7 @@ public final class ParameterResolutionDelegate { MethodParameter methodParameter = SynthesizingMethodParameter.forExecutable( parameter.getDeclaringExecutable(), parameterIndex); - DependencyDescriptor descriptor = new DependencyDescriptor(methodParameter, required); + DependencyDescriptor descriptor = new NamedParameterDependencyDescriptor(methodParameter, required, parameterName); descriptor.setContainingClass(containingClass); return beanFactory.resolveDependency(descriptor, null); } @@ -171,4 +199,26 @@ public final class ParameterResolutionDelegate { return parameter; } + + @SuppressWarnings("serial") + private static class NamedParameterDependencyDescriptor extends DependencyDescriptor { + + private final @Nullable String parameterName; + + NamedParameterDependencyDescriptor(MethodParameter methodParameter, boolean required, @Nullable String parameterName) { + super(methodParameter, required); + this.parameterName = parameterName; + } + + @Override + public @Nullable String getDependencyName() { + return (this.parameterName != null ? this.parameterName : super.getDependencyName()); + } + + @Override + public boolean usesStandardBeanLookup() { + return true; + } + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/ParameterResolutionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/ParameterResolutionTests.java index 3ec16e2fef1..08ccff1b759 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/ParameterResolutionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/ParameterResolutionTests.java @@ -134,9 +134,64 @@ class ParameterResolutionTests { parameter, parameterIndex, AutowirableClass.class, beanFactory); assertThat(intermediateDependencyDescriptor.getAnnotatedElement()).isEqualTo(constructor); assertThat(intermediateDependencyDescriptor.getMethodParameter().getParameter()).isEqualTo(parameter); + assertThat(intermediateDependencyDescriptor.usesStandardBeanLookup()).isTrue(); } } + @Test + void resolveDependencyWithCustomParameterNamePreconditionsForParameter() { + assertThatIllegalArgumentException() + .isThrownBy(() -> ParameterResolutionDelegate.resolveDependency(null, 0, "customName", getClass(), mock())) + .withMessageContaining("Parameter must not be null"); + } + + @Test + void resolveDependencyWithCustomParameterNamePreconditionsForContainingClass() { + assertThatIllegalArgumentException() + .isThrownBy(() -> ParameterResolutionDelegate.resolveDependency(getParameter(), 0, "customName", null, mock())) + .withMessageContaining("Containing class must not be null"); + } + + @Test + void resolveDependencyWithCustomParameterNamePreconditionsForBeanFactory() { + assertThatIllegalArgumentException() + .isThrownBy(() -> ParameterResolutionDelegate.resolveDependency(getParameter(), 0, "customName", getClass(), null)) + .withMessageContaining("AutowireCapableBeanFactory must not be null"); + } + + @Test + void resolveDependencyWithNullCustomParameterNameFallsBackToDefaultParameterNameDiscovery() throws Exception { + Constructor> constructor = AutowirableClass.class.getConstructor(String.class, String.class, String.class, String.class); + AutowireCapableBeanFactory beanFactory = mock(); + given(beanFactory.resolveDependency(any(), isNull())).willAnswer(invocation -> invocation.getArgument(0)); + + Parameter[] parameters = constructor.getParameters(); + for (int parameterIndex = 0; parameterIndex < parameters.length; parameterIndex++) { + Parameter parameter = parameters[parameterIndex]; + DependencyDescriptor via4ArgMethod = (DependencyDescriptor) ParameterResolutionDelegate.resolveDependency( + parameter, parameterIndex, AutowirableClass.class, beanFactory); + DependencyDescriptor via5ArgMethod = (DependencyDescriptor) ParameterResolutionDelegate.resolveDependency( + parameter, parameterIndex, null, AutowirableClass.class, beanFactory); + assertThat(via5ArgMethod.getDependencyName()).isEqualTo(via4ArgMethod.getDependencyName()); + } + } + + @Test + void resolveDependencyWithCustomParameterName() throws Exception { + Constructor> constructor = AutowirableClass.class.getConstructor(String.class, String.class, String.class, String.class); + AutowireCapableBeanFactory beanFactory = mock(); + given(beanFactory.resolveDependency(any(), isNull())).willAnswer(invocation -> invocation.getArgument(0)); + + Parameter parameter = constructor.getParameters()[0]; + DependencyDescriptor descriptor = (DependencyDescriptor) ParameterResolutionDelegate.resolveDependency( + parameter, 0, "customBeanName", AutowirableClass.class, beanFactory); + + assertThat(descriptor.getAnnotatedElement()).isEqualTo(constructor); + assertThat(descriptor.getMethodParameter().getParameter()).isEqualTo(parameter); + assertThat(descriptor.getDependencyName()).isEqualTo("customBeanName"); + assertThat(descriptor.usesStandardBeanLookup()).isTrue(); + } + void autowirableMethod( @Autowired String firstParameter,