Skip to content

Commit 775bfd6

Browse files
authored
GH-9442: Register dynamic flows as singletons
Fixes: #9442 Issue link: #9442 When we register a `BeanDefinition`, its metadata is cached in the `DefaultListableBeanFactory.mergedBeanDefinitionHolders` and it is not removed when we destroy bean. This causes a memory leak with dynamic integration flows where even we destroy beans, their metadata is still cached. This has more serious impact when random ids are used for dynamic integration flows. * Rework `IntegrationFlowContext` logic to register singletons instead of `BeanDefinition` * Adjust `IntegrationFlowBeanPostProcessor` logic to register singletons or `BeanDefinition` according to the presence of `BeanDefinition` of the `IntegrationFlow` we are processing * Introduce `ComponentSourceAware` to mimic a `BeanDefinition` metadata logic for bean source and its description. This info helps a lot with exceptions where class in the `IntegrationFlow` might be fully from a Spring Integration package. So, to give a clue what end-user code is related to the exception, such a `ComponentSourceAware` is there to preserver and transfer "bean source" * Implement `ComponentSourceAware` in the `IntegrationObjectSupport` since this is a central place where we generate info for the Spring Integration exceptions * Implement `ComponentSourceAware` in the `StandardIntegrationFlow` to propagate bean source info down to its components for the mentioned `IntegrationObjectSupport` logic * Introduce inner `StandardIntegrationFlowContext.IntegrationFlowComponentSourceAwareAdapter` to be able to transfer "bean source" info from builder down to the target `IntegrationFlow` bean
1 parent d5d9f75 commit 775bfd6

File tree

9 files changed

+466
-145
lines changed

9 files changed

+466
-145
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2024 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.integration.context;
18+
19+
import org.springframework.beans.factory.BeanNameAware;
20+
import org.springframework.lang.Nullable;
21+
22+
/**
23+
* The contract to supply and provide useful information about
24+
* a bean definition (or singleton) source - the place where this bean is declared.
25+
* Usually populated from a respective {@link org.springframework.beans.factory.config.BeanDefinition}
26+
* or via Spring Integration infrastructure.
27+
* <p>
28+
* The information from this contract is typically used from exceptions to easy determine
29+
* the place in the application resources where this bean is declared.
30+
*
31+
* @author Artem Bilan
32+
*
33+
* @since 6.4
34+
*
35+
* @see org.springframework.beans.factory.config.BeanDefinition
36+
*/
37+
public interface ComponentSourceAware extends BeanNameAware {
38+
39+
/**
40+
* Set a configuration source {@code Object} for this bean definition.
41+
* For normal {@link org.springframework.beans.factory.config.BeanDefinition} this is supplied
42+
* by application context automatically.
43+
* Could be useful when bean is registered at runtime via
44+
* {@link org.springframework.beans.factory.config.SingletonBeanRegistry#registerSingleton(String, Object)}
45+
* @param source the configuration source
46+
*/
47+
void setComponentSource(Object source);
48+
49+
/**
50+
* Return the configuration source {@code Object} for this bean (maybe {@code null}).
51+
* Usually (if not set explicitly) a {@link org.springframework.beans.factory.config.BeanDefinition#getSource()}.
52+
* @return the configuration source for the bean (if any).
53+
*/
54+
@Nullable
55+
Object getComponentSource();
56+
57+
/**
58+
* Set a human-readable description of this bean.
59+
* For normal bean definition a {@link org.springframework.beans.factory.config.BeanDefinition#getDescription()}
60+
* is used.
61+
* @param description the bean description
62+
*/
63+
void setComponentDescription(String description);
64+
65+
/**
66+
* Return a human-readable description of this bean.
67+
* Usually (if not set explicitly) a {@link org.springframework.beans.factory.config.BeanDefinition#getDescription()}.
68+
* @return the bean description (if any).
69+
*/
70+
@Nullable
71+
String getComponentDescription();
72+
73+
/**
74+
* Return the bean name populated by the {@link BeanNameAware#setBeanName(String)}.
75+
* @return the bean name.
76+
*/
77+
@Nullable
78+
String getBeanName();
79+
80+
}

spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationObjectSupport.java

Lines changed: 47 additions & 22 deletions
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.
@@ -22,7 +22,6 @@
2222
import org.springframework.beans.BeansException;
2323
import org.springframework.beans.factory.BeanFactory;
2424
import org.springframework.beans.factory.BeanFactoryAware;
25-
import org.springframework.beans.factory.BeanNameAware;
2625
import org.springframework.beans.factory.InitializingBean;
2726
import org.springframework.beans.factory.config.BeanDefinition;
2827
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -64,7 +63,7 @@
6463
* @author Gary Russell
6564
* @author Artem Bilan
6665
*/
67-
public abstract class IntegrationObjectSupport implements BeanNameAware, NamedComponent,
66+
public abstract class IntegrationObjectSupport implements ComponentSourceAware, NamedComponent,
6867
ApplicationContextAware, BeanFactoryAware, InitializingBean, ExpressionCapable {
6968

7069
protected static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
@@ -95,6 +94,10 @@ public abstract class IntegrationObjectSupport implements BeanNameAware, NamedCo
9594

9695
private Expression expression;
9796

97+
private Object beanSource;
98+
99+
private String beanDescription;
100+
98101
private boolean initialized;
99102

100103
@Override
@@ -132,28 +135,41 @@ public String getComponentType() {
132135
return null;
133136
}
134137

135-
public String getBeanDescription() {
136-
String description = null;
137-
Object source = null;
138-
139-
if (this.beanFactory instanceof ConfigurableListableBeanFactory &&
140-
((ConfigurableListableBeanFactory) this.beanFactory).containsBeanDefinition(this.beanName)) {
141-
BeanDefinition beanDefinition =
142-
((ConfigurableListableBeanFactory) this.beanFactory).getBeanDefinition(this.beanName);
143-
description = beanDefinition.getResourceDescription();
144-
source = beanDefinition.getSource();
145-
}
138+
@Override
139+
public void setComponentSource(Object source) {
140+
this.beanSource = source;
141+
}
142+
143+
@Nullable
144+
@Override
145+
public Object getComponentSource() {
146+
return this.beanSource;
147+
}
146148

147-
StringBuilder sb = new StringBuilder("bean '")
148-
.append(this.beanName).append("'");
149-
if (!this.beanName.equals(getComponentName())) {
150-
sb.append(" for component '").append(getComponentName()).append("'");
149+
@Override
150+
public void setComponentDescription(String description) {
151+
this.beanDescription = description;
152+
}
153+
154+
@Nullable
155+
@Override
156+
public String getComponentDescription() {
157+
return this.beanDescription;
158+
}
159+
160+
public String getBeanDescription() {
161+
StringBuilder sb =
162+
new StringBuilder("bean '")
163+
.append(this.beanName).append("'");
164+
String beanComponentName = getComponentName();
165+
if (!this.beanName.equals(beanComponentName)) {
166+
sb.append(" for component '").append(beanComponentName).append("'");
151167
}
152-
if (description != null) {
153-
sb.append("; defined in: '").append(description).append("'");
168+
if (this.beanDescription != null) {
169+
sb.append("; defined in: '").append(this.beanDescription).append("'");
154170
}
155-
if (source != null) {
156-
sb.append("; from source: '").append(source).append("'");
171+
if (this.beanSource != null) {
172+
sb.append("; from source: '").append(this.beanSource).append("'");
157173
}
158174
return sb.toString();
159175
}
@@ -205,6 +221,15 @@ public final void afterPropertiesSet() {
205221
this.messageBuilderFactory = new DefaultMessageBuilderFactory();
206222
}
207223
}
224+
if (this.beanSource == null && this.beanName != null
225+
&& this.beanFactory instanceof ConfigurableListableBeanFactory configurableListableBeanFactory
226+
&& configurableListableBeanFactory.containsBeanDefinition(this.beanName)) {
227+
BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(this.beanName);
228+
this.beanSource = beanDefinition.getSource();
229+
if (this.beanDescription == null) {
230+
this.beanDescription = beanDefinition.getResourceDescription();
231+
}
232+
}
208233
onInit();
209234
this.initialized = true;
210235
}

spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowAdapter.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2023 the original author or authors.
2+
* Copyright 2016-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.
@@ -23,6 +23,7 @@
2323

2424
import org.reactivestreams.Publisher;
2525

26+
import org.springframework.integration.context.ComponentSourceAware;
2627
import org.springframework.integration.core.MessageSource;
2728
import org.springframework.integration.endpoint.MessageProducerSupport;
2829
import org.springframework.integration.gateway.MessagingGatewaySupport;
@@ -59,18 +60,64 @@
5960
*
6061
* @since 5.0
6162
*/
62-
public abstract class IntegrationFlowAdapter implements IntegrationFlow, ManageableSmartLifecycle {
63+
public abstract class IntegrationFlowAdapter
64+
implements IntegrationFlow, ManageableSmartLifecycle, ComponentSourceAware {
6365

6466
private final AtomicBoolean running = new AtomicBoolean();
6567

6668
private StandardIntegrationFlow targetIntegrationFlow;
6769

70+
private String beanName;
71+
72+
private Object beanSource;
73+
74+
private String beanDescription;
75+
76+
@Override
77+
public void setBeanName(String name) {
78+
this.beanName = name;
79+
}
80+
81+
@Nullable
82+
@Override
83+
public String getBeanName() {
84+
return this.beanName;
85+
}
86+
87+
@Override
88+
public void setComponentSource(Object source) {
89+
this.beanSource = source;
90+
}
91+
92+
@Nullable
93+
@Override
94+
public Object getComponentSource() {
95+
return this.beanSource;
96+
}
97+
98+
@Override
99+
public void setComponentDescription(String description) {
100+
this.beanDescription = description;
101+
}
102+
103+
@Nullable
104+
@Override
105+
public String getComponentDescription() {
106+
return this.beanDescription;
107+
}
108+
68109
@Override
69110
public final void configure(IntegrationFlowDefinition<?> flow) {
70111
IntegrationFlowDefinition<?> targetFlow = buildFlow();
71112
flow.integrationComponents.clear();
72113
flow.integrationComponents.putAll(targetFlow.integrationComponents);
73114
this.targetIntegrationFlow = flow.get();
115+
if (this.beanSource != null) {
116+
this.targetIntegrationFlow.setComponentSource(this.beanSource);
117+
}
118+
if (this.beanDescription != null) {
119+
this.targetIntegrationFlow.setComponentDescription(this.beanDescription);
120+
}
74121
}
75122

76123
@Nullable

spring-integration-core/src/main/java/org/springframework/integration/dsl/StandardIntegrationFlow.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2023 the original author or authors.
2+
* Copyright 2016-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.
@@ -24,8 +24,8 @@
2424
import java.util.Map;
2525
import java.util.concurrent.atomic.AtomicInteger;
2626

27-
import org.springframework.beans.factory.BeanNameAware;
2827
import org.springframework.context.SmartLifecycle;
28+
import org.springframework.integration.context.ComponentSourceAware;
2929
import org.springframework.integration.support.context.NamedComponent;
3030
import org.springframework.lang.Nullable;
3131
import org.springframework.messaging.MessageChannel;
@@ -69,7 +69,7 @@
6969
* @see SmartLifecycle
7070
*/
7171
public class StandardIntegrationFlow
72-
implements IntegrationFlow, SmartLifecycle, BeanNameAware, NamedComponent {
72+
implements IntegrationFlow, SmartLifecycle, ComponentSourceAware, NamedComponent {
7373

7474
private final Map<Object, String> integrationComponents;
7575

@@ -79,6 +79,10 @@ public class StandardIntegrationFlow
7979

8080
private String beanName;
8181

82+
private Object beanSource;
83+
84+
private String beanDescription;
85+
8286
StandardIntegrationFlow(Map<Object, String> integrationComponents) {
8387
this.integrationComponents = new LinkedHashMap<>(integrationComponents);
8488
}
@@ -98,6 +102,34 @@ public String getComponentType() {
98102
return "integration-flow";
99103
}
100104

105+
@Override
106+
public void setComponentSource(Object source) {
107+
this.beanSource = source;
108+
}
109+
110+
@Nullable
111+
@Override
112+
public Object getComponentSource() {
113+
return this.beanSource;
114+
}
115+
116+
@Override
117+
public void setComponentDescription(String description) {
118+
this.beanDescription = description;
119+
}
120+
121+
@Nullable
122+
@Override
123+
public String getComponentDescription() {
124+
return this.beanDescription;
125+
}
126+
127+
@Nullable
128+
@Override
129+
public String getBeanName() {
130+
return this.beanName;
131+
}
132+
101133
@Override
102134
public void configure(IntegrationFlowDefinition<?> flow) {
103135
throw new UnsupportedOperationException();

0 commit comments

Comments
 (0)