1717package org .springframework .context .aot ;
1818
1919import java .util .function .BiConsumer ;
20+ import java .util .function .BiFunction ;
2021
2122import org .junit .jupiter .api .Test ;
2223
2324import org .springframework .aot .test .generate .TestGenerationContext ;
24- import org .springframework .beans .factory .BeanFactory ;
25- import org .springframework .beans .factory .BeanFactoryAware ;
2625import org .springframework .beans .factory .annotation .Autowired ;
2726import org .springframework .beans .factory .annotation .AutowiredAnnotationBeanPostProcessor ;
2827import org .springframework .beans .factory .config .BeanDefinition ;
3635import static org .assertj .core .api .Assertions .assertThat ;
3736
3837/**
39- * Reproduction test for <a href="https://github.com/spring-projects/spring-framework/issues/35871">gh-35871</a>.
40- * Prototype bean with {@code @Autowired} setter injection fails when retrieved
41- * with explicit constructor args via {@code getBean(name, args)} in AOT mode.
38+ * Tests for <a href="https://github.com/spring-projects/spring-framework/issues/35871">gh-35871</a>.
39+ *
40+ * <p>Verifies that {@code getBean(name, args)} produces beans with the same
41+ * observable state in both regular and AOT modes. In AOT mode, an
42+ * {@code InstanceSupplier} with {@code andThen()} post-processing is generated
43+ * for {@code @Autowired} setter injection. When explicit constructor args
44+ * bypass the instance supplier, the post-processing must still be applied.
4245 */
4346class PrototypeWithArgsAotTests {
4447
4548 @ Test
46- void prototypeWithArgsAndAutowiredSetterInAotMode () {
47- GenericApplicationContext applicationContext = new GenericApplicationContext ();
48- registerBeanPostProcessor (applicationContext );
49-
50- // Register a singleton bean
51- applicationContext .registerBeanDefinition ("singletonService" ,
52- new RootBeanDefinition (SingletonService .class ));
53-
54- // Register a prototype bean with constructor arg + @Autowired setter
55- RootBeanDefinition prototypeDef = new RootBeanDefinition (PrototypeService .class );
56- prototypeDef .setScope (BeanDefinition .SCOPE_PROTOTYPE );
57- applicationContext .registerBeanDefinition ("prototypeService" , prototypeDef );
58-
59- testCompiledResult (applicationContext , (initializer , compiled ) -> {
60- GenericApplicationContext freshContext = toFreshApplicationContext (initializer );
61-
62- // This is the key: getBean with explicit constructor args bypasses instance supplier
63- PrototypeService instance = (PrototypeService ) freshContext .getBean ("prototypeService" , "testName" );
64-
65- assertThat (instance .getName ()).isEqualTo ("testName" );
66- assertThat (instance .getBeanFactory ())
67- .as ("BeanFactoryAware should still work" )
68- .isNotNull ();
69- assertThat (instance .getSingletonService ())
70- .as ("@Autowired setter should be called even when instance supplier is bypassed" )
71- .isNotNull ();
49+ void prototypeWithAutowiredSetterAndExplicitArgs () {
50+ assertAotAndRegularModesProduceSameResult ((ctx , args ) -> {
51+ PrototypeService instance = (PrototypeService ) ctx .getBean ("prototypeService" , args );
52+ return new BeanSnapshot (instance .getName (), instance .getSingletonService () != null );
53+ }, "testName" );
54+ }
7255
73- freshContext .close ();
56+ /**
57+ * Sets up a context with a prototype bean that has an {@code @Autowired}
58+ * setter, then asserts that the given extractor produces the same result
59+ * in regular mode and in AOT-compiled mode.
60+ */
61+ private static void assertAotAndRegularModesProduceSameResult (
62+ BiFunction <GenericApplicationContext , Object [], BeanSnapshot > extractor , Object ... args ) {
63+
64+ // Regular mode
65+ GenericApplicationContext regularContext = createApplicationContext ();
66+ regularContext .refresh ();
67+ BeanSnapshot regularResult = extractor .apply (regularContext , args );
68+ regularContext .close ();
69+
70+ // AOT mode
71+ GenericApplicationContext aotSource = createApplicationContext ();
72+ testCompiledResult (aotSource , (initializer , compiled ) -> {
73+ GenericApplicationContext aotContext = toFreshApplicationContext (initializer );
74+ BeanSnapshot aotResult = extractor .apply (aotContext , args );
75+ assertThat (aotResult )
76+ .as ("AOT mode should produce the same result as regular mode" )
77+ .isEqualTo (regularResult );
78+ aotContext .close ();
7479 });
7580 }
7681
77- private static void registerBeanPostProcessor (GenericApplicationContext applicationContext ) {
78- applicationContext .registerBeanDefinition (
82+ private static GenericApplicationContext createApplicationContext () {
83+ GenericApplicationContext context = new GenericApplicationContext ();
84+ context .registerBeanDefinition (
7985 AnnotationConfigUtils .AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME ,
8086 new RootBeanDefinition (AutowiredAnnotationBeanPostProcessor .class ));
87+ context .registerBeanDefinition ("singletonService" ,
88+ new RootBeanDefinition (SingletonService .class ));
89+ RootBeanDefinition prototypeDef = new RootBeanDefinition (PrototypeService .class );
90+ prototypeDef .setScope (BeanDefinition .SCOPE_PROTOTYPE );
91+ context .registerBeanDefinition ("prototypeService" , prototypeDef );
92+ return context ;
8193 }
8294
8395 private static TestGenerationContext processAheadOfTime (GenericApplicationContext applicationContext ) {
@@ -109,16 +121,17 @@ private static GenericApplicationContext toFreshApplicationContext(
109121 }
110122
111123
124+ record BeanSnapshot (String name , boolean hasInjectedDependency ) {
125+ }
126+
112127 public static class SingletonService {
113- public String getValue () {
114- return "singleton" ;
115- }
116128 }
117129
118- public static class PrototypeService implements BeanFactoryAware {
130+ public static class PrototypeService {
131+
119132 private final String name ;
133+
120134 private SingletonService singletonService ;
121- private BeanFactory beanFactory ;
122135
123136 public PrototypeService (String name ) {
124137 this .name = name ;
@@ -136,14 +149,5 @@ public SingletonService getSingletonService() {
136149 public void setSingletonService (SingletonService singletonService ) {
137150 this .singletonService = singletonService ;
138151 }
139-
140- @ Override
141- public void setBeanFactory (BeanFactory beanFactory ) {
142- this .beanFactory = beanFactory ;
143- }
144-
145- public BeanFactory getBeanFactory () {
146- return beanFactory ;
147- }
148152 }
149153}
0 commit comments