Skip to content

Commit ee07e21

Browse files
Fix non-root namespace bean
injection timing issue (#2579)
1 parent bc5ab1d commit ee07e21

File tree

3 files changed

+171
-16
lines changed

3 files changed

+171
-16
lines changed

temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/NonRootBeanPostProcessor.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,41 +43,41 @@ public class NonRootBeanPostProcessor implements BeanPostProcessor, BeanFactoryA
4343
private static final Logger log = LoggerFactory.getLogger(NonRootBeanPostProcessor.class);
4444

4545
private ConfigurableListableBeanFactory beanFactory;
46+
private boolean beansCreated = false;
4647

4748
private final @Nonnull TemporalProperties temporalProperties;
48-
private final @Nullable List<NonRootNamespaceProperties> namespaceProperties;
4949
private @Nullable Tracer tracer;
5050
private @Nullable TestWorkflowEnvironmentAdapter testWorkflowEnvironment;
5151
private @Nullable Scope metricsScope;
5252

5353
public NonRootBeanPostProcessor(@Nonnull TemporalProperties temporalProperties) {
5454
this.temporalProperties = temporalProperties;
55-
this.namespaceProperties = temporalProperties.getNamespaces();
5655
}
5756

5857
@Override
5958
public Object postProcessAfterInitialization(@Nonnull Object bean, @Nonnull String beanName)
6059
throws BeansException {
6160
if (bean instanceof NamespaceTemplate && beanName.equals("temporalRootNamespaceTemplate")) {
62-
if (namespaceProperties != null) {
63-
// If there are non-root namespaces, we need to inject beans for each of them. Look
64-
// up the bean manually instead of using @Autowired to avoid circular dependencies or
65-
// causing the dependency to
66-
// get initialized to early and skip post-processing.
67-
//
68-
// Note: We don't use @Lazy here because these dependencies are optional and @Lazy doesn't
69-
// interact well with
70-
// optional dependencies.
71-
metricsScope = findBean("temporalMetricsScope", Scope.class);
72-
tracer = findBean(Tracer.class);
73-
testWorkflowEnvironment =
74-
findBean("temporalTestWorkflowEnvironment", TestWorkflowEnvironmentAdapter.class);
75-
namespaceProperties.forEach(this::injectBeanByNonRootNamespace);
61+
if (!beansCreated) {
62+
createNonRootNamespaceBeans();
63+
beansCreated = true;
7664
}
7765
}
7866
return bean;
7967
}
8068

69+
private void createNonRootNamespaceBeans() {
70+
List<NonRootNamespaceProperties> namespaces = temporalProperties.getNamespaces();
71+
if (namespaces != null) {
72+
// Look up dependencies that might not be available at bean definition time
73+
metricsScope = findBean("temporalMetricsScope", Scope.class);
74+
tracer = findBean(Tracer.class);
75+
testWorkflowEnvironment =
76+
findBean("temporalTestWorkflowEnvironment", TestWorkflowEnvironmentAdapter.class);
77+
namespaces.forEach(this::injectBeanByNonRootNamespace);
78+
}
79+
}
80+
8181
private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
8282
String beanPrefix = MoreObjects.firstNonNull(ns.getAlias(), ns.getNamespace());
8383
DataConverter dataConverterByNamespace = findBeanByNamespace(beanPrefix, DataConverter.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package io.temporal.spring.boot.autoconfigure;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.mockito.Mockito.when;
5+
6+
import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties;
7+
import io.temporal.spring.boot.autoconfigure.template.NamespaceTemplate;
8+
import java.util.Collections;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.Test;
11+
import org.junit.jupiter.api.extension.ExtendWith;
12+
import org.mockito.Mock;
13+
import org.mockito.junit.jupiter.MockitoExtension;
14+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
15+
16+
@ExtendWith(MockitoExtension.class)
17+
public class NonRootBeanPostProcessorTest {
18+
19+
@Mock private TemporalProperties temporalProperties;
20+
@Mock private ConfigurableListableBeanFactory beanFactory;
21+
@Mock private NamespaceTemplate rootNamespaceTemplate;
22+
23+
private NonRootBeanPostProcessor processor;
24+
25+
@BeforeEach
26+
void setUp() {
27+
processor = new NonRootBeanPostProcessor(temporalProperties);
28+
processor.setBeanFactory(beanFactory);
29+
}
30+
31+
@Test
32+
void shouldNotProcessBeansWhenNoNamespaces() {
33+
when(temporalProperties.getNamespaces()).thenReturn(null);
34+
Object result =
35+
processor.postProcessAfterInitialization(
36+
rootNamespaceTemplate, "temporalRootNamespaceTemplate");
37+
assertThat(result).isSameAs(rootNamespaceTemplate);
38+
}
39+
40+
@Test
41+
void shouldNotProcessBeansWhenEmptyNamespaces() {
42+
when(temporalProperties.getNamespaces()).thenReturn(Collections.emptyList());
43+
Object result =
44+
processor.postProcessAfterInitialization(
45+
rootNamespaceTemplate, "temporalRootNamespaceTemplate");
46+
assertThat(result).isSameAs(rootNamespaceTemplate);
47+
}
48+
49+
@Test
50+
void shouldOnlyProcessRootNamespaceTemplate() {
51+
Object otherBean = new Object();
52+
Object result = processor.postProcessAfterInitialization(otherBean, "someOtherBean");
53+
assertThat(result).isSameAs(otherBean);
54+
}
55+
56+
@Test
57+
void shouldProcessOnlyOnceEvenWithMultipleCalls() {
58+
when(temporalProperties.getNamespaces()).thenReturn(Collections.emptyList());
59+
Object result1 =
60+
processor.postProcessAfterInitialization(
61+
rootNamespaceTemplate, "temporalRootNamespaceTemplate");
62+
Object result2 =
63+
processor.postProcessAfterInitialization(
64+
rootNamespaceTemplate, "temporalRootNamespaceTemplate");
65+
assertThat(result1).isSameAs(rootNamespaceTemplate);
66+
assertThat(result2).isSameAs(rootNamespaceTemplate);
67+
}
68+
69+
@Test
70+
void shouldReturnOriginalBeanUnchanged() {
71+
when(temporalProperties.getNamespaces()).thenReturn(Collections.emptyList());
72+
Object result =
73+
processor.postProcessAfterInitialization(
74+
rootNamespaceTemplate, "temporalRootNamespaceTemplate");
75+
assertThat(result).isSameAs(rootNamespaceTemplate);
76+
}
77+
78+
@Test
79+
void shouldHandleNullNamespaceGracefully() {
80+
when(temporalProperties.getNamespaces()).thenReturn(null);
81+
Object result =
82+
processor.postProcessAfterInitialization(
83+
rootNamespaceTemplate, "temporalRootNamespaceTemplate");
84+
assertThat(result).isSameAs(rootNamespaceTemplate);
85+
}
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package io.temporal.spring.boot.autoconfigure;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import org.junit.jupiter.api.BeforeEach;
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.TestInstance;
8+
import org.junit.jupiter.api.Timeout;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
11+
import org.springframework.boot.test.context.SpringBootTest;
12+
import org.springframework.context.ConfigurableApplicationContext;
13+
import org.springframework.test.context.ActiveProfiles;
14+
15+
@SpringBootTest(classes = NonRootNamespaceBeanAvailabilityTest.Configuration.class)
16+
@ActiveProfiles(profiles = "multi-namespaces")
17+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
18+
public class NonRootNamespaceBeanAvailabilityTest {
19+
20+
@Autowired ConfigurableApplicationContext applicationContext;
21+
22+
@BeforeEach
23+
void setUp() {
24+
applicationContext.start();
25+
}
26+
27+
@Test
28+
@Timeout(value = 10)
29+
public void shouldRegisterNonRootNamespaceBeansInContext() {
30+
assertThat(applicationContext.containsBean("ns1WorkflowClient")).isTrue();
31+
assertThat(applicationContext.containsBean("namespace2WorkflowClient")).isTrue();
32+
assertThat(applicationContext.containsBean("ns1ScheduleClient")).isTrue();
33+
assertThat(applicationContext.containsBean("namespace2ScheduleClient")).isTrue();
34+
assertThat(applicationContext.containsBean("ns1WorkerFactory")).isTrue();
35+
assertThat(applicationContext.containsBean("namespace2WorkerFactory")).isTrue();
36+
}
37+
38+
@Test
39+
@Timeout(value = 10)
40+
public void shouldRegisterNonRootNamespaceTemplateBeansInContext() {
41+
assertThat(applicationContext.containsBean("ns1NamespaceTemplate")).isTrue();
42+
assertThat(applicationContext.containsBean("namespace2NamespaceTemplate")).isTrue();
43+
assertThat(applicationContext.containsBean("ns1ClientTemplate")).isTrue();
44+
assertThat(applicationContext.containsBean("namespace2ClientTemplate")).isTrue();
45+
assertThat(applicationContext.containsBean("ns1WorkersTemplate")).isTrue();
46+
assertThat(applicationContext.containsBean("namespace2WorkersTemplate")).isTrue();
47+
}
48+
49+
@Test
50+
@Timeout(value = 10)
51+
public void shouldHaveBeansAvailableForLookup() {
52+
assertThat(applicationContext.getBean("ns1WorkflowClient")).isNotNull();
53+
assertThat(applicationContext.getBean("namespace2WorkflowClient")).isNotNull();
54+
assertThat(applicationContext.getBean("ns1ScheduleClient")).isNotNull();
55+
assertThat(applicationContext.getBean("namespace2ScheduleClient")).isNotNull();
56+
assertThat(applicationContext.getBean("ns1WorkerFactory")).isNotNull();
57+
assertThat(applicationContext.getBean("namespace2WorkerFactory")).isNotNull();
58+
}
59+
60+
@Test
61+
@Timeout(value = 10)
62+
public void applicationContextStartsSuccessfully() {
63+
assertThat(applicationContext.isRunning()).isTrue();
64+
assertThat(applicationContext.isActive()).isTrue();
65+
}
66+
67+
@EnableAutoConfiguration
68+
public static class Configuration {}
69+
}

0 commit comments

Comments
 (0)