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 c6afd9353a..eb0187f9c4 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 @@ -300,13 +300,15 @@ public abstract class AutowireUtils { * {@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 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 * @since 5.2 */ 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) || @@ -326,14 +328,15 @@ public abstract class AutowireUtils { * 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 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 * @param beanFactory the {@code AutowireCapableBeanFactory} from which to resolve - * the dependency + * 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 @@ -347,6 +350,9 @@ public abstract class AutowireUtils { Parameter parameter, int parameterIndex, Class containingClass, AutowireCapableBeanFactory beanFactory) throws BeansException { + Assert.notNull(parameter, "Parameter 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()); 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 08aec5ef0e..7ba1b10c6f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * 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. @@ -16,33 +16,40 @@ 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.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.mockito.Mockito; +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}. + * * @author Juergen Hoeller * @author Sam Brannen + * @author Loïc Ledoyen */ public class AutowireUtilsTests { + + @Rule + public final ExpectedException exception = ExpectedException.none(); @Test public void genericMethodReturnTypes() { @@ -97,46 +104,96 @@ public class AutowireUtilsTests { } @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 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); + } - for (int parameterIndex = 0; parameterIndex < autowirableConstructor.getParameterCount(); parameterIndex++) { - Parameter parameter = autowirableConstructor.getParameters()[parameterIndex]; + 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 not_marked_parameters_are_not_candidate_for_autowiring() throws NoSuchMethodException { - Constructor notAutowirableConstructor = ReflectionUtils.accessibleConstructor(AutowirableClass.class, String.class); + public void nonAnnotatedParametersInTopLevelClassConstructorAreNotCandidatesForAutowiring() throws Exception { + Constructor notAutowirableConstructor = AutowirableClass.class.getConstructor(String.class); - for (int parameterIndex = 0; parameterIndex < notAutowirableConstructor.getParameterCount(); parameterIndex++) { - Parameter parameter = notAutowirableConstructor.getParameters()[parameterIndex]; - assertFalse("Parameter " + parameter + " must not be autowirable", AutowireUtils.isAutowirable(parameter, 0)); + 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 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]; + 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 resolveDependencyPreconditionsForBeanFactory() throws Exception { + Method method = getClass().getDeclaredMethod("autowirableMethod", String.class, String.class, String.class, String.class); + Parameter parameter = method.getParameters()[0]; + + exception.expect(IllegalArgumentException.class); + exception.expectMessage("AutowireCapableBeanFactory must not be null"); + AutowireUtils.resolveDependency(parameter, 0, null, null); + } + + @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(intermediateDependencyDescriptor.getAnnotatedElement(), autowirableConstructor); - assertEquals(intermediateDependencyDescriptor.getMethodParameter().getParameter(), parameter); + assertEquals(constructor, intermediateDependencyDescriptor.getAnnotatedElement()); + assertEquals(parameter, intermediateDependencyDescriptor.getMethodParameter().getParameter()); } } + public interface MyInterfaceType { } + public class MySimpleInterfaceType implements MyInterfaceType { + } + public static class MyTypeWithMethods { /** @@ -229,7 +286,15 @@ 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, @@ -238,8 +303,13 @@ public class AutowireUtilsTests { public AutowirableClass(String notAutowirableParameter) { } - } - public class MySimpleInterfaceType implements MyInterfaceType { + public class InnerAutowirableClass { + + public InnerAutowirableClass(@Autowired String firstParameter, + @Qualifier("someQualifier") String secondParameter) { + } + } } + }