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 5710a32f0138..c6afd9353ad2 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,12 +18,15 @@ 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; @@ -33,8 +36,17 @@ 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; @@ -49,13 +61,29 @@ * @since 1.1.2 * @see AbstractAutowireCapableBeanFactory */ -abstract class AutowireUtils { +public 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 @@ -64,7 +92,7 @@ abstract class AutowireUtils { * decreasing number of arguments. * @param constructors the constructor array to sort */ - public static void sortConstructors(Constructor[] constructors) { + static void sortConstructors(Constructor[] constructors) { Arrays.sort(constructors, EXECUTABLE_COMPARATOR); } @@ -75,7 +103,7 @@ public static void sortConstructors(Constructor[] constructors) { * decreasing number of arguments. * @param factoryMethods the factory method array to sort */ - public static void sortFactoryMethods(Method[] factoryMethods) { + static void sortFactoryMethods(Method[] factoryMethods) { Arrays.sort(factoryMethods, EXECUTABLE_COMPARATOR); } @@ -85,7 +113,7 @@ public static void sortFactoryMethods(Method[] factoryMethods) { * @param pd the PropertyDescriptor of the bean property * @return whether the bean property is excluded */ - public static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { + static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { Method wm = pd.getWriteMethod(); if (wm == null) { return false; @@ -107,7 +135,7 @@ public static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { * @param interfaces the Set of interfaces (Class objects) * @return whether the setter method is defined by an interface */ - public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set> interfaces) { + static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set> interfaces) { Method setter = pd.getWriteMethod(); if (setter != null) { Class targetClass = setter.getDeclaringClass(); @@ -128,7 +156,7 @@ public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set requiredType) { + static Object resolveAutowiringValue(Object autowiringValue, Class requiredType) { if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { ObjectFactory factory = (ObjectFactory) autowiringValue; if (autowiringValue instanceof Serializable && requiredType.isInterface()) { @@ -173,7 +201,7 @@ public static Object resolveAutowiringValue(Object autowiringValue, Class req * @return the resolved target return type or the standard method return type * @since 3.2.5 */ - public static Class resolveReturnTypeForFactoryMethod( + static Class resolveReturnTypeForFactoryMethod( Method method, Object[] args, @Nullable ClassLoader classLoader) { Assert.notNull(method, "Method must not be null"); @@ -264,6 +292,103 @@ else if (arg instanceof TypedStringValue) { 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 + * @param parameterIndex the index of the parameter in the constructor or method + * that declares the parameter + * @see #resolveDependency + * @since 5.2 + */ + public static boolean isAutowirable(Parameter parameter, int parameterIndex) { + 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 + * @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 + * @param beanFactory the {@code AutowireCapableBeanFactory} from which to resolve + * the dependency + * @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) + * @since 5.2 + */ + @Nullable + public static Object resolveDependency( + Parameter parameter, int parameterIndex, Class containingClass, AutowireCapableBeanFactory beanFactory) + throws BeansException { + + 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 InvocationHandler for lazy access to the current target object. 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 c1de073dc27f..08aec5ef0ee2 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,16 +16,28 @@ package org.springframework.beans.factory.support; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.util.HashMap; import java.util.Map; import org.junit.Test; - +import org.mockito.Mockito; +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.ReflectionUtils; -import static org.junit.Assert.*; - /** * @author Juergen Hoeller * @author Sam Brannen @@ -40,40 +52,40 @@ public void genericMethodReturnTypes() { 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. @@ -81,42 +93,51 @@ public void genericMethodReturnTypes() { 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())); - } - - - public interface MyInterfaceType { - } - - public class MySimpleInterfaceType implements MyInterfaceType { + assertEquals(Object.class, AutowireUtils.resolveReturnTypeForFactoryMethod(extractMagicValue, new Object[]{map}, getClass().getClassLoader())); } - public static class MyTypeWithMethods { + @Test + public void marked_parameters_are_candidate_for_autowiring() throws NoSuchMethodException { + Constructor autowirableConstructor = ReflectionUtils.accessibleConstructor( + AutowirableClass.class, String.class, String.class, String.class, String.class); - public MyInterfaceType integer() { - return null; + for (int parameterIndex = 0; parameterIndex < autowirableConstructor.getParameterCount(); parameterIndex++) { + Parameter parameter = autowirableConstructor.getParameters()[parameterIndex]; + assertTrue("Parameter " + parameter + " must be autowirable", AutowireUtils.isAutowirable(parameter, parameterIndex)); } + } - public MySimpleInterfaceType string() { - return null; - } + @Test + public void not_marked_parameters_are_not_candidate_for_autowiring() throws NoSuchMethodException { + Constructor notAutowirableConstructor = ReflectionUtils.accessibleConstructor(AutowirableClass.class, String.class); - public Object object() { - return null; + for (int parameterIndex = 0; parameterIndex < notAutowirableConstructor.getParameterCount(); parameterIndex++) { + Parameter parameter = notAutowirableConstructor.getParameters()[parameterIndex]; + assertFalse("Parameter " + parameter + " must not be autowirable", AutowireUtils.isAutowirable(parameter, 0)); } + } - @SuppressWarnings("rawtypes") - public MyInterfaceType raw() { - return null; + @Test + public void dependency_resolution_for_marked_parameters() throws NoSuchMethodException { + Constructor autowirableConstructor = ReflectionUtils.accessibleConstructor( + AutowirableClass.class, String.class, String.class, String.class, String.class); + AutowireCapableBeanFactory beanFactory = Mockito.mock(AutowireCapableBeanFactory.class); + // BeanFactory will return the DependencyDescriptor for convenience and to avoid using an ArgumentCaptor + when(beanFactory.resolveDependency(any(), isNull())).thenAnswer(iom -> iom.getArgument(0)); + + for (int parameterIndex = 0; parameterIndex < autowirableConstructor.getParameterCount(); parameterIndex++) { + Parameter parameter = autowirableConstructor.getParameters()[parameterIndex]; + DependencyDescriptor intermediateDependencyDescriptor = (DependencyDescriptor) AutowireUtils.resolveDependency( + parameter, parameterIndex, AutowirableClass.class, beanFactory); + assertEquals(intermediateDependencyDescriptor.getAnnotatedElement(), autowirableConstructor); + assertEquals(intermediateDependencyDescriptor.getMethodParameter().getParameter(), parameter); } + } - public String notParameterized() { - return null; - } + public interface MyInterfaceType { + } - public String notParameterizedWithArguments(Integer x, Boolean b) { - return null; - } + public static class MyTypeWithMethods { /** * Simulates a factory method that wraps the supplied object in a proxy of the @@ -173,6 +194,31 @@ public static V extractMagicValue(Map map) { return null; } + public MyInterfaceType integer() { + return null; + } + + public MySimpleInterfaceType string() { + return null; + } + + public Object object() { + return null; + } + + @SuppressWarnings("rawtypes") + public MyInterfaceType raw() { + return null; + } + + public String notParameterized() { + return null; + } + + public String notParameterizedWithArguments(Integer x, Boolean b) { + return null; + } + public void readIntegerInputMessage(MyInterfaceType message) { } @@ -183,4 +229,17 @@ public void readGenericArrayInputMessage(T[] message) { } } + 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 MySimpleInterfaceType implements MyInterfaceType { + } } diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/ParameterAutowireUtils.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/ParameterAutowireUtils.java deleted file mode 100644 index b49d801a63c5..000000000000 --- a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/ParameterAutowireUtils.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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.test.context.junit.jupiter; - -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.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.core.MethodParameter; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.SynthesizingMethodParameter; -import org.springframework.lang.Nullable; -import org.springframework.util.ClassUtils; - -/** - * Collection of utilities related to autowiring of individual method parameters. - * - * @author Sam Brannen - * @since 5.0 - * @see #isAutowirable - * @see #resolveDependency - */ -abstract class ParameterAutowireUtils { - - 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]; - } - }; - - - /** - * 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 - * @param parameterIndex the index of the parameter in the constructor or method - * that declares the parameter - * @see #resolveDependency - */ - static boolean isAutowirable(Parameter parameter, int parameterIndex) { - 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 - * @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 - * @param beanFactory the {@code AutowireCapableBeanFactory} from which to resolve - * the dependency - * @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 - static Object resolveDependency( - Parameter parameter, int parameterIndex, Class containingClass, AutowireCapableBeanFactory beanFactory) { - - 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-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 43b7c98bad41..24a0990a8b72 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,6 +35,7 @@ import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.support.AutowireUtils; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.lang.Nullable; @@ -145,13 +146,13 @@ public void afterEach(ExtensionContext context) throws Exception { *

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 ParameterAutowireUtils#isAutowirable}. + * to {@link AutowireUtils#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 ParameterAutowireUtils#isAutowirable + * @see AutowireUtils#isAutowirable */ @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { @@ -161,15 +162,15 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon return (executable instanceof Constructor && AnnotatedElementUtils.hasAnnotation(executable, Autowired.class)) || ApplicationContext.class.isAssignableFrom(parameter.getType()) || - ParameterAutowireUtils.isAutowirable(parameter, index); + AutowireUtils.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 ParameterAutowireUtils#resolveDependency}. + *

Delegates to {@link AutowireUtils#resolveDependency}. * @see #supportsParameter - * @see ParameterAutowireUtils#resolveDependency + * @see AutowireUtils#resolveDependency */ @Override @Nullable @@ -178,7 +179,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte int index = parameterContext.getIndex(); Class testClass = extensionContext.getRequiredTestClass(); ApplicationContext applicationContext = getApplicationContext(extensionContext); - return ParameterAutowireUtils.resolveDependency(parameter, index, testClass, + return AutowireUtils.resolveDependency(parameter, index, testClass, applicationContext.getAutowireCapableBeanFactory()); }