Skip to content

Open parameter autowiring utility for external use #2060

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 27, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Move ParameterAutowireUtils features to AutowireUtils
Issues: gh-2060
  • Loading branch information
ledoyen committed Feb 27, 2019
commit 96538414911949d2aee600e470561f3ff4a5585f
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -56,6 +68,22 @@ public abstract class AutowireUtils {
return result != 0 ? result : Integer.compare(e2.getParameterCount(), e1.getParameterCount());
};

private static final AnnotatedElement EMPTY_ANNOTATED_ELEMENT = new AnnotatedElement() {
@Override
@Nullable
public <T extends Annotation> T getAnnotation(Class<T> 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
Expand Down Expand Up @@ -264,6 +292,103 @@ else if (arg instanceof TypedStringValue) {
return method.getReturnType();
}

/**
* Determine if the supplied {@link Parameter} can <em>potentially</em> be
* autowired from an {@link AutowireCapableBeanFactory}.
* <p>Returns {@code true} if the supplied parameter is annotated or
* meta-annotated with {@link Autowired @Autowired},
* {@link Qualifier @Qualifier}, or {@link Value @Value}.
* <p>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}.
* <p>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.
* <p>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}.
* <p>If an explicit <em>qualifier</em> 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.
* <h4>Bug in javac in JDK &lt; 9</h4>
* <p>The parameter annotations array in the compiled byte code excludes an entry
* for the implicit <em>enclosing instance</em> parameter for an inner class
* constructor.
* <h4>Workaround</h4>
* <p>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}.
* <h4>WARNING</h4>
* <p>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 <em>effective</em> {@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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,83 +52,92 @@ 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.
Map<Integer, Boolean> map = new HashMap<>();
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<T> {
}

public class MySimpleInterfaceType implements MyInterfaceType<String> {
assertEquals(Object.class, AutowireUtils.resolveReturnTypeForFactoryMethod(extractMagicValue, new Object[]{map}, getClass().getClassLoader()));
}

public static class MyTypeWithMethods<T> {
@Test
public void marked_parameters_are_candidate_for_autowiring() throws NoSuchMethodException {
Constructor<AutowirableClass> autowirableConstructor = ReflectionUtils.accessibleConstructor(
AutowirableClass.class, String.class, String.class, String.class, String.class);

public MyInterfaceType<Integer> 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<AutowirableClass> 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<AutowirableClass> 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<T> {
}

public String notParameterizedWithArguments(Integer x, Boolean b) {
return null;
}
public static class MyTypeWithMethods<T> {

/**
* Simulates a factory method that wraps the supplied object in a proxy of the
Expand Down Expand Up @@ -173,6 +194,31 @@ public static <K, V> V extractMagicValue(Map<K, V> map) {
return null;
}

public MyInterfaceType<Integer> 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<Integer> message) {
}

Expand All @@ -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<String> {
}
}
Loading