diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java index 44e3b55f7d..c008e6537c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java @@ -32,6 +32,8 @@ import org.springframework.lang.Nullable; *
In a {@link BeanFactory} environment, every {@code ObjectProvider} obtained * from the factory will be bound to its {@code BeanFactory} for a specific bean * type, matching all provider calls against factory-registered bean definitions. + * Note that all such calls dynamically operate on the underlying factory state, + * freshly resolving the requested target object on every call. * *
As of 5.1, this interface extends {@link Iterable} and provides {@link Stream}
* support. It can be therefore be used in {@code for} loops, provides {@link #forEach}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ContextAnnotationAutowireCandidateResolver.java b/spring-context/src/main/java/org/springframework/context/annotation/ContextAnnotationAutowireCandidateResolver.java
index dcf4aa91ae..bc012d0580 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ContextAnnotationAutowireCandidateResolver.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ContextAnnotationAutowireCandidateResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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,6 +16,7 @@
package org.springframework.context.annotation;
+import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
@@ -27,7 +28,6 @@ import java.util.Set;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactory;
-import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver;
import org.springframework.beans.factory.config.DependencyDescriptor;
@@ -35,7 +35,6 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
-import org.springframework.util.Assert;
/**
* Complete implementation of the
@@ -85,47 +84,13 @@ public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotat
}
private Object buildLazyResolutionProxy(
- final DependencyDescriptor descriptor, @Nullable final String beanName, boolean classOnly) {
+ DependencyDescriptor descriptor, @Nullable String beanName, boolean classOnly) {
- BeanFactory beanFactory = getBeanFactory();
- Assert.state(beanFactory instanceof DefaultListableBeanFactory,
- "BeanFactory needs to be a DefaultListableBeanFactory");
- final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;
+ if (!(getBeanFactory() instanceof DefaultListableBeanFactory dlbf)) {
+ throw new IllegalStateException("Lazy resolution only supported with DefaultListableBeanFactory");
+ }
- TargetSource ts = new TargetSource() {
- @Override
- public Class> getTargetClass() {
- return descriptor.getDependencyType();
- }
- @Override
- @SuppressWarnings("NullAway")
- public Object getTarget() {
- Set In addition to its role for component initialization, this annotation may also be placed
* on injection points marked with {@link org.springframework.beans.factory.annotation.Autowired}
* or {@link jakarta.inject.Inject}: In that context, it leads to the creation of a
- * lazy-resolution proxy for all affected dependencies, as an alternative to using
+ * lazy-resolution proxy for the affected dependency, caching it on first access in case of
+ * a singleton or re-resolving it on every access otherwise. This is an alternative to using
* {@link org.springframework.beans.factory.ObjectFactory} or {@link jakarta.inject.Provider}.
* Please note that such a lazy-resolution proxy will always be injected; if the target
* dependency does not exist, you will only be able to find out through an exception on
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/LazyAutowiredAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/LazyAutowiredAnnotationBeanPostProcessorTests.java
index 0eecab3836..9faa292a98 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/LazyAutowiredAnnotationBeanPostProcessorTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/LazyAutowiredAnnotationBeanPostProcessorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -22,6 +22,8 @@ import java.util.List;
import org.junit.jupiter.api.Test;
+import org.springframework.aop.TargetSource;
+import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
@@ -67,7 +69,7 @@ class LazyAutowiredAnnotationBeanPostProcessorTests {
}
@Test
- void lazyResourceInjectionWithField() {
+ void lazyResourceInjectionWithField() throws Exception {
doTestLazyResourceInjection(FieldResourceInjectionBean.class);
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
@@ -84,9 +86,36 @@ class LazyAutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBeans()).isNotEmpty();
assertThat(bean.getTestBeans().get(0).getName()).isNull();
assertThat(ac.getBeanFactory().containsSingleton("testBean")).isTrue();
+
TestBean tb = (TestBean) ac.getBean("testBean");
tb.setName("tb");
assertThat(bean.getTestBean().getName()).isSameAs("tb");
+
+ assertThat(bean.getTestBeans() instanceof Advised).isTrue();
+ TargetSource targetSource = ((Advised) bean.getTestBeans()).getTargetSource();
+ assertThat(targetSource.getTarget()).isSameAs(targetSource.getTarget());
+
+ ac.close();
+ }
+
+ @Test
+ void lazyResourceInjectionWithFieldForPrototype() {
+ doTestLazyResourceInjection(FieldResourceInjectionBean.class);
+
+ AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
+ RootBeanDefinition abd = new RootBeanDefinition(FieldResourceInjectionBean.class);
+ abd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
+ ac.registerBeanDefinition("annotatedBean", abd);
+ RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class);
+ tbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
+ tbd.setLazyInit(true);
+ ac.registerBeanDefinition("testBean", tbd);
+ ac.refresh();
+
+ FieldResourceInjectionBean bean = ac.getBean("annotatedBean", FieldResourceInjectionBean.class);
+ assertThat(bean.getTestBeans()).isNotEmpty();
+ TestBean tb = bean.getTestBeans().get(0);
+ assertThat(bean.getTestBeans().get(0)).isNotSameAs(tb);
ac.close();
}