Skip to content

Commit b5a86de

Browse files
committed
Retain previous factory method in case of nested invocation with AOT
This commit harmonizes the invocation of a bean supplier with what SimpleInstantiationStrategy does. Previously, the current factory method was set to `null` once the invocation completes. This did not take into account recursive scenarios where an instance supplier triggers another instance supplier. For consistency, the thread local is removed now if we attempt to set the current method to null. SimpleInstantiationStrategy itself uses the shortcut to align the code as much as possible. Closes gh-33180
1 parent c55c644 commit b5a86de

File tree

3 files changed

+59
-14
lines changed

3 files changed

+59
-14
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -213,12 +213,13 @@ private T invokeBeanSupplier(Executable executable, ThrowingSupplier<T> beanSupp
213213
if (!(executable instanceof Method method)) {
214214
return beanSupplier.get();
215215
}
216+
Method priorInvokedFactoryMethod = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
216217
try {
217218
SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod(method);
218219
return beanSupplier.get();
219220
}
220221
finally {
221-
SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod(null);
222+
SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod(priorInvokedFactoryMethod);
222223
}
223224
}
224225

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

+12-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,12 +55,18 @@ public static Method getCurrentlyInvokedFactoryMethod() {
5555
}
5656

5757
/**
58-
* Set the factory method currently being invoked or {@code null} to reset.
58+
* Set the factory method currently being invoked or {@code null} to remove
59+
* the current value, if any.
5960
* @param method the factory method currently being invoked or {@code null}
6061
* @since 6.0
6162
*/
6263
public static void setCurrentlyInvokedFactoryMethod(@Nullable Method method) {
63-
currentlyInvokedFactoryMethod.set(method);
64+
if (method != null) {
65+
currentlyInvokedFactoryMethod.set(method);
66+
}
67+
else {
68+
currentlyInvokedFactoryMethod.remove();
69+
}
6470
}
6571

6672

@@ -134,22 +140,17 @@ public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, Bean
134140
try {
135141
ReflectionUtils.makeAccessible(factoryMethod);
136142

137-
Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get();
143+
Method priorInvokedFactoryMethod = getCurrentlyInvokedFactoryMethod();
138144
try {
139-
currentlyInvokedFactoryMethod.set(factoryMethod);
145+
setCurrentlyInvokedFactoryMethod(factoryMethod);
140146
Object result = factoryMethod.invoke(factoryBean, args);
141147
if (result == null) {
142148
result = new NullBean();
143149
}
144150
return result;
145151
}
146152
finally {
147-
if (priorInvokedFactoryMethod != null) {
148-
currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod);
149-
}
150-
else {
151-
currentlyInvokedFactoryMethod.remove();
152-
}
153+
setCurrentlyInvokedFactoryMethod(priorInvokedFactoryMethod);
153154
}
154155
}
155156
catch (IllegalArgumentException ex) {

spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanInstanceSupplierTests.java

+44-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import java.util.Set;
28+
import java.util.concurrent.atomic.AtomicReference;
2829
import java.util.function.Consumer;
2930
import java.util.stream.Stream;
3031

@@ -51,6 +52,7 @@
5152
import org.springframework.beans.factory.support.InstanceSupplier;
5253
import org.springframework.beans.factory.support.RegisteredBean;
5354
import org.springframework.beans.factory.support.RootBeanDefinition;
55+
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
5456
import org.springframework.core.env.Environment;
5557
import org.springframework.core.io.DefaultResourceLoader;
5658
import org.springframework.core.io.ResourceLoader;
@@ -292,6 +294,33 @@ void getNestedWithNoGeneratorUsesReflection(Source source) throws Exception {
292294
assertThat(instance).isEqualTo("1");
293295
}
294296

297+
@Test // gh-33180
298+
void getWithNestedInvocationRetainsFactoryMethod() throws Exception {
299+
AtomicReference<Method> testMethodReference = new AtomicReference<>();
300+
AtomicReference<Method> anotherMethodReference = new AtomicReference<>();
301+
302+
BeanInstanceSupplier<Object> nestedInstanceSupplier = BeanInstanceSupplier
303+
.forFactoryMethod(AnotherTestStringFactory.class, "another")
304+
.withGenerator(registeredBean -> {
305+
anotherMethodReference.set(SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod());
306+
return "Another";
307+
});
308+
RegisteredBean nestedRegisteredBean = new Source(String.class, nestedInstanceSupplier).registerBean(this.beanFactory);
309+
BeanInstanceSupplier<Object> instanceSupplier = BeanInstanceSupplier
310+
.forFactoryMethod(TestStringFactory.class, "test")
311+
.withGenerator(registeredBean -> {
312+
Object nested = nestedInstanceSupplier.get(nestedRegisteredBean);
313+
testMethodReference.set(SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod());
314+
return "custom" + nested;
315+
});
316+
RegisteredBean registeredBean = new Source(String.class, instanceSupplier).registerBean(this.beanFactory);
317+
Object value = instanceSupplier.get(registeredBean);
318+
319+
assertThat(value).isEqualTo("customAnother");
320+
assertThat(testMethodReference.get()).isEqualTo(instanceSupplier.getFactoryMethod());
321+
assertThat(anotherMethodReference.get()).isEqualTo(nestedInstanceSupplier.getFactoryMethod());
322+
}
323+
295324
@Test
296325
void resolveArgumentsWithNoArgConstructor() {
297326
RootBeanDefinition beanDefinition = new RootBeanDefinition(
@@ -1030,4 +1059,18 @@ static class MethodOnInterfaceImpl implements MethodOnInterface {
10301059

10311060
}
10321061

1062+
static class TestStringFactory {
1063+
1064+
String test() {
1065+
return "test";
1066+
}
1067+
}
1068+
1069+
static class AnotherTestStringFactory {
1070+
1071+
String another() {
1072+
return "another";
1073+
}
1074+
}
1075+
10331076
}

0 commit comments

Comments
 (0)