Skip to content

Commit de9fe52

Browse files
vlsiclaude
andcommitted
Apply InstanceSupplier post-processing when bypassed by explicit args
When getBean(name, args) is called with explicit constructor arguments, the InstanceSupplier is intentionally bypassed (gh-32657). However, in AOT mode, @Autowired setter/field injection is baked into the InstanceSupplier's andThen() chain and AutowiredAnnotationBeanPostProcessor is excluded from runtime registration. This means the autowiring post-processing is lost when the supplier is bypassed. Add InstanceSupplier.postProcessInstance() to allow applying only the post-processing steps (from andThen()) to an already-created instance without re-invoking creation. Call this from doCreateBean() when the instance supplier was bypassed due to explicit args. Closes gh-35871 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Vladimir Sitnikov <sitnikov.vladimir@gmail.com>
1 parent 227bc19 commit de9fe52

File tree

4 files changed

+223
-0
lines changed

4 files changed

+223
-0
lines changed

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,18 @@ protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable
598598

599599
// Initialize the bean instance.
600600
Object exposedObject = bean;
601+
602+
// If the instance supplier was bypassed (e.g. explicit args were provided),
603+
// apply any post-processing that was registered via InstanceSupplier.andThen().
604+
// This is done after singleton caching since it only applies to non-singleton
605+
// beans (singletons don't use explicit args with getBean).
606+
if (args != null && mbd.getInstanceSupplier() instanceof InstanceSupplier<?>) {
607+
exposedObject = applyInstanceSupplierPostProcessing(exposedObject, beanName, mbd);
608+
if (exposedObject != instanceWrapper.getWrappedInstance()) {
609+
instanceWrapper = new BeanWrapperImpl(exposedObject);
610+
initBeanWrapper(instanceWrapper);
611+
}
612+
}
601613
try {
602614
populateBean(beanName, mbd, instanceWrapper);
603615
exposedObject = initializeBean(beanName, exposedObject, mbd);
@@ -1285,6 +1297,23 @@ private BeanWrapper obtainFromSupplier(Supplier<?> supplier, String beanName, Ro
12851297
return supplier.get();
12861298
}
12871299

1300+
/**
1301+
* Apply any post-processing from the bean definition's
1302+
* {@link InstanceSupplier} to an already-created instance. This is called
1303+
* when the instance supplier was bypassed during creation (for example,
1304+
* when explicit constructor arguments were provided) but the post-processing
1305+
* registered via {@link InstanceSupplier#andThen} still needs to be applied.
1306+
* @param bean the already-created bean instance
1307+
* @param beanName the name of the bean
1308+
* @param mbd the bean definition for the bean
1309+
* @return the post-processed bean instance
1310+
* @since 7.0
1311+
* @see InstanceSupplier#postProcessInstance
1312+
*/
1313+
protected Object applyInstanceSupplierPostProcessing(Object bean, String beanName, RootBeanDefinition mbd) {
1314+
return bean;
1315+
}
1316+
12881317
/**
12891318
* Overridden in order to implicitly register the currently created bean as
12901319
* dependent on further beans getting programmatically retrieved during a

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,26 @@ protected boolean isBeanEligibleForMetadataCaching(String beanName) {
10261026
return super.obtainInstanceFromSupplier(supplier, beanName, mbd);
10271027
}
10281028

1029+
@Override
1030+
@SuppressWarnings("unchecked")
1031+
protected Object applyInstanceSupplierPostProcessing(Object bean, String beanName, RootBeanDefinition mbd) {
1032+
InstanceSupplier<?> instanceSupplier = (InstanceSupplier<?>) mbd.getInstanceSupplier();
1033+
if (instanceSupplier != null) {
1034+
try {
1035+
return ((InstanceSupplier<Object>) instanceSupplier)
1036+
.postProcessInstance(RegisteredBean.of(this, beanName, mbd), bean);
1037+
}
1038+
catch (RuntimeException ex) {
1039+
throw ex;
1040+
}
1041+
catch (Exception ex) {
1042+
throw new BeanCreationException(beanName,
1043+
"Post-processing of instance supplier failed", ex);
1044+
}
1045+
}
1046+
return bean;
1047+
}
1048+
10291049
@Override
10301050
protected void cacheMergedBeanDefinition(RootBeanDefinition mbd, String beanName) {
10311051
super.cacheMergedBeanDefinition(mbd, beanName);

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,25 @@ default T getWithException() {
6464
return null;
6565
}
6666

67+
/**
68+
* Apply only the post-processing steps of this supplier to an
69+
* already-created instance, without invoking the instance creation itself.
70+
* <p>This is used when the instance was created through a different path
71+
* (for example, when explicit constructor arguments bypass the instance
72+
* supplier) but post-processing registered via {@link #andThen} still
73+
* needs to be applied.
74+
* @param registeredBean the registered bean
75+
* @param instance the already-created instance to post-process
76+
* @return the post-processed instance
77+
* @throws Exception on error
78+
* @since 7.0
79+
* @see #andThen
80+
*/
81+
@SuppressWarnings("unchecked")
82+
default T postProcessInstance(RegisteredBean registeredBean, T instance) throws Exception {
83+
return instance;
84+
}
85+
6786
/**
6887
* Return a composed instance supplier that first obtains the instance from
6988
* this supplier and then applies the {@code after} function to obtain the
@@ -83,6 +102,12 @@ public V get(RegisteredBean registeredBean) throws Exception {
83102
return after.applyWithException(registeredBean, InstanceSupplier.this.get(registeredBean));
84103
}
85104
@Override
105+
@SuppressWarnings("unchecked")
106+
public V postProcessInstance(RegisteredBean registeredBean, V instance) throws Exception {
107+
T postProcessed = InstanceSupplier.this.postProcessInstance(registeredBean, (T) instance);
108+
return after.applyWithException(registeredBean, postProcessed);
109+
}
110+
@Override
86111
public @Nullable Method getFactoryMethod() {
87112
return InstanceSupplier.this.getFactoryMethod();
88113
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.aot;
18+
19+
import java.util.function.BiConsumer;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.aot.test.generate.TestGenerationContext;
24+
import org.springframework.beans.factory.BeanFactory;
25+
import org.springframework.beans.factory.BeanFactoryAware;
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
28+
import org.springframework.beans.factory.config.BeanDefinition;
29+
import org.springframework.beans.factory.support.RootBeanDefinition;
30+
import org.springframework.context.ApplicationContextInitializer;
31+
import org.springframework.context.annotation.AnnotationConfigUtils;
32+
import org.springframework.context.support.GenericApplicationContext;
33+
import org.springframework.core.test.tools.Compiled;
34+
import org.springframework.core.test.tools.TestCompiler;
35+
36+
import static org.assertj.core.api.Assertions.assertThat;
37+
38+
/**
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.
42+
*/
43+
class PrototypeWithArgsAotTests {
44+
45+
@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();
72+
73+
freshContext.close();
74+
});
75+
}
76+
77+
private static void registerBeanPostProcessor(GenericApplicationContext applicationContext) {
78+
applicationContext.registerBeanDefinition(
79+
AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
80+
new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class));
81+
}
82+
83+
private static TestGenerationContext processAheadOfTime(GenericApplicationContext applicationContext) {
84+
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
85+
TestGenerationContext generationContext = new TestGenerationContext();
86+
generator.processAheadOfTime(applicationContext, generationContext);
87+
generationContext.writeGeneratedContent();
88+
return generationContext;
89+
}
90+
91+
private static void testCompiledResult(GenericApplicationContext applicationContext,
92+
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
93+
testCompiledResult(processAheadOfTime(applicationContext), result);
94+
}
95+
96+
@SuppressWarnings("unchecked")
97+
private static void testCompiledResult(TestGenerationContext generationContext,
98+
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
99+
TestCompiler.forSystem().with(generationContext).compile(compiled ->
100+
result.accept(compiled.getInstance(ApplicationContextInitializer.class), compiled));
101+
}
102+
103+
private static GenericApplicationContext toFreshApplicationContext(
104+
ApplicationContextInitializer<GenericApplicationContext> initializer) {
105+
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
106+
initializer.initialize(freshApplicationContext);
107+
freshApplicationContext.refresh();
108+
return freshApplicationContext;
109+
}
110+
111+
112+
public static class SingletonService {
113+
public String getValue() {
114+
return "singleton";
115+
}
116+
}
117+
118+
public static class PrototypeService implements BeanFactoryAware {
119+
private final String name;
120+
private SingletonService singletonService;
121+
private BeanFactory beanFactory;
122+
123+
public PrototypeService(String name) {
124+
this.name = name;
125+
}
126+
127+
public String getName() {
128+
return name;
129+
}
130+
131+
public SingletonService getSingletonService() {
132+
return singletonService;
133+
}
134+
135+
@Autowired
136+
public void setSingletonService(SingletonService singletonService) {
137+
this.singletonService = singletonService;
138+
}
139+
140+
@Override
141+
public void setBeanFactory(BeanFactory beanFactory) {
142+
this.beanFactory = beanFactory;
143+
}
144+
145+
public BeanFactory getBeanFactory() {
146+
return beanFactory;
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)