diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/ComponentSourceAware.java b/spring-integration-core/src/main/java/org/springframework/integration/context/ComponentSourceAware.java new file mode 100644 index 00000000000..af96a91c25f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/context/ComponentSourceAware.java @@ -0,0 +1,80 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.context; + +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.lang.Nullable; + +/** + * The contract to supply and provide useful information about + * a bean definition (or singleton) source - the place where this bean is declared. + * Usually populated from a respective {@link org.springframework.beans.factory.config.BeanDefinition} + * or via Spring Integration infrastructure. + *

+ * The information from this contract is typically used from exceptions to easy determine + * the place in the application resources where this bean is declared. + * + * @author Artem Bilan + * + * @since 6.4 + * + * @see org.springframework.beans.factory.config.BeanDefinition + */ +public interface ComponentSourceAware extends BeanNameAware { + + /** + * Set a configuration source {@code Object} for this bean definition. + * For normal {@link org.springframework.beans.factory.config.BeanDefinition} this is supplied + * by application context automatically. + * Could be useful when bean is registered at runtime via + * {@link org.springframework.beans.factory.config.SingletonBeanRegistry#registerSingleton(String, Object)} + * @param source the configuration source + */ + void setComponentSource(Object source); + + /** + * Return the configuration source {@code Object} for this bean (maybe {@code null}). + * Usually (if not set explicitly) a {@link org.springframework.beans.factory.config.BeanDefinition#getSource()}. + * @return the configuration source for the bean (if any). + */ + @Nullable + Object getComponentSource(); + + /** + * Set a human-readable description of this bean. + * For normal bean definition a {@link org.springframework.beans.factory.config.BeanDefinition#getDescription()} + * is used. + * @param description the bean description + */ + void setComponentDescription(String description); + + /** + * Return a human-readable description of this bean. + * Usually (if not set explicitly) a {@link org.springframework.beans.factory.config.BeanDefinition#getDescription()}. + * @return the bean description (if any). + */ + @Nullable + String getComponentDescription(); + + /** + * Return the bean name populated by the {@link BeanNameAware#setBeanName(String)}. + * @return the bean name. + */ + @Nullable + String getBeanName(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationObjectSupport.java b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationObjectSupport.java index f587156513e..377b958ce1c 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationObjectSupport.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationObjectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -64,7 +63,7 @@ * @author Gary Russell * @author Artem Bilan */ -public abstract class IntegrationObjectSupport implements BeanNameAware, NamedComponent, +public abstract class IntegrationObjectSupport implements ComponentSourceAware, NamedComponent, ApplicationContextAware, BeanFactoryAware, InitializingBean, ExpressionCapable { protected static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); @@ -95,6 +94,10 @@ public abstract class IntegrationObjectSupport implements BeanNameAware, NamedCo private Expression expression; + private Object beanSource; + + private String beanDescription; + private boolean initialized; @Override @@ -132,28 +135,41 @@ public String getComponentType() { return null; } - public String getBeanDescription() { - String description = null; - Object source = null; - - if (this.beanFactory instanceof ConfigurableListableBeanFactory && - ((ConfigurableListableBeanFactory) this.beanFactory).containsBeanDefinition(this.beanName)) { - BeanDefinition beanDefinition = - ((ConfigurableListableBeanFactory) this.beanFactory).getBeanDefinition(this.beanName); - description = beanDefinition.getResourceDescription(); - source = beanDefinition.getSource(); - } + @Override + public void setComponentSource(Object source) { + this.beanSource = source; + } + + @Nullable + @Override + public Object getComponentSource() { + return this.beanSource; + } - StringBuilder sb = new StringBuilder("bean '") - .append(this.beanName).append("'"); - if (!this.beanName.equals(getComponentName())) { - sb.append(" for component '").append(getComponentName()).append("'"); + @Override + public void setComponentDescription(String description) { + this.beanDescription = description; + } + + @Nullable + @Override + public String getComponentDescription() { + return this.beanDescription; + } + + public String getBeanDescription() { + StringBuilder sb = + new StringBuilder("bean '") + .append(this.beanName).append("'"); + String beanComponentName = getComponentName(); + if (!this.beanName.equals(beanComponentName)) { + sb.append(" for component '").append(beanComponentName).append("'"); } - if (description != null) { - sb.append("; defined in: '").append(description).append("'"); + if (this.beanDescription != null) { + sb.append("; defined in: '").append(this.beanDescription).append("'"); } - if (source != null) { - sb.append("; from source: '").append(source).append("'"); + if (this.beanSource != null) { + sb.append("; from source: '").append(this.beanSource).append("'"); } return sb.toString(); } @@ -205,6 +221,15 @@ public final void afterPropertiesSet() { this.messageBuilderFactory = new DefaultMessageBuilderFactory(); } } + if (this.beanSource == null && this.beanName != null + && this.beanFactory instanceof ConfigurableListableBeanFactory configurableListableBeanFactory + && configurableListableBeanFactory.containsBeanDefinition(this.beanName)) { + BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(this.beanName); + this.beanSource = beanDefinition.getSource(); + if (this.beanDescription == null) { + this.beanDescription = beanDefinition.getResourceDescription(); + } + } onInit(); this.initialized = true; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowAdapter.java index 1b30df054ec..74631924fb6 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowAdapter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 the original author or authors. + * Copyright 2016-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.reactivestreams.Publisher; +import org.springframework.integration.context.ComponentSourceAware; import org.springframework.integration.core.MessageSource; import org.springframework.integration.endpoint.MessageProducerSupport; import org.springframework.integration.gateway.MessagingGatewaySupport; @@ -59,18 +60,64 @@ * * @since 5.0 */ -public abstract class IntegrationFlowAdapter implements IntegrationFlow, ManageableSmartLifecycle { +public abstract class IntegrationFlowAdapter + implements IntegrationFlow, ManageableSmartLifecycle, ComponentSourceAware { private final AtomicBoolean running = new AtomicBoolean(); private StandardIntegrationFlow targetIntegrationFlow; + private String beanName; + + private Object beanSource; + + private String beanDescription; + + @Override + public void setBeanName(String name) { + this.beanName = name; + } + + @Nullable + @Override + public String getBeanName() { + return this.beanName; + } + + @Override + public void setComponentSource(Object source) { + this.beanSource = source; + } + + @Nullable + @Override + public Object getComponentSource() { + return this.beanSource; + } + + @Override + public void setComponentDescription(String description) { + this.beanDescription = description; + } + + @Nullable + @Override + public String getComponentDescription() { + return this.beanDescription; + } + @Override public final void configure(IntegrationFlowDefinition flow) { IntegrationFlowDefinition targetFlow = buildFlow(); flow.integrationComponents.clear(); flow.integrationComponents.putAll(targetFlow.integrationComponents); this.targetIntegrationFlow = flow.get(); + if (this.beanSource != null) { + this.targetIntegrationFlow.setComponentSource(this.beanSource); + } + if (this.beanDescription != null) { + this.targetIntegrationFlow.setComponentDescription(this.beanDescription); + } } @Nullable diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/StandardIntegrationFlow.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/StandardIntegrationFlow.java index f9dd9f72964..da8f18ffbff 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dsl/StandardIntegrationFlow.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/StandardIntegrationFlow.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 the original author or authors. + * Copyright 2016-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -import org.springframework.beans.factory.BeanNameAware; import org.springframework.context.SmartLifecycle; +import org.springframework.integration.context.ComponentSourceAware; import org.springframework.integration.support.context.NamedComponent; import org.springframework.lang.Nullable; import org.springframework.messaging.MessageChannel; @@ -69,7 +69,7 @@ * @see SmartLifecycle */ public class StandardIntegrationFlow - implements IntegrationFlow, SmartLifecycle, BeanNameAware, NamedComponent { + implements IntegrationFlow, SmartLifecycle, ComponentSourceAware, NamedComponent { private final Map integrationComponents; @@ -79,6 +79,10 @@ public class StandardIntegrationFlow private String beanName; + private Object beanSource; + + private String beanDescription; + StandardIntegrationFlow(Map integrationComponents) { this.integrationComponents = new LinkedHashMap<>(integrationComponents); } @@ -98,6 +102,34 @@ public String getComponentType() { return "integration-flow"; } + @Override + public void setComponentSource(Object source) { + this.beanSource = source; + } + + @Nullable + @Override + public Object getComponentSource() { + return this.beanSource; + } + + @Override + public void setComponentDescription(String description) { + this.beanDescription = description; + } + + @Nullable + @Override + public String getComponentDescription() { + return this.beanDescription; + } + + @Nullable + @Override + public String getBeanName() { + return this.beanName; + } + @Override public void configure(IntegrationFlowDefinition flow) { throw new UnsupportedOperationException(); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowBeanPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowBeanPostProcessor.java index b746627c833..d470d6a9159 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowBeanPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowBeanPostProcessor.java @@ -29,17 +29,18 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionCustomizer; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.EmbeddedValueResolver; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionOverrideException; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -52,7 +53,6 @@ import org.springframework.context.SmartLifecycle; import org.springframework.core.ResolvableType; import org.springframework.core.io.DescriptiveResource; -import org.springframework.core.type.MethodMetadata; import org.springframework.integration.channel.AbstractMessageChannel; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.FixedSubscriberChannel; @@ -60,6 +60,7 @@ import org.springframework.integration.config.ConsumerEndpointFactoryBean; import org.springframework.integration.config.IntegrationConfigUtils; import org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean; +import org.springframework.integration.context.ComponentSourceAware; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.core.MessageSource; import org.springframework.integration.dsl.ComponentsRegistration; @@ -97,7 +98,7 @@ public class IntegrationFlowBeanPostProcessor private StringValueResolver embeddedValueResolver; - private ConfigurableListableBeanFactory beanFactory; + private DefaultListableBeanFactory beanFactory; private volatile IntegrationFlowContext flowContext; @@ -109,7 +110,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws ); this.applicationContext = (ConfigurableApplicationContext) applicationContext; - this.beanFactory = this.applicationContext.getBeanFactory(); + this.beanFactory = (DefaultListableBeanFactory) this.applicationContext.getBeanFactory(); this.embeddedValueResolver = new EmbeddedValueResolver(this.beanFactory); } @@ -142,8 +143,16 @@ public void afterSingletonsInstantiated() { } } - private Object processStandardIntegrationFlow(StandardIntegrationFlow flow, String flowBeanName) { // NOSONAR - // complexity + private Object processStandardIntegrationFlow(StandardIntegrationFlow flow, String flowBeanName) { // NOSONAR complexity + boolean registerBeanDefinitions = this.beanFactory.containsBeanDefinition(flowBeanName); + if (registerBeanDefinitions) { + BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(flowBeanName); + flow.setComponentSource(beanDefinition.getSource()); + flow.setComponentDescription(beanDefinition.getDescription()); + } + Object beanSource = flow.getComponentSource(); + String beanDescription = flow.getComponentDescription(); + String flowNamePrefix = flowBeanName + "."; if (this.flowContext == null) { this.flowContext = this.beanFactory.getBean(IntegrationFlowContext.class); @@ -172,18 +181,21 @@ else if (useFlowIdAsPrefix) { if (noBeanPresentForComponent(messageHandler, flowBeanName)) { String handlerBeanName = generateBeanName(messageHandler, flowNamePrefix); - registerComponent(messageHandler, handlerBeanName, flowBeanName); + registerComponent(registerBeanDefinitions, messageHandler, handlerBeanName, + beanSource, beanDescription, flowBeanName); this.beanFactory.registerAlias(handlerBeanName, id + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX); } - registerComponent(endpoint, id, flowBeanName); + registerComponent(registerBeanDefinitions, endpoint, id, beanSource, + beanDescription, flowBeanName); targetIntegrationComponents.put(endpoint, id); } else if (component instanceof MessageChannelReference messageChannelReference) { String channelBeanName = messageChannelReference.name(); if (!this.beanFactory.containsBean(channelBeanName)) { DirectChannel directChannel = new DirectChannel(); - registerComponent(directChannel, channelBeanName, flowBeanName); + registerComponent(registerBeanDefinitions, directChannel, channelBeanName, + beanSource, beanDescription, flowBeanName); targetIntegrationComponents.put(directChannel, channelBeanName); } } @@ -194,9 +206,9 @@ else if (component instanceof SourcePollingChannelAdapterSpec spec) { .stream() .filter(o -> noBeanPresentForComponent(o.getKey(), flowBeanName)) .forEach(o -> - registerComponent(o.getKey(), + registerComponent(registerBeanDefinitions, o.getKey(), generateBeanName(o.getKey(), flowNamePrefix, o.getValue(), - useFlowIdAsPrefix))); + useFlowIdAsPrefix), beanSource, beanDescription, spec.getId())); } SourcePollingChannelAdapterFactoryBean pollingChannelAdapterFactoryBean = spec.getObject().getT1(); String id = spec.getId(); @@ -208,7 +220,8 @@ else if (useFlowIdAsPrefix) { id = flowNamePrefix + id; } - registerComponent(pollingChannelAdapterFactoryBean, id, flowBeanName); + registerComponent(registerBeanDefinitions, pollingChannelAdapterFactoryBean, id, + beanSource, beanDescription, flowBeanName); targetIntegrationComponents.put(pollingChannelAdapterFactoryBean, id); MessageSource messageSource = spec.getObject().getT2(); @@ -219,7 +232,8 @@ else if (useFlowIdAsPrefix) { messageSourceId = namedComponent.getComponentName(); } - registerComponent(messageSource, messageSourceId, flowBeanName); + registerComponent(registerBeanDefinitions, messageSource, messageSourceId, + beanSource, beanDescription, flowBeanName); } } else { @@ -233,7 +247,8 @@ else if (useFlowIdAsPrefix) { BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + channelNameIndex++; } } - registerComponent(component, channelBeanName, flowBeanName); + registerComponent(registerBeanDefinitions, component, channelBeanName, + beanSource, beanDescription, flowBeanName); targetIntegrationComponents.put(component, channelBeanName); } else if (component instanceof FixedSubscriberChannel fixedSubscriberChannel) { @@ -242,7 +257,8 @@ else if (component instanceof FixedSubscriberChannel fixedSubscriberChannel) { channelBeanName = flowNamePrefix + "channel" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + channelNameIndex++; } - registerComponent(component, channelBeanName, flowBeanName); + registerComponent(registerBeanDefinitions, component, channelBeanName, + beanSource, beanDescription, flowBeanName); targetIntegrationComponents.put(component, channelBeanName); } else if (component instanceof StandardIntegrationFlow) { @@ -251,7 +267,8 @@ else if (component instanceof StandardIntegrationFlow) { ? entry.getValue() : flowNamePrefix + "subFlow" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + subFlowNameIndex++; - registerComponent(component, subFlowBeanName, flowBeanName); + registerComponent(registerBeanDefinitions, component, subFlowBeanName, + beanSource, beanDescription, flowBeanName); targetIntegrationComponents.put(component, subFlowBeanName); } else if (component instanceof AnnotationGatewayProxyFactoryBean gateway) { @@ -264,15 +281,21 @@ else if (component instanceof AnnotationGatewayProxyFactoryBean gateway) { gatewayId = flowNamePrefix + "gateway"; } - registerComponent(gateway, gatewayId, flowBeanName, - beanDefinition -> { - RootBeanDefinition definition = (RootBeanDefinition) beanDefinition; - Class serviceInterface = gateway.getObjectType(); - definition.setSource(new DescriptiveResource("" + serviceInterface)); - definition.setTargetType( - ResolvableType.forClassWithGenerics(AnnotationGatewayProxyFactoryBean.class, - serviceInterface)); - }); + if (registerBeanDefinitions) { + registerBeanDefinition(gateway, gatewayId, beanSource, + beanDescription, flowBeanName, + beanDefinition -> { + RootBeanDefinition definition = (RootBeanDefinition) beanDefinition; + Class serviceInterface = gateway.getObjectType(); + definition.setSource(new DescriptiveResource("" + serviceInterface)); + definition.setTargetType( + ResolvableType.forClassWithGenerics(AnnotationGatewayProxyFactoryBean.class, + serviceInterface)); + }); + } + else { + registerSingleton(gateway, gatewayId, beanSource, beanDescription, flowBeanName); + } targetIntegrationComponents.put(component, gatewayId); } @@ -280,7 +303,8 @@ else if (component instanceof AnnotationGatewayProxyFactoryBean gateway) { String generatedBeanName = generateBeanName(component, flowNamePrefix, entry.getValue(), useFlowIdAsPrefix); - registerComponent(component, generatedBeanName, flowBeanName); + registerComponent(registerBeanDefinitions, component, generatedBeanName, + beanSource, beanDescription, flowBeanName); targetIntegrationComponents.put(component, generatedBeanName); } } @@ -352,13 +376,20 @@ private void processIntegrationComponentSpec(String beanName, IntegrationCompone if (bean instanceof ComponentsRegistration componentsRegistration) { Map componentsToRegister = componentsRegistration.getComponentsToRegister(); if (!CollectionUtils.isEmpty(componentsToRegister)) { + boolean registerBeanDefinitions = this.beanFactory.containsBeanDefinition(beanName); + BeanDefinition beanDefinition = null; + if (registerBeanDefinitions) { + beanDefinition = this.beanFactory.getBeanDefinition(beanName); + } + Object beanDefinitionSource = registerBeanDefinitions ? beanDefinition.getSource() : null; + String beanDefinitionDescription = registerBeanDefinitions ? beanDefinition.getDescription() : null; componentsToRegister.entrySet() .stream() .filter(component -> noBeanPresentForComponent(component.getKey(), beanName)) .forEach(component -> - registerComponent(component.getKey(), - generateBeanName(component.getKey(), beanName, component.getValue(), false))); - + registerComponent(registerBeanDefinitions, component.getKey(), + generateBeanName(component.getKey(), beanName, component.getValue(), false), + beanDefinitionSource, beanDefinitionDescription, beanName)); } } } @@ -405,21 +436,34 @@ private boolean noBeanPresentForComponent(Object instance, String parentBeanName return true; } else { - BeanDefinition existingBeanDefinition = - IntegrationContextUtils.getBeanDefinition(beanName, this.beanFactory); - if (!ConfigurableBeanFactory.SCOPE_PROTOTYPE.equals(existingBeanDefinition.getScope()) - && !instance.equals(this.beanFactory.getBean(beanName))) { - - AbstractBeanDefinition beanDefinition = - BeanDefinitionBuilder.genericBeanDefinition((Class) instance.getClass(), - () -> instance) - .getBeanDefinition(); - beanDefinition.setResourceDescription("the '" + parentBeanName + "' bean definition"); - throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingBeanDefinition); + BeanDefinition existingBeanDefinition = null; + try { + existingBeanDefinition = IntegrationContextUtils.getBeanDefinition(beanName, this.beanFactory); } - else { - return false; + catch (NoSuchBeanDefinitionException ex) { + // Ignore and move on as no bean definition (possibly just singleton?) } + + if (existingBeanDefinition == null + || !ConfigurableBeanFactory.SCOPE_PROTOTYPE.equals(existingBeanDefinition.getScope())) { + + Object existingBean = this.beanFactory.getBean(beanName); + if (!instance.equals(existingBean)) { + AbstractBeanDefinition beanDefinition = + BeanDefinitionBuilder.genericBeanDefinition((Class) instance.getClass(), + () -> instance) + .getBeanDefinition(); + beanDefinition.setResourceDescription("the '" + parentBeanName + "' bean definition"); + if (existingBeanDefinition == null) { + existingBeanDefinition = + BeanDefinitionBuilder.genericBeanDefinition((Class) existingBean.getClass(), + () -> existingBean) + .getBeanDefinition(); + } + throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingBeanDefinition); + } + } + return false; } } @@ -427,12 +471,21 @@ private boolean noBeanPresentForComponent(Object instance, String parentBeanName .containsValue(instance); } - private void registerComponent(Object component, String beanName) { - registerComponent(component, beanName, null); + private void registerComponent(boolean registerBeanDefinition, + Object component, String beanName, @Nullable Object source, + @Nullable String description, @Nullable String parentName) { + + if (registerBeanDefinition) { + registerBeanDefinition(component, beanName, source, description, parentName); + } + else { + registerSingleton(component, beanName, source, description, parentName); + } } @SuppressWarnings("unchecked") - private void registerComponent(Object component, String beanName, @Nullable String parentName, + private void registerBeanDefinition(Object component, String beanName, @Nullable Object source, + @Nullable String description, @Nullable String parentName, BeanDefinitionCustomizer... customizers) { AbstractBeanDefinition beanDefinition = @@ -440,20 +493,31 @@ private void registerComponent(Object component, String beanName, @Nullable Stri .applyCustomizers(customizers) .getRawBeanDefinition(); - if (parentName != null && this.beanFactory.containsBeanDefinition(parentName)) { - AbstractBeanDefinition parentBeanDefinition = - (AbstractBeanDefinition) this.beanFactory.getBeanDefinition(parentName); - beanDefinition.setResource(parentBeanDefinition.getResource()); - Object source = parentBeanDefinition.getSource(); - if (source instanceof MethodMetadata) { - source = "bean method " + ((MethodMetadata) source).getMethodName(); - } - beanDefinition.setSource(source); + beanDefinition.setSource(source); + beanDefinition.setDescription(description); + + if (parentName != null) { this.beanFactory.registerDependentBean(parentName, beanName); } ((BeanDefinitionRegistry) this.beanFactory).registerBeanDefinition(beanName, beanDefinition); + this.beanFactory.getBean(beanName); + } + + private void registerSingleton(Object component, String beanName, @Nullable Object source, + @Nullable String description, @Nullable String parentName) { + + if (component instanceof ComponentSourceAware componentSourceAware) { + componentSourceAware.setComponentSource(source); + componentSourceAware.setComponentDescription(description); + } + + if (parentName != null) { + this.beanFactory.registerDependentBean(parentName, beanName); + } + this.beanFactory.registerSingleton(beanName, component); + this.beanFactory.initializeBean(component, beanName); this.beanFactory.getBean(beanName); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowContext.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowContext.java index ea67587f614..5e0f3d81c46 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowContext.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2023 the original author or authors. + * Copyright 2018-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -214,6 +214,14 @@ interface IntegrationFlowRegistrationBuilder { */ IntegrationFlowRegistrationBuilder setSource(Object source); + /** + * Set a human-readable description of this integration flow. + * @param description the description for integration flow instance. + * @return the current builder instance + * @since 6.4 + */ + IntegrationFlowRegistrationBuilder setDescription(String description); + /** * Invoke this method to prefix bean names in the flow with the (required) flow id * and a period. This is useful if you wish to register the same flow multiple times diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/StandardIntegrationFlowContext.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/StandardIntegrationFlowContext.java index 5c7635b02fb..82a51af159a 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/StandardIntegrationFlowContext.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/StandardIntegrationFlowContext.java @@ -27,14 +27,15 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.SmartLifecycle; -import org.springframework.core.type.MethodMetadata; +import org.springframework.integration.context.ComponentSourceAware; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlowAdapter; +import org.springframework.integration.dsl.IntegrationFlowBuilder; +import org.springframework.integration.dsl.IntegrationFlowDefinition; +import org.springframework.integration.dsl.StandardIntegrationFlow; import org.springframework.integration.support.context.NamedComponent; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -59,21 +60,18 @@ public final class StandardIntegrationFlowContext implements IntegrationFlowCont private final Lock registerFlowsLock = new ReentrantLock(); - private ConfigurableListableBeanFactory beanFactory; - - private BeanDefinitionRegistry beanDefinitionRegistry; + private DefaultListableBeanFactory beanFactory; StandardIntegrationFlowContext() { } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory, + Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory, "To use Spring Integration Java DSL the 'beanFactory' has to be an instance of " + "'ConfigurableListableBeanFactory'. " + "Consider using 'GenericApplicationContext' implementation."); - this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; - this.beanDefinitionRegistry = (BeanDefinitionRegistry) this.beanFactory; + this.beanFactory = (DefaultListableBeanFactory) beanFactory; } /** @@ -107,7 +105,7 @@ else if (this.registry.containsKey(flowId)) { "An existing IntegrationFlowRegistration must be destroyed before overriding."); } - integrationFlow = registerFlowBean(integrationFlow, flowId, builder.source); + integrationFlow = registerFlowBean(integrationFlow, flowId, builder.source, builder.description); } finally { this.registerFlowsLock.unlock(); @@ -116,7 +114,8 @@ else if (this.registry.containsKey(flowId)) { builder.integrationFlow = integrationFlow; final String theFlowId = flowId; - builder.additionalBeans.forEach((key, value) -> registerBean(key, value, theFlowId)); + builder.additionalBeans.forEach((key, value) -> + registerBean(key, value, theFlowId, builder.source, builder.description)); IntegrationFlowRegistration registration = new StandardIntegrationFlowRegistration(integrationFlow, this, flowId); @@ -133,38 +132,37 @@ else if (this.registry.containsKey(flowId)) { return registration; } - private IntegrationFlow registerFlowBean(IntegrationFlow flow, String flowId, @Nullable Object source) { - AbstractBeanDefinition beanDefinition = - BeanDefinitionBuilder.genericBeanDefinition(IntegrationFlow.class, () -> flow) - .getRawBeanDefinition(); - beanDefinition.setSource(source); - this.beanDefinitionRegistry.registerBeanDefinition(flowId, beanDefinition); - return this.beanFactory.getBean(flowId, IntegrationFlow.class); + private IntegrationFlow registerFlowBean(IntegrationFlow flow, @Nullable String beanName, + @Nullable Object source, @Nullable String description) { + + IntegrationFlow flowToRegister = flow; + + if (!(flow instanceof StandardIntegrationFlow) && !(flow instanceof IntegrationFlowAdapter)) { + flowToRegister = new IntegrationFlowComponentSourceAwareAdapter(flow); + } + + return registerBean(flowToRegister, beanName, null, source, description); } @SuppressWarnings("unchecked") - private void registerBean(Object bean, @Nullable String beanNameArg, String parentName) { - String beanName = beanNameArg; - if (beanName == null) { - beanName = generateBeanName(bean, parentName); - } + private B registerBean(B bean, @Nullable String beanNameArg, @Nullable String parentName, + @Nullable Object source, @Nullable String description) { - AbstractBeanDefinition beanDefinition = - BeanDefinitionBuilder.genericBeanDefinition((Class) bean.getClass(), () -> bean) - .getRawBeanDefinition(); + String beanName = beanNameArg != null ? beanNameArg : generateBeanName(bean, parentName); if (parentName != null) { - AbstractBeanDefinition parentBeanDefinition = - (AbstractBeanDefinition) this.beanFactory.getBeanDefinition(parentName); - Object source = parentBeanDefinition.getSource(); - if (source instanceof MethodMetadata) { - source = "bean method " + ((MethodMetadata) source).getMethodName(); - } - beanDefinition.setSource(source); this.beanFactory.registerDependentBean(parentName, beanName); } - this.beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition); - this.beanFactory.getBean(beanName); + if (bean instanceof ComponentSourceAware componentSourceAware) { + if (source != null && componentSourceAware.getComponentSource() == null) { + componentSourceAware.setComponentSource(source); + } + if (description != null && componentSourceAware.getComponentDescription() == null) { + componentSourceAware.setComponentDescription(description); + } + } + this.beanFactory.registerSingleton(beanName, bean); + return (B) this.beanFactory.initializeBean(bean, beanName); } /** @@ -192,7 +190,7 @@ public void remove(String flowId) { removeDependantBeans(flowId); - this.beanDefinitionRegistry.removeBeanDefinition(flowId); + this.beanFactory.destroySingleton(flowId); } else { throw new IllegalStateException("An IntegrationFlow with the id " @@ -203,12 +201,13 @@ public void remove(String flowId) { private void removeDependantBeans(String parentName) { String[] dependentBeans = this.beanFactory.getDependentBeans(parentName); for (String beanName : dependentBeans) { - removeDependantBeans(beanName); - this.beanDefinitionRegistry.removeBeanDefinition(beanName); - String[] aliases = this.beanDefinitionRegistry.getAliases(beanName); + this.beanFactory.destroyBean(this.beanFactory.getBean(beanName)); + this.beanFactory.destroySingleton(beanName); + String[] aliases = this.beanFactory.getAliases(beanName); for (String alias : aliases) { - this.beanDefinitionRegistry.removeAlias(alias); + this.beanFactory.removeAlias(alias); } + removeDependantBeans(beanName); } } @@ -216,9 +215,8 @@ private void removeDependantBeans(String parentName) { * Obtain a {@link MessagingTemplate} with its default destination set to the input channel * of the {@link IntegrationFlow} for provided {@code flowId}. *

Any {@link IntegrationFlow} bean (not only manually registered) can be used for this method. - *

If {@link IntegrationFlow} doesn't start with the - * {@link org.springframework.messaging.MessageChannel}, the - * {@link IllegalStateException} is thrown. + *

If {@link IntegrationFlow} doesn't start with the {@link org.springframework.messaging.MessageChannel}, + * the {@link IllegalStateException} is thrown. * @param flowId the bean name to obtain the input channel from * @return the {@link MessagingTemplate} instance */ @@ -275,6 +273,9 @@ public final class StandardIntegrationFlowRegistrationBuilder implements Integra @Nullable private Object source; + @Nullable + private String description; + StandardIntegrationFlowRegistrationBuilder(IntegrationFlow integrationFlow) { this.integrationFlow = integrationFlow; } @@ -338,6 +339,12 @@ public IntegrationFlowRegistrationBuilder setSource(Object source) { return this; } + @Override + public IntegrationFlowRegistrationBuilder setDescription(String description) { + this.description = description; + return this; + } + @Override public IntegrationFlowRegistrationBuilder useFlowIdAsPrefix() { this.idAsPrefix = true; @@ -364,4 +371,62 @@ public IntegrationFlowRegistration register() { } + private static final class IntegrationFlowComponentSourceAwareAdapter + implements IntegrationFlow, ComponentSourceAware { + + private final IntegrationFlow delegate; + + private Object beanSource; + + private String beanDescription; + + IntegrationFlowComponentSourceAwareAdapter(IntegrationFlow delegate) { + this.delegate = delegate; + } + + @Override + public void setComponentSource(Object source) { + this.beanSource = source; + } + + @Override + public Object getComponentSource() { + return this.beanSource; + } + + @Override + public void setComponentDescription(String description) { + this.beanDescription = description; + } + + @Override + public String getComponentDescription() { + return this.beanDescription; + } + + @Nullable + @Override + public String getBeanName() { + return null; + } + + @Override + public void setBeanName(String name) { + + } + + @Override + public void configure(IntegrationFlowDefinition flow) { + this.delegate.configure(flow); + StandardIntegrationFlow standardIntegrationFlow = ((IntegrationFlowBuilder) flow).get(); + if (this.beanSource != null) { + standardIntegrationFlow.setComponentSource(this.beanSource); + } + if (this.beanDescription != null) { + standardIntegrationFlow.setComponentDescription(this.beanDescription); + } + } + + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/gateway/GatewayDslTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/gateway/GatewayDslTests.java index 4598c9439f1..200ccce6eff 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/dsl/gateway/GatewayDslTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/gateway/GatewayDslTests.java @@ -48,6 +48,7 @@ import org.springframework.messaging.PollableChannel; import org.springframework.messaging.support.ErrorMessage; import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; @@ -59,6 +60,7 @@ * @since 5.1.3 */ @SpringJUnitConfig +@DirtiesContext public class GatewayDslTests { @Autowired @@ -96,9 +98,8 @@ void testGatewayFlow() { String exceptionMessage = ((Exception) receive.getPayload()).getMessage(); assertThat(exceptionMessage) .contains("message has been rejected in filter") - .contains("defined in: " + - "'org.springframework.integration.dsl.gateway.GatewayDslTests$ContextConfiguration'; " + - "from source: 'bean method gatewayRequestFlow'"); + .contains("from source: 'public org.springframework.integration.dsl.IntegrationFlow " + + "org.springframework.integration.dsl.gateway.GatewayDslTests$ContextConfiguration.gatewayRequestFlow()'"); } @Autowired diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java index 4427905310f..8c5bf4282de 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java @@ -21,8 +21,10 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -34,9 +36,7 @@ import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; -import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationNotAllowedException; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; @@ -52,7 +52,6 @@ import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.config.EnableIntegrationManagement; import org.springframework.integration.config.EnableMessageHistory; -import org.springframework.integration.config.IntegrationManagementConfigurer; import org.springframework.integration.core.MessageProducer; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.dsl.IntegrationFlow; @@ -112,9 +111,6 @@ public class ManualFlowTests { @Autowired private SmartLifecycleRoleController roleController; - @Autowired - private IntegrationManagementConfigurer integrationManagementConfigurer; - @Test @SuppressWarnings("unchecked") public void testWithAnonymousMessageProducerStart() { @@ -212,7 +208,6 @@ public void testManualFlowRegistration() throws InterruptedException { this.beanFactory.getBean(flowRegistrationId + BeanFactoryHandler.class.getName() + "#0", BeanFactoryHandler.class); assertThat(bean).isSameAs(additionalBean); - assertThat(bean.beanFactory).isSameAs(this.beanFactory); bean = this.beanFactory.getBean(flowRegistrationId + "." + "anId.handler", BeanFactoryHandler.class); MessagingTemplate messagingTemplate = flowRegistration.getMessagingTemplate(); @@ -486,23 +481,28 @@ public void testConcurrentRegistration() throws InterruptedException { @Test public void testDisabledBeansOverride() { - assertThatExceptionOfType(BeanCreationException.class) + assertThatExceptionOfType(BeanDefinitionOverrideException.class) .isThrownBy(() -> this.integrationFlowContext.registration(f -> f.channel(c -> c.direct("doNotOverrideChannel"))) .register()) - .isExactlyInstanceOf(BeanCreationException.class) - .withCauseExactlyInstanceOf(BeanDefinitionOverrideException.class) .withMessageContaining("Invalid bean definition with name 'doNotOverrideChannel'"); } @Test public void testBeanDefinitionInfoInTheException() { + Map mergedBeanDefinitions = + TestUtils.getPropertyValue(this.beanFactory, "mergedBeanDefinitions", Map.class); + + int mergedBeanDefinitionsSizeBeforeRandomFlow = mergedBeanDefinitions.size(); + IntegrationFlow testFlow = f -> f.transform(String::toUpperCase); Method source = ReflectionUtils.findMethod(ManualFlowTests.class, "testBeanDefinitionInfoInTheException"); IntegrationFlowRegistration flowRegistration = this.integrationFlowContext.registration(testFlow) .setSource(source) + .id("flow#" + UUID.randomUUID()) .register(); + assertThatExceptionOfType(MessageTransformationException.class) .isThrownBy(() -> flowRegistration.getInputChannel().send(new GenericMessage<>(new Date()))) .withCauseExactlyInstanceOf(ClassCastException.class) @@ -510,6 +510,8 @@ public void testBeanDefinitionInfoInTheException() { .withStackTraceContaining("java.util.Date cannot be cast to"); flowRegistration.destroy(); + + assertThat(mergedBeanDefinitions.size()).isEqualTo(mergedBeanDefinitionsSizeBeforeRandomFlow); } @Configuration @@ -565,20 +567,17 @@ public IntegrationFlow wrongScopeFlow() { private final class BeanFactoryHandler extends AbstractReplyProducingMessageHandler { - @Autowired - private BeanFactory beanFactory; - private boolean destroyed; @Override protected Object handleRequestMessage(Message requestMessage) { - Objects.requireNonNull(this.beanFactory); + Objects.requireNonNull(getBeanFactory()); return requestMessage; } @Override protected void doInit() { - this.beanFactory.getClass(); // ensure wiring before afterPropertiesSet() + getBeanFactory().getClass(); // ensure wiring before afterPropertiesSet() } @Override