Browse Source

Introduce support for custom parameter names in ParameterResolutionDelegate

The resolveDependency() utility method in ParameterResolutionDelegate
resolves a dependency using the name of the parameter as a fallback
qualifier. That suffices for most use cases; however, there are times
when a custom parameter name should be used instead.

For example, for our Bean Override support in the Spring TestContext
Framework, an annotation such as @⁠MockitoBean("myBean") specifies an
explicit name that should be used instead of name of the annotated
parameter.

Furthermore, introducing support for custom parameter names will
greatly simplify the logic in SpringExtension that will be required to
implement #36096.

To address those issues, this commit introduces an overloaded variant
of resolveDependency() which accepts a custom parameter name.
Internally, a custom DependencyDescriptor has been implemented to
transparently support this use case.

See gh-36096
Closes gh-36534
pull/36543/head
Sam Brannen 6 days ago
parent
commit
3d70074089
  1. 58
      spring-beans/src/main/java/org/springframework/beans/factory/annotation/ParameterResolutionDelegate.java
  2. 55
      spring-beans/src/test/java/org/springframework/beans/factory/annotation/ParameterResolutionTests.java

58
spring-beans/src/main/java/org/springframework/beans/factory/annotation/ParameterResolutionDelegate.java

@ -89,6 +89,31 @@ public final class ParameterResolutionDelegate { @@ -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}.
* <p>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 { @@ -101,11 +126,13 @@ public final class ParameterResolutionDelegate {
* with {@link Autowired @Autowired} with the {@link Autowired#required required}
* flag set to {@code false}.
* <p>If an explicit <em>qualifier</em> 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 { @@ -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 { @@ -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 { @@ -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;
}
}
}

55
spring-beans/src/test/java/org/springframework/beans/factory/annotation/ParameterResolutionTests.java

@ -134,9 +134,64 @@ class ParameterResolutionTests { @@ -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,

Loading…
Cancel
Save