Browse Source

Cache DependencyDescriptor per autowired constructor argument

Aligned with shortcut handling in AutowiredAnnotationBeanPostProcessor.
Includes minor MethodInvoker optimization for pre-resolved targetClass.

Closes gh-30883

(cherry picked from commit 6183f06846)
pull/30971/head
Juergen Hoeller 3 years ago
parent
commit
0b4b313bae
  1. 24
      spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
  2. 4
      spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java
  3. 4
      spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java
  4. 159
      spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java
  5. 150
      spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
  6. 12
      spring-core/src/main/java/org/springframework/util/MethodInvoker.java

24
spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java

@ -651,7 +651,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) { private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required); DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass()); desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1); Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
Assert.state(beanFactory != null, "No BeanFactory available"); Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter(); TypeConverter typeConverter = beanFactory.getTypeConverter();
Object value; Object value;
@ -670,8 +670,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
String autowiredBeanName = autowiredBeanNames.iterator().next(); String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) && if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
cachedFieldValue = new ShortcutDependencyDescriptor( cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName);
desc, autowiredBeanName, field.getType());
} }
} }
this.cachedFieldValue = cachedFieldValue; this.cachedFieldValue = cachedFieldValue;
@ -754,7 +753,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
int argumentCount = method.getParameterCount(); int argumentCount = method.getParameterCount();
Object[] arguments = new Object[argumentCount]; Object[] arguments = new Object[argumentCount];
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount]; DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount); Set<String> autowiredBeanNames = new LinkedHashSet<>(argumentCount * 2);
Assert.state(beanFactory != null, "No BeanFactory available"); Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter(); TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < arguments.length; i++) { for (int i = 0; i < arguments.length; i++) {
@ -763,7 +762,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
currDesc.setContainingClass(bean.getClass()); currDesc.setContainingClass(bean.getClass());
descriptors[i] = currDesc; descriptors[i] = currDesc;
try { try {
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter); Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeanNames, typeConverter);
if (arg == null && !this.required) { if (arg == null && !this.required) {
arguments = null; arguments = null;
break; break;
@ -778,16 +777,16 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
if (!this.cached) { if (!this.cached) {
if (arguments != null) { if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, argumentCount); DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, argumentCount);
registerDependentBeans(beanName, autowiredBeans); registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeans.size() == argumentCount) { if (autowiredBeanNames.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator(); Iterator<String> it = autowiredBeanNames.iterator();
Class<?>[] paramTypes = method.getParameterTypes(); Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) { for (int i = 0; i < paramTypes.length; i++) {
String autowiredBeanName = it.next(); String autowiredBeanName = it.next();
if (arguments[i] != null && beanFactory.containsBean(autowiredBeanName) && if (arguments[i] != null && beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
cachedMethodArguments[i] = new ShortcutDependencyDescriptor( cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
descriptors[i], autowiredBeanName, paramTypes[i]); descriptors[i], autowiredBeanName);
} }
} }
} }
@ -813,17 +812,14 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
private final String shortcut; private final String shortcut;
private final Class<?> requiredType; public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcut) {
public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcut, Class<?> requiredType) {
super(original); super(original);
this.shortcut = shortcut; this.shortcut = shortcut;
this.requiredType = requiredType;
} }
@Override @Override
public Object resolveShortcut(BeanFactory beanFactory) { public Object resolveShortcut(BeanFactory beanFactory) {
return beanFactory.getBean(this.shortcut, this.requiredType); return beanFactory.getBean(this.shortcut, getDependencyType());
} }
} }

4
spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -1504,8 +1504,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
converter = bw; converter = bw;
} }
Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw); String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
Set<String> autowiredBeanNames = new LinkedHashSet<>(propertyNames.length * 2);
for (String propertyName : propertyNames) { for (String propertyName : propertyNames) {
try { try {
PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);

4
spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -134,7 +134,7 @@ class BeanDefinitionValueResolver {
return resolveInnerBean(argName, innerBeanName, bd); return resolveInnerBean(argName, innerBeanName, bd);
} }
else if (value instanceof DependencyDescriptor) { else if (value instanceof DependencyDescriptor) {
Set<String> autowiredBeanNames = new LinkedHashSet<>(4); Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
Object result = this.beanFactory.resolveDependency( Object result = this.beanFactory.resolveDependency(
(DependencyDescriptor) value, this.beanName, autowiredBeanNames, this.typeConverter); (DependencyDescriptor) value, this.beanName, autowiredBeanNames, this.typeConverter);
for (String autowiredBeanName : autowiredBeanNames) { for (String autowiredBeanName : autowiredBeanNames) {

159
spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -45,6 +45,7 @@ import org.springframework.beans.TypeConverter;
import org.springframework.beans.TypeMismatchException; import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
@ -85,12 +86,6 @@ class ConstructorResolver {
private static final Object[] EMPTY_ARGS = new Object[0]; private static final Object[] EMPTY_ARGS = new Object[0];
/**
* Marker for autowired arguments in a cached argument array, to be replaced
* by a {@linkplain #resolveAutowiredArgument resolved autowired argument}.
*/
private static final Object autowiredArgumentMarker = new Object();
private static final NamedThreadLocal<InjectionPoint> currentInjectionPoint = private static final NamedThreadLocal<InjectionPoint> currentInjectionPoint =
new NamedThreadLocal<>("Current injection point"); new NamedThreadLocal<>("Current injection point");
@ -729,7 +724,7 @@ class ConstructorResolver {
ArgumentsHolder args = new ArgumentsHolder(paramTypes.length); ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length); Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
Set<String> autowiredBeanNames = new LinkedHashSet<>(4); Set<String> allAutowiredBeanNames = new LinkedHashSet<>(paramTypes.length * 2);
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
Class<?> paramType = paramTypes[paramIndex]; Class<?> paramType = paramTypes[paramIndex];
@ -764,8 +759,8 @@ class ConstructorResolver {
throw new UnsatisfiedDependencyException( throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
"Could not convert argument value of type [" + "Could not convert argument value of type [" +
ObjectUtils.nullSafeClassName(valueHolder.getValue()) + ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage()); "] to required type [" + paramType.getName() + "]: " + ex.getMessage());
} }
Object sourceHolder = valueHolder.getSource(); Object sourceHolder = valueHolder.getSource();
if (sourceHolder instanceof ConstructorArgumentValues.ValueHolder) { if (sourceHolder instanceof ConstructorArgumentValues.ValueHolder) {
@ -788,11 +783,17 @@ class ConstructorResolver {
"] - did you specify the correct bean references as arguments?"); "] - did you specify the correct bean references as arguments?");
} }
try { try {
Object autowiredArgument = resolveAutowiredArgument( ConstructorDependencyDescriptor desc = new ConstructorDependencyDescriptor(methodParam, true);
methodParam, beanName, autowiredBeanNames, converter, fallback); Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
args.rawArguments[paramIndex] = autowiredArgument; Object arg = resolveAutowiredArgument(
args.arguments[paramIndex] = autowiredArgument; desc, paramType, beanName, autowiredBeanNames, converter, fallback);
args.preparedArguments[paramIndex] = autowiredArgumentMarker; if (arg != null) {
setShortcutIfPossible(desc, paramType, autowiredBeanNames);
}
allAutowiredBeanNames.addAll(autowiredBeanNames);
args.rawArguments[paramIndex] = arg;
args.arguments[paramIndex] = arg;
args.preparedArguments[paramIndex] = desc;
args.resolveNecessary = true; args.resolveNecessary = true;
} }
catch (BeansException ex) { catch (BeansException ex) {
@ -802,14 +803,7 @@ class ConstructorResolver {
} }
} }
for (String autowiredBeanName : autowiredBeanNames) { registerDependentBeans(executable, beanName, allAutowiredBeanNames);
this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
if (logger.isDebugEnabled()) {
logger.debug("Autowiring by type from bean name '" + beanName +
"' via " + (executable instanceof Constructor ? "constructor" : "factory method") +
" to bean named '" + autowiredBeanName + "'");
}
}
return args; return args;
} }
@ -829,31 +823,57 @@ class ConstructorResolver {
Object[] resolvedArgs = new Object[argsToResolve.length]; Object[] resolvedArgs = new Object[argsToResolve.length];
for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) { for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) {
Object argValue = argsToResolve[argIndex]; Object argValue = argsToResolve[argIndex];
MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex); Class<?> paramType = paramTypes[argIndex];
if (argValue == autowiredArgumentMarker) { boolean convertNecessary = false;
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, true); if (argValue instanceof ConstructorDependencyDescriptor) {
ConstructorDependencyDescriptor descriptor = (ConstructorDependencyDescriptor) argValue;
try {
argValue = resolveAutowiredArgument(descriptor, paramType, beanName,
null, converter, true);
}
catch (BeansException ex) {
// Unexpected target bean mismatch for cached argument -> re-resolve
synchronized (descriptor) {
if (!descriptor.hasShortcut()) {
throw ex;
}
descriptor.setShortcut(null);
Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
argValue = resolveAutowiredArgument(descriptor, paramType, beanName,
autowiredBeanNames, converter, true);
if (argValue != null) {
setShortcutIfPossible(descriptor, paramType, autowiredBeanNames);
}
registerDependentBeans(executable, beanName, autowiredBeanNames);
}
}
} }
else if (argValue instanceof BeanMetadataElement) { else if (argValue instanceof BeanMetadataElement) {
argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue); argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue);
convertNecessary = true;
} }
else if (argValue instanceof String) { else if (argValue instanceof String) {
argValue = this.beanFactory.evaluateBeanDefinitionString((String) argValue, mbd); argValue = this.beanFactory.evaluateBeanDefinitionString((String) argValue, mbd);
convertNecessary = true;
} }
Class<?> paramType = paramTypes[argIndex]; if (convertNecessary) {
try { MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
resolvedArgs[argIndex] = converter.convertIfNecessary(argValue, paramType, methodParam); try {
} argValue = converter.convertIfNecessary(argValue, paramType, methodParam);
catch (TypeMismatchException ex) { }
throw new UnsatisfiedDependencyException( catch (TypeMismatchException ex) {
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), throw new UnsatisfiedDependencyException(
"Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) + mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
"] to required type [" + paramType.getName() + "]: " + ex.getMessage()); "Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
}
} }
resolvedArgs[argIndex] = argValue;
} }
return resolvedArgs; return resolvedArgs;
} }
protected Constructor<?> getUserDeclaredConstructor(Constructor<?> constructor) { private Constructor<?> getUserDeclaredConstructor(Constructor<?> constructor) {
Class<?> declaringClass = constructor.getDeclaringClass(); Class<?> declaringClass = constructor.getDeclaringClass();
Class<?> userClass = ClassUtils.getUserClass(declaringClass); Class<?> userClass = ClassUtils.getUserClass(declaringClass);
if (userClass != declaringClass) { if (userClass != declaringClass) {
@ -869,23 +889,22 @@ class ConstructorResolver {
} }
/** /**
* Template method for resolving the specified argument which is supposed to be autowired. * Resolve the specified argument which is supposed to be autowired.
*/ */
@Nullable @Nullable
protected Object resolveAutowiredArgument(MethodParameter param, String beanName, Object resolveAutowiredArgument(DependencyDescriptor descriptor, Class<?> paramType, String beanName,
@Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter, boolean fallback) { @Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter, boolean fallback) {
Class<?> paramType = param.getParameterType();
if (InjectionPoint.class.isAssignableFrom(paramType)) { if (InjectionPoint.class.isAssignableFrom(paramType)) {
InjectionPoint injectionPoint = currentInjectionPoint.get(); InjectionPoint injectionPoint = currentInjectionPoint.get();
if (injectionPoint == null) { if (injectionPoint == null) {
throw new IllegalStateException("No current InjectionPoint available for " + param); throw new IllegalStateException("No current InjectionPoint available for " + descriptor);
} }
return injectionPoint; return injectionPoint;
} }
try { try {
return this.beanFactory.resolveDependency( return this.beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter);
new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
} }
catch (NoUniqueBeanDefinitionException ex) { catch (NoUniqueBeanDefinitionException ex) {
throw ex; throw ex;
@ -908,6 +927,31 @@ class ConstructorResolver {
} }
} }
private void setShortcutIfPossible(
ConstructorDependencyDescriptor descriptor, Class<?> paramType, Set<String> autowiredBeanNames) {
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (this.beanFactory.containsBean(autowiredBeanName) &&
this.beanFactory.isTypeMatch(autowiredBeanName, paramType)) {
descriptor.setShortcut(autowiredBeanName);
}
}
}
private void registerDependentBeans(
Executable executable, String beanName, Set<String> autowiredBeanNames) {
for (String autowiredBeanName : autowiredBeanNames) {
this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
if (logger.isDebugEnabled()) {
logger.debug("Autowiring by type from bean name '" + beanName + "' via " +
(executable instanceof Constructor ? "constructor" : "factory method") +
" to bean named '" + autowiredBeanName + "'");
}
}
}
static InjectionPoint setCurrentInjectionPoint(@Nullable InjectionPoint injectionPoint) { static InjectionPoint setCurrentInjectionPoint(@Nullable InjectionPoint injectionPoint) {
InjectionPoint old = currentInjectionPoint.get(); InjectionPoint old = currentInjectionPoint.get();
if (injectionPoint != null) { if (injectionPoint != null) {
@ -1006,4 +1050,35 @@ class ConstructorResolver {
} }
} }
/**
* DependencyDescriptor marker for constructor arguments,
* for differentiating between a provided DependencyDescriptor instance
* and an internally built DependencyDescriptor for autowiring purposes.
*/
@SuppressWarnings("serial")
private static class ConstructorDependencyDescriptor extends DependencyDescriptor {
@Nullable
private volatile String shortcut;
public ConstructorDependencyDescriptor(MethodParameter methodParameter, boolean required) {
super(methodParameter, required);
}
public void setShortcut(@Nullable String shortcut) {
this.shortcut = shortcut;
}
public boolean hasShortcut() {
return (this.shortcut != null);
}
@Override
public Object resolveShortcut(BeanFactory beanFactory) {
String shortcut = this.shortcut;
return (shortcut != null ? beanFactory.getBean(shortcut, getDependencyType()) : null);
}
}
} }

150
spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java

@ -72,6 +72,8 @@ import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.testfixture.io.SerializationTestUtils; import org.springframework.core.testfixture.io.SerializationTestUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -131,6 +133,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bean = bf.getBean("annotatedBean", ResourceInjectionBean.class); bean = bf.getBean("annotatedBean", ResourceInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(tb); assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb); assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
} }
@Test @Test
@ -152,10 +156,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean()).isNull(); assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull(); assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean3()).isNull();
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
} }
@Test @Test
void resourceInjectionWithSometimesNullBean() { void resourceInjectionWithSometimesNullBeanEarly() {
RootBeanDefinition bd = new RootBeanDefinition(OptionalResourceInjectionBean.class); RootBeanDefinition bd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd); bf.registerBeanDefinition("annotatedBean", bd);
@ -170,6 +176,18 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean2()).isNull(); assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = false;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = true;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull();
assertThat(bean.getTestBean2()).isNotNull();
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = true; SometimesNullFactoryMethods.active = true;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull(); assertThat(bean.getTestBean()).isNotNull();
@ -188,12 +206,43 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean2()).isNull(); assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean3()).isNull();
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
void resourceInjectionWithSometimesNullBeanLate() {
RootBeanDefinition bd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
RootBeanDefinition tb = new RootBeanDefinition(SometimesNullFactoryMethods.class);
tb.setFactoryMethodName("createTestBean");
tb.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("testBean", tb);
SometimesNullFactoryMethods.active = true;
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull();
assertThat(bean.getTestBean2()).isNotNull();
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = true; SometimesNullFactoryMethods.active = true;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull(); assertThat(bean.getTestBean()).isNotNull();
assertThat(bean.getTestBean2()).isNotNull(); assertThat(bean.getTestBean2()).isNotNull();
assertThat(bean.getTestBean3()).isNotNull(); assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = false;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = false;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = true; SometimesNullFactoryMethods.active = true;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull(); assertThat(bean.getTestBean()).isNotNull();
@ -205,6 +254,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean()).isNull(); assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull(); assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean3()).isNull();
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
} }
@Test @Test
@ -233,10 +284,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getNestedTestBean()).isSameAs(ntb); assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf); assertThat(bean.getBeanFactory()).isSameAs(bf);
String[] depBeans = bf.getDependenciesForBean("annotatedBean"); assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean", "nestedTestBean"});
assertThat(depBeans.length).isEqualTo(2);
assertThat(depBeans[0]).isEqualTo("testBean");
assertThat(depBeans[1]).isEqualTo("nestedTestBean");
} }
@Test @Test
@ -719,6 +767,9 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean4()).isSameAs(tb); assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(ntb); assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf); assertThat(bean.getBeanFactory()).isSameAs(bf);
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(
new String[] {"testBean", "nestedTestBean", ObjectUtils.identityToString(bf)});
} }
@Test @Test
@ -881,6 +932,80 @@ public class AutowiredAnnotationBeanPostProcessorTests {
.satisfies(methodParameterDeclaredOn(ConstructorWithoutFallbackBean.class)); .satisfies(methodParameterDeclaredOn(ConstructorWithoutFallbackBean.class));
} }
@Test
void constructorResourceInjectionWithSometimesNullBeanEarly() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorWithNullableArgument.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
RootBeanDefinition tb = new RootBeanDefinition(SometimesNullFactoryMethods.class);
tb.setFactoryMethodName("createTestBean");
tb.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("testBean", tb);
SometimesNullFactoryMethods.active = false;
ConstructorWithNullableArgument bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = true;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = true;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
void constructorResourceInjectionWithSometimesNullBeanLate() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorWithNullableArgument.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
RootBeanDefinition tb = new RootBeanDefinition(SometimesNullFactoryMethods.class);
tb.setFactoryMethodName("createTestBean");
tb.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("testBean", tb);
SometimesNullFactoryMethods.active = true;
ConstructorWithNullableArgument bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = true;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = true;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test @Test
public void testConstructorResourceInjectionWithCollectionAndNullFromFactoryBean() { public void testConstructorResourceInjectionWithCollectionAndNullFromFactoryBean() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition( bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(
@ -2804,6 +2929,21 @@ public class AutowiredAnnotationBeanPostProcessorTests {
} }
public static class ConstructorWithNullableArgument {
protected ITestBean testBean3;
@Autowired(required = false)
public ConstructorWithNullableArgument(@Nullable ITestBean testBean3) {
this.testBean3 = testBean3;
}
public ITestBean getTestBean3() {
return this.testBean3;
}
}
public static class ConstructorsCollectionResourceInjectionBean { public static class ConstructorsCollectionResourceInjectionBean {
protected ITestBean testBean3; protected ITestBean testBean3;

12
spring-core/src/main/java/org/springframework/util/MethodInvoker.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -123,8 +123,8 @@ public class MethodInvoker {
/** /**
* Set a fully qualified static method name to invoke, * Set a fully qualified static method name to invoke,
* e.g. "example.MyExampleClass.myExampleMethod". * e.g. "example.MyExampleClass.myExampleMethod". This is a
* Convenient alternative to specifying targetClass and targetMethod. * convenient alternative to specifying targetClass and targetMethod.
* @see #setTargetClass * @see #setTargetClass
* @see #setTargetMethod * @see #setTargetMethod
*/ */
@ -157,14 +157,16 @@ public class MethodInvoker {
public void prepare() throws ClassNotFoundException, NoSuchMethodException { public void prepare() throws ClassNotFoundException, NoSuchMethodException {
if (this.staticMethod != null) { if (this.staticMethod != null) {
int lastDotIndex = this.staticMethod.lastIndexOf('.'); int lastDotIndex = this.staticMethod.lastIndexOf('.');
if (lastDotIndex == -1 || lastDotIndex == this.staticMethod.length()) { if (lastDotIndex == -1 || lastDotIndex == this.staticMethod.length() - 1) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"staticMethod must be a fully qualified class plus method name: " + "staticMethod must be a fully qualified class plus method name: " +
"e.g. 'example.MyExampleClass.myExampleMethod'"); "e.g. 'example.MyExampleClass.myExampleMethod'");
} }
String className = this.staticMethod.substring(0, lastDotIndex); String className = this.staticMethod.substring(0, lastDotIndex);
String methodName = this.staticMethod.substring(lastDotIndex + 1); String methodName = this.staticMethod.substring(lastDotIndex + 1);
this.targetClass = resolveClassName(className); if (this.targetClass == null || !this.targetClass.getName().equals(className)) {
this.targetClass = resolveClassName(className);
}
this.targetMethod = methodName; this.targetMethod = methodName;
} }

Loading…
Cancel
Save