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 new file mode 100644 index 0000000000000000000000000000000000000000..f815d3cbfd6780b2ef78da273a2b89b5293947a4 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/ParameterResolutionDelegate.java @@ -0,0 +1,171 @@ +/* + * Copyright 2002-2019 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Parameter; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.SynthesizingMethodParameter; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Public delegate for resolving autowirable parameters on externally managed + * constructors and methods. + * + * @author Sam Brannen + * @author Juergen Hoeller + * @since 5.2 + * @see #isAutowirable + * @see #resolveDependency + */ +public final class ParameterResolutionDelegate { + + private static final AnnotatedElement EMPTY_ANNOTATED_ELEMENT = new AnnotatedElement() { + @Override + @Nullable + public T getAnnotation(Class annotationClass) { + return null; + } + @Override + public Annotation[] getAnnotations() { + return new Annotation[0]; + } + @Override + public Annotation[] getDeclaredAnnotations() { + return new Annotation[0]; + } + }; + + + private ParameterResolutionDelegate() { + } + + + /** + * Determine if the supplied {@link Parameter} can potentially be + * autowired from an {@link AutowireCapableBeanFactory}. + *

Returns {@code true} if the supplied parameter is annotated or + * meta-annotated with {@link Autowired @Autowired}, + * {@link Qualifier @Qualifier}, or {@link Value @Value}. + *

Note that {@link #resolveDependency} may still be able to resolve the + * dependency for the supplied parameter even if this method returns {@code false}. + * @param parameter the parameter whose dependency should be autowired + * (must not be {@code null}) + * @param parameterIndex the index of the parameter in the constructor or method + * that declares the parameter + * @see #resolveDependency + */ + public static boolean isAutowirable(Parameter parameter, int parameterIndex) { + Assert.notNull(parameter, "Parameter must not be null"); + AnnotatedElement annotatedParameter = getEffectiveAnnotatedParameter(parameter, parameterIndex); + return (AnnotatedElementUtils.hasAnnotation(annotatedParameter, Autowired.class) || + AnnotatedElementUtils.hasAnnotation(annotatedParameter, Qualifier.class) || + AnnotatedElementUtils.hasAnnotation(annotatedParameter, Value.class)); + } + + /** + * Resolve the dependency for the supplied {@link Parameter} from the + * supplied {@link AutowireCapableBeanFactory}. + *

Provides comprehensive autowiring support for individual method parameters + * on par with Spring's dependency injection facilities for autowired fields and + * methods, including support for {@link Autowired @Autowired}, + * {@link Qualifier @Qualifier}, and {@link Value @Value} with support for property + * placeholders and SpEL expressions in {@code @Value} declarations. + *

The dependency is required unless the parameter is annotated or meta-annotated + * 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. + * @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 #isAutowirable + * @see Autowired#required + * @see SynthesizingMethodParameter#forExecutable(Executable, int) + * @see AutowireCapableBeanFactory#resolveDependency(DependencyDescriptor, String) + */ + @Nullable + public static Object resolveDependency( + Parameter parameter, int parameterIndex, Class containingClass, AutowireCapableBeanFactory beanFactory) + throws BeansException { + + Assert.notNull(parameter, "Parameter must not be null"); + Assert.notNull(containingClass, "Containing class must not be null"); + Assert.notNull(beanFactory, "AutowireCapableBeanFactory must not be null"); + + AnnotatedElement annotatedParameter = getEffectiveAnnotatedParameter(parameter, parameterIndex); + Autowired autowired = AnnotatedElementUtils.findMergedAnnotation(annotatedParameter, Autowired.class); + boolean required = (autowired == null || autowired.required()); + + MethodParameter methodParameter = SynthesizingMethodParameter.forExecutable( + parameter.getDeclaringExecutable(), parameterIndex); + DependencyDescriptor descriptor = new DependencyDescriptor(methodParameter, required); + descriptor.setContainingClass(containingClass); + return beanFactory.resolveDependency(descriptor, null); + } + + /** + * Due to a bug in {@code javac} on JDK versions prior to JDK 9, looking up + * annotations directly on a {@link Parameter} will fail for inner class + * constructors. + *

Bug in javac in JDK < 9

+ *

The parameter annotations array in the compiled byte code excludes an entry + * for the implicit enclosing instance parameter for an inner class + * constructor. + *

Workaround

+ *

This method provides a workaround for this off-by-one error by allowing the + * caller to access annotations on the preceding {@link Parameter} object (i.e., + * {@code index - 1}). If the supplied {@code index} is zero, this method returns + * an empty {@code AnnotatedElement}. + *

WARNING

+ *

The {@code AnnotatedElement} returned by this method should never be cast and + * treated as a {@code Parameter} since the metadata (e.g., {@link Parameter#getName()}, + * {@link Parameter#getType()}, etc.) will not match those for the declared parameter + * at the given index in an inner class constructor. + * @return the supplied {@code parameter} or the effective {@code Parameter} + * if the aforementioned bug is in effect + */ + private static AnnotatedElement getEffectiveAnnotatedParameter(Parameter parameter, int index) { + Executable executable = parameter.getDeclaringExecutable(); + if (executable instanceof Constructor && ClassUtils.isInnerClass(executable.getDeclaringClass()) && + executable.getParameterAnnotations().length == executable.getParameterCount() - 1) { + // Bug in javac in JDK <9: annotation array excludes enclosing instance parameter + // for inner classes, so access it with the actual parameter index lowered by 1 + return (index == 0 ? EMPTY_ANNOTATED_ELEMENT : executable.getParameters()[index - 1]); + } + return parameter; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java index 9ebc71cf621d9190ea7efab870b6e2822ca7f030..43e966c98e093d1def00bd1184954bc4a8383a48 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java @@ -18,15 +18,12 @@ package org.springframework.beans.factory.support; import java.beans.PropertyDescriptor; import java.io.Serializable; -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; import java.lang.reflect.Type; @@ -36,17 +33,8 @@ import java.util.Comparator; import java.util.Set; import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.TypedStringValue; -import org.springframework.core.MethodParameter; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -61,29 +49,13 @@ import org.springframework.util.ClassUtils; * @since 1.1.2 * @see AbstractAutowireCapableBeanFactory */ -public abstract class AutowireUtils { +abstract class AutowireUtils { private static final Comparator EXECUTABLE_COMPARATOR = (e1, e2) -> { int result = Boolean.compare(Modifier.isPublic(e2.getModifiers()), Modifier.isPublic(e1.getModifiers())); return result != 0 ? result : Integer.compare(e2.getParameterCount(), e1.getParameterCount()); }; - private static final AnnotatedElement EMPTY_ANNOTATED_ELEMENT = new AnnotatedElement() { - @Override - @Nullable - public T getAnnotation(Class annotationClass) { - return null; - } - @Override - public Annotation[] getAnnotations() { - return new Annotation[0]; - } - @Override - public Annotation[] getDeclaredAnnotations() { - return new Annotation[0]; - } - }; - /** * Sort the given constructors, preferring public constructors and "greedy" ones with @@ -92,7 +64,7 @@ public abstract class AutowireUtils { * decreasing number of arguments. * @param constructors the constructor array to sort */ - static void sortConstructors(Constructor[] constructors) { + public static void sortConstructors(Constructor[] constructors) { Arrays.sort(constructors, EXECUTABLE_COMPARATOR); } @@ -103,7 +75,7 @@ public abstract class AutowireUtils { * decreasing number of arguments. * @param factoryMethods the factory method array to sort */ - static void sortFactoryMethods(Method[] factoryMethods) { + public static void sortFactoryMethods(Method[] factoryMethods) { Arrays.sort(factoryMethods, EXECUTABLE_COMPARATOR); } @@ -113,7 +85,7 @@ public abstract class AutowireUtils { * @param pd the PropertyDescriptor of the bean property * @return whether the bean property is excluded */ - static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { + public static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { Method wm = pd.getWriteMethod(); if (wm == null) { return false; @@ -135,7 +107,7 @@ public abstract class AutowireUtils { * @param interfaces the Set of interfaces (Class objects) * @return whether the setter method is defined by an interface */ - static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set> interfaces) { + public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set> interfaces) { Method setter = pd.getWriteMethod(); if (setter != null) { Class targetClass = setter.getDeclaringClass(); @@ -156,7 +128,7 @@ public abstract class AutowireUtils { * @param requiredType the type to assign the result to * @return the resolved value */ - static Object resolveAutowiringValue(Object autowiringValue, Class requiredType) { + public static Object resolveAutowiringValue(Object autowiringValue, Class requiredType) { if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { ObjectFactory factory = (ObjectFactory) autowiringValue; if (autowiringValue instanceof Serializable && requiredType.isInterface()) { @@ -201,7 +173,7 @@ public abstract class AutowireUtils { * @return the resolved target return type or the standard method return type * @since 3.2.5 */ - static Class resolveReturnTypeForFactoryMethod( + public static Class resolveReturnTypeForFactoryMethod( Method method, Object[] args, @Nullable ClassLoader classLoader) { Assert.notNull(method, "Method must not be null"); @@ -292,111 +264,6 @@ public abstract class AutowireUtils { return method.getReturnType(); } - /** - * Determine if the supplied {@link Parameter} can potentially be - * autowired from an {@link AutowireCapableBeanFactory}. - *

Returns {@code true} if the supplied parameter is annotated or - * meta-annotated with {@link Autowired @Autowired}, - * {@link Qualifier @Qualifier}, or {@link Value @Value}. - *

Note that {@link #resolveDependency} may still be able to resolve the - * dependency for the supplied parameter even if this method returns {@code false}. - * @param parameter the parameter whose dependency should be autowired - * (must not be {@code null}) - * @param parameterIndex the index of the parameter in the constructor or method - * that declares the parameter - * @since 5.2 - * @see #resolveDependency - */ - public static boolean isAutowirable(Parameter parameter, int parameterIndex) { - Assert.notNull(parameter, "Parameter must not be null"); - AnnotatedElement annotatedParameter = getEffectiveAnnotatedParameter(parameter, parameterIndex); - return (AnnotatedElementUtils.hasAnnotation(annotatedParameter, Autowired.class) || - AnnotatedElementUtils.hasAnnotation(annotatedParameter, Qualifier.class) || - AnnotatedElementUtils.hasAnnotation(annotatedParameter, Value.class)); - } - - /** - * Resolve the dependency for the supplied {@link Parameter} from the - * supplied {@link AutowireCapableBeanFactory}. - *

Provides comprehensive autowiring support for individual method parameters - * on par with Spring's dependency injection facilities for autowired fields and - * methods, including support for {@link Autowired @Autowired}, - * {@link Qualifier @Qualifier}, and {@link Value @Value} with support for property - * placeholders and SpEL expressions in {@code @Value} declarations. - *

The dependency is required unless the parameter is annotated or meta-annotated - * 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. - * @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 - * @since 5.2 - * @see #isAutowirable - * @see Autowired#required - * @see SynthesizingMethodParameter#forExecutable(Executable, int) - * @see AutowireCapableBeanFactory#resolveDependency(DependencyDescriptor, String) - */ - @Nullable - public static Object resolveDependency( - Parameter parameter, int parameterIndex, Class containingClass, AutowireCapableBeanFactory beanFactory) - throws BeansException { - - Assert.notNull(parameter, "Parameter must not be null"); - Assert.notNull(containingClass, "Containing class must not be null"); - Assert.notNull(beanFactory, "AutowireCapableBeanFactory must not be null"); - - AnnotatedElement annotatedParameter = getEffectiveAnnotatedParameter(parameter, parameterIndex); - Autowired autowired = AnnotatedElementUtils.findMergedAnnotation(annotatedParameter, Autowired.class); - boolean required = (autowired == null || autowired.required()); - - MethodParameter methodParameter = SynthesizingMethodParameter.forExecutable( - parameter.getDeclaringExecutable(), parameterIndex); - DependencyDescriptor descriptor = new DependencyDescriptor(methodParameter, required); - descriptor.setContainingClass(containingClass); - return beanFactory.resolveDependency(descriptor, null); - } - - /** - * Due to a bug in {@code javac} on JDK versions prior to JDK 9, looking up - * annotations directly on a {@link Parameter} will fail for inner class - * constructors. - *

Bug in javac in JDK < 9

- *

The parameter annotations array in the compiled byte code excludes an entry - * for the implicit enclosing instance parameter for an inner class - * constructor. - *

Workaround

- *

This method provides a workaround for this off-by-one error by allowing the - * caller to access annotations on the preceding {@link Parameter} object (i.e., - * {@code index - 1}). If the supplied {@code index} is zero, this method returns - * an empty {@code AnnotatedElement}. - *

WARNING

- *

The {@code AnnotatedElement} returned by this method should never be cast and - * treated as a {@code Parameter} since the metadata (e.g., {@link Parameter#getName()}, - * {@link Parameter#getType()}, etc.) will not match those for the declared parameter - * at the given index in an inner class constructor. - * @return the supplied {@code parameter} or the effective {@code Parameter} - * if the aforementioned bug is in effect - */ - private static AnnotatedElement getEffectiveAnnotatedParameter(Parameter parameter, int index) { - Executable executable = parameter.getDeclaringExecutable(); - if (executable instanceof Constructor && ClassUtils.isInnerClass(executable.getDeclaringClass()) && - executable.getParameterAnnotations().length == executable.getParameterCount() - 1) { - // Bug in javac in JDK <9: annotation array excludes enclosing instance parameter - // for inner classes, so access it with the actual parameter index lowered by 1 - return (index == 0 ? EMPTY_ANNOTATED_ELEMENT : executable.getParameters()[index - 1]); - } - return parameter; - } - /** * Reflective {@link InvocationHandler} for lazy access to the current target object. 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 new file mode 100644 index 0000000000000000000000000000000000000000..fda2c7f4e7f41cd30eecf3261365b5554172cab0 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/ParameterResolutionTests.java @@ -0,0 +1,171 @@ +/* + * Copyright 2002-2019 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory.annotation; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.util.ClassUtils; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for {@link ParameterResolutionDelegate}. + * + * @author Sam Brannen + * @author Juergen Hoeller + * @author Loïc Ledoyen + */ +public class ParameterResolutionTests { + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + + @Test + public void isAutowirablePreconditions() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Parameter must not be null"); + ParameterResolutionDelegate.isAutowirable(null, 0); + } + + @Test + public void annotatedParametersInMethodAreCandidatesForAutowiring() throws Exception { + Method method = getClass().getDeclaredMethod("autowirableMethod", String.class, String.class, String.class, String.class); + assertAutowirableParameters(method); + } + + @Test + public void annotatedParametersInTopLevelClassConstructorAreCandidatesForAutowiring() throws Exception { + Constructor constructor = AutowirableClass.class.getConstructor(String.class, String.class, String.class, String.class); + assertAutowirableParameters(constructor); + } + + @Test + public void annotatedParametersInInnerClassConstructorAreCandidatesForAutowiring() throws Exception { + Class innerClass = AutowirableClass.InnerAutowirableClass.class; + assertTrue(ClassUtils.isInnerClass(innerClass)); + Constructor constructor = innerClass.getConstructor(AutowirableClass.class, String.class, String.class); + assertAutowirableParameters(constructor); + } + + private void assertAutowirableParameters(Executable executable) { + int startIndex = (executable instanceof Constructor) + && ClassUtils.isInnerClass(executable.getDeclaringClass()) ? 1 : 0; + Parameter[] parameters = executable.getParameters(); + for (int parameterIndex = startIndex; parameterIndex < parameters.length; parameterIndex++) { + Parameter parameter = parameters[parameterIndex]; + assertTrue("Parameter " + parameter + " must be autowirable", + ParameterResolutionDelegate.isAutowirable(parameter, parameterIndex)); + } + } + + @Test + public void nonAnnotatedParametersInTopLevelClassConstructorAreNotCandidatesForAutowiring() throws Exception { + Constructor notAutowirableConstructor = AutowirableClass.class.getConstructor(String.class); + + Parameter[] parameters = notAutowirableConstructor.getParameters(); + for (int parameterIndex = 0; parameterIndex < parameters.length; parameterIndex++) { + Parameter parameter = parameters[parameterIndex]; + assertFalse("Parameter " + parameter + " must not be autowirable", + ParameterResolutionDelegate.isAutowirable(parameter, parameterIndex)); + } + } + + @Test + public void resolveDependencyPreconditionsForParameter() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Parameter must not be null"); + ParameterResolutionDelegate.resolveDependency(null, 0, null, mock(AutowireCapableBeanFactory.class)); + } + + @Test + public void resolveDependencyPreconditionsForContainingClass() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Containing class must not be null"); + ParameterResolutionDelegate.resolveDependency(getParameter(), 0, null, null); + } + + @Test + public void resolveDependencyPreconditionsForBeanFactory() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("AutowireCapableBeanFactory must not be null"); + ParameterResolutionDelegate.resolveDependency(getParameter(), 0, getClass(), null); + } + + private Parameter getParameter() throws NoSuchMethodException { + Method method = getClass().getDeclaredMethod("autowirableMethod", String.class, String.class, String.class, String.class); + return method.getParameters()[0]; + } + + @Test + public void resolveDependencyForAnnotatedParametersInTopLevelClassConstructor() throws Exception { + Constructor constructor = AutowirableClass.class.getConstructor(String.class, String.class, String.class, String.class); + + AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + // Configure the mocked BeanFactory to return the DependencyDescriptor for convenience and + // to avoid using an ArgumentCaptor. + when(beanFactory.resolveDependency(any(), isNull())).thenAnswer(invocation -> invocation.getArgument(0)); + + Parameter[] parameters = constructor.getParameters(); + for (int parameterIndex = 0; parameterIndex < parameters.length; parameterIndex++) { + Parameter parameter = parameters[parameterIndex]; + DependencyDescriptor intermediateDependencyDescriptor = (DependencyDescriptor) ParameterResolutionDelegate.resolveDependency( + parameter, parameterIndex, AutowirableClass.class, beanFactory); + assertEquals(constructor, intermediateDependencyDescriptor.getAnnotatedElement()); + assertEquals(parameter, intermediateDependencyDescriptor.getMethodParameter().getParameter()); + } + } + + + void autowirableMethod( + @Autowired String firstParameter, + @Qualifier("someQualifier") String secondParameter, + @Value("${someValue}") String thirdParameter, + @Autowired(required = false) String fourthParameter) { + } + + + public static class AutowirableClass { + + public AutowirableClass(@Autowired String firstParameter, + @Qualifier("someQualifier") String secondParameter, + @Value("${someValue}") String thirdParameter, + @Autowired(required = false) String fourthParameter) { + } + + public AutowirableClass(String notAutowirableParameter) { + } + + public class InnerAutowirableClass { + + public InnerAutowirableClass(@Autowired String firstParameter, + @Qualifier("someQualifier") String secondParameter) { + } + } + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java index 5e17b1e4b6138df56bf0493162416c618647f808..00548277279908655d34a59c61ffb0831f7fb71d 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java @@ -16,28 +16,15 @@ package org.springframework.beans.factory.support; -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; import java.lang.reflect.Method; -import java.lang.reflect.Parameter; import java.util.HashMap; import java.util.Map; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.beans.factory.config.DependencyDescriptor; -import org.springframework.util.ClassUtils; + import org.springframework.util.ReflectionUtils; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; /** * Unit tests for {@link AutowireUtils}. @@ -47,52 +34,49 @@ import static org.mockito.Mockito.*; * @author Loïc Ledoyen */ public class AutowireUtilsTests { - - @Rule - public final ExpectedException exception = ExpectedException.none(); @Test public void genericMethodReturnTypes() { Method notParameterized = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterized"); assertEquals(String.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterized, new Object[]{}, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterized, new Object[0], getClass().getClassLoader())); Method notParameterizedWithArguments = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments", Integer.class, Boolean.class); assertEquals(String.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterizedWithArguments, new Object[]{99, true}, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterizedWithArguments, new Object[] {99, true}, getClass().getClassLoader())); Method createProxy = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createProxy", Object.class); assertEquals(String.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createProxy, new Object[]{"foo"}, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createProxy, new Object[] {"foo"}, getClass().getClassLoader())); Method createNamedProxyWithDifferentTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", String.class, Object.class); assertEquals(Long.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[]{"enigma", 99L}, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[] {"enigma", 99L}, getClass().getClassLoader())); Method createNamedProxyWithDuplicateTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", String.class, Object.class); assertEquals(String.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDuplicateTypes, new Object[]{"enigma", "foo"}, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDuplicateTypes, new Object[] {"enigma", "foo"}, getClass().getClassLoader())); Method createMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createMock", Class.class); assertEquals(Runnable.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[]{Runnable.class}, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] {Runnable.class}, getClass().getClassLoader())); assertEquals(Runnable.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[]{Runnable.class.getName()}, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] {Runnable.class.getName()}, getClass().getClassLoader())); Method createNamedMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedMock", String.class, Class.class); assertEquals(Runnable.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedMock, new Object[]{"foo", Runnable.class}, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedMock, new Object[] {"foo", Runnable.class}, getClass().getClassLoader())); Method createVMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createVMock", Object.class, Class.class); assertEquals(Runnable.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createVMock, new Object[]{"foo", Runnable.class}, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createVMock, new Object[] {"foo", Runnable.class}, getClass().getClassLoader())); // Ideally we would expect String.class instead of Object.class, but // resolveReturnTypeForFactoryMethod() does not currently support this form of // look-up. Method extractValueFrom = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractValueFrom", MyInterfaceType.class); assertEquals(Object.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(extractValueFrom, new Object[]{new MySimpleInterfaceType()}, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(extractValueFrom, new Object[] {new MySimpleInterfaceType()}, getClass().getClassLoader())); // Ideally we would expect Boolean.class instead of Object.class, but this // information is not available at run-time due to type erasure. @@ -100,100 +84,7 @@ public class AutowireUtilsTests { map.put(0, false); map.put(1, true); Method extractMagicValue = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractMagicValue", Map.class); - assertEquals(Object.class, AutowireUtils.resolveReturnTypeForFactoryMethod(extractMagicValue, new Object[]{map}, getClass().getClassLoader())); - } - - @Test - public void isAutowirablePreconditions() { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("Parameter must not be null"); - AutowireUtils.isAutowirable(null, 0); - } - - @Test - public void annotatedParametersInMethodAreCandidatesForAutowiring() throws Exception { - Method method = getClass().getDeclaredMethod("autowirableMethod", String.class, String.class, String.class, String.class); - assertAutowirableParameters(method); - } - - @Test - public void annotatedParametersInTopLevelClassConstructorAreCandidatesForAutowiring() throws Exception { - Constructor constructor = AutowirableClass.class.getConstructor(String.class, String.class, String.class, String.class); - assertAutowirableParameters(constructor); - } - - @Test - public void annotatedParametersInInnerClassConstructorAreCandidatesForAutowiring() throws Exception { - Class innerClass = AutowirableClass.InnerAutowirableClass.class; - assertTrue(ClassUtils.isInnerClass(innerClass)); - Constructor constructor = innerClass.getConstructor(AutowirableClass.class, String.class, String.class); - assertAutowirableParameters(constructor); - } - - private void assertAutowirableParameters(Executable executable) { - int startIndex = (executable instanceof Constructor) - && ClassUtils.isInnerClass(executable.getDeclaringClass()) ? 1 : 0; - Parameter[] parameters = executable.getParameters(); - for (int parameterIndex = startIndex; parameterIndex < parameters.length; parameterIndex++) { - Parameter parameter = parameters[parameterIndex]; - assertTrue("Parameter " + parameter + " must be autowirable", AutowireUtils.isAutowirable(parameter, parameterIndex)); - } - } - - @Test - public void nonAnnotatedParametersInTopLevelClassConstructorAreNotCandidatesForAutowiring() throws Exception { - Constructor notAutowirableConstructor = AutowirableClass.class.getConstructor(String.class); - - Parameter[] parameters = notAutowirableConstructor.getParameters(); - for (int parameterIndex = 0; parameterIndex < parameters.length; parameterIndex++) { - Parameter parameter = parameters[parameterIndex]; - assertFalse("Parameter " + parameter + " must not be autowirable", AutowireUtils.isAutowirable(parameter, parameterIndex)); - } - } - - @Test - public void resolveDependencyPreconditionsForParameter() { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("Parameter must not be null"); - AutowireUtils.resolveDependency(null, 0, null, mock(AutowireCapableBeanFactory.class)); - } - - @Test - public void resolveDependencyPreconditionsForContainingClass() throws Exception { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("Containing class must not be null"); - AutowireUtils.resolveDependency(getParameter(), 0, null, null); - } - - @Test - public void resolveDependencyPreconditionsForBeanFactory() throws Exception { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("AutowireCapableBeanFactory must not be null"); - AutowireUtils.resolveDependency(getParameter(), 0, getClass(), null); - } - - private Parameter getParameter() throws NoSuchMethodException { - Method method = getClass().getDeclaredMethod("autowirableMethod", String.class, String.class, String.class, String.class); - return method.getParameters()[0]; - } - - @Test - public void resolveDependencyForAnnotatedParametersInTopLevelClassConstructor() throws Exception { - Constructor constructor = AutowirableClass.class.getConstructor(String.class, String.class, String.class, String.class); - - AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); - // Configure the mocked BeanFactory to return the DependencyDescriptor for convenience and - // to avoid using an ArgumentCaptor. - when(beanFactory.resolveDependency(any(), isNull())).thenAnswer(invocation -> invocation.getArgument(0)); - - Parameter[] parameters = constructor.getParameters(); - for (int parameterIndex = 0; parameterIndex < parameters.length; parameterIndex++) { - Parameter parameter = parameters[parameterIndex]; - DependencyDescriptor intermediateDependencyDescriptor = (DependencyDescriptor) AutowireUtils.resolveDependency( - parameter, parameterIndex, AutowirableClass.class, beanFactory); - assertEquals(constructor, intermediateDependencyDescriptor.getAnnotatedElement()); - assertEquals(parameter, intermediateDependencyDescriptor.getMethodParameter().getParameter()); - } + assertEquals(Object.class, AutowireUtils.resolveReturnTypeForFactoryMethod(extractMagicValue, new Object[] {map}, getClass().getClassLoader())); } @@ -295,30 +186,4 @@ public class AutowireUtilsTests { } } - void autowirableMethod( - @Autowired String firstParameter, - @Qualifier("someQualifier") String secondParameter, - @Value("${someValue}") String thirdParameter, - @Autowired(required = false) String fourthParameter) { - } - - public static class AutowirableClass { - - public AutowirableClass(@Autowired String firstParameter, - @Qualifier("someQualifier") String secondParameter, - @Value("${someValue}") String thirdParameter, - @Autowired(required = false) String fourthParameter) { - } - - public AutowirableClass(String notAutowirableParameter) { - } - - public class InnerAutowirableClass { - - public InnerAutowirableClass(@Autowired String firstParameter, - @Qualifier("someQualifier") String secondParameter) { - } - } - } - } diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java index 24a0990a8b729e2693d11a0cb7893ef00258c18c..acbdc3e06c542ad3dd4c1e22e215e7f3c1283178 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java @@ -35,7 +35,7 @@ import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.support.AutowireUtils; +import org.springframework.beans.factory.annotation.ParameterResolutionDelegate; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.lang.Nullable; @@ -146,31 +146,30 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes *

Returns {@code true} if the parameter is declared in a {@link Constructor} * that is annotated with {@link Autowired @Autowired} or if the parameter is * of type {@link ApplicationContext} (or a sub-type thereof) and otherwise delegates - * to {@link AutowireUtils#isAutowirable}. + * to {@link ParameterResolutionDelegate#isAutowirable}. *

WARNING: If the parameter is declared in a {@code Constructor} * that is annotated with {@code @Autowired}, Spring will assume the responsibility * for resolving all parameters in the constructor. Consequently, no other registered * {@link ParameterResolver} will be able to resolve parameters. * @see #resolveParameter - * @see AutowireUtils#isAutowirable + * @see ParameterResolutionDelegate#isAutowirable */ @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { Parameter parameter = parameterContext.getParameter(); int index = parameterContext.getIndex(); - Executable executable = parameter.getDeclaringExecutable(); - return (executable instanceof Constructor && - AnnotatedElementUtils.hasAnnotation(executable, Autowired.class)) || + Executable exec = parameter.getDeclaringExecutable(); + return ((exec instanceof Constructor && AnnotatedElementUtils.hasAnnotation(exec, Autowired.class)) || ApplicationContext.class.isAssignableFrom(parameter.getType()) || - AutowireUtils.isAutowirable(parameter, index); + ParameterResolutionDelegate.isAutowirable(parameter, index)); } /** * Resolve a value for the {@link Parameter} in the supplied {@link ParameterContext} by * retrieving the corresponding dependency from the test's {@link ApplicationContext}. - *

Delegates to {@link AutowireUtils#resolveDependency}. + *

Delegates to {@link ParameterResolutionDelegate#resolveDependency}. * @see #supportsParameter - * @see AutowireUtils#resolveDependency + * @see ParameterResolutionDelegate#resolveDependency */ @Override @Nullable @@ -179,8 +178,8 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes int index = parameterContext.getIndex(); Class testClass = extensionContext.getRequiredTestClass(); ApplicationContext applicationContext = getApplicationContext(extensionContext); - return AutowireUtils.resolveDependency(parameter, index, testClass, - applicationContext.getAutowireCapableBeanFactory()); + return ParameterResolutionDelegate.resolveDependency(parameter, index, testClass, + applicationContext.getAutowireCapableBeanFactory()); }