Skip to content

Commit a6099fa

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 a6099fa

File tree

4 files changed

+820
-0
lines changed

4 files changed

+820
-0
lines changed

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,20 @@ 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+
// Note: this runs after early singleton caching. For the typical case where
605+
// post-processing returns the same instance, this is safe. If post-processing
606+
// wraps the instance, circular reference detection at the end of this method
607+
// will detect the mismatch and throw BeanCurrentlyInCreationException.
608+
if (args != null && mbd.getInstanceSupplier() instanceof InstanceSupplier<?>) {
609+
exposedObject = applyInstanceSupplierPostProcessing(exposedObject, beanName, mbd);
610+
if (exposedObject != instanceWrapper.getWrappedInstance()) {
611+
instanceWrapper = new BeanWrapperImpl(exposedObject);
612+
initBeanWrapper(instanceWrapper);
613+
}
614+
}
601615
try {
602616
populateBean(beanName, mbd, instanceWrapper);
603617
exposedObject = initializeBean(beanName, exposedObject, mbd);
@@ -1285,6 +1299,23 @@ private BeanWrapper obtainFromSupplier(Supplier<?> supplier, String beanName, Ro
12851299
return supplier.get();
12861300
}
12871301

1302+
/**
1303+
* Apply any post-processing from the bean definition's
1304+
* {@link InstanceSupplier} to an already-created instance. This is called
1305+
* when the instance supplier was bypassed during creation (for example,
1306+
* when explicit constructor arguments were provided) but the post-processing
1307+
* registered via {@link InstanceSupplier#andThen} still needs to be applied.
1308+
* @param bean the already-created bean instance
1309+
* @param beanName the name of the bean
1310+
* @param mbd the bean definition for the bean
1311+
* @return the post-processed bean instance
1312+
* @since 7.0
1313+
* @see InstanceSupplier#postProcessInstance
1314+
*/
1315+
protected Object applyInstanceSupplierPostProcessing(Object bean, String beanName, RootBeanDefinition mbd) {
1316+
return bean;
1317+
}
1318+
12881319
/**
12891320
* Overridden in order to implicitly register the currently created bean as
12901321
* 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
}

0 commit comments

Comments
 (0)