diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpIntegrationConfigurationInitializer.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpIntegrationConfigurationInitializer.java index f8c14709bf..d287b1c52a 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpIntegrationConfigurationInitializer.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpIntegrationConfigurationInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2023 the original author or authors. + * Copyright 2014-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,8 +23,10 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.integration.config.IntegrationConfigurationInitializer; +import org.springframework.integration.http.inbound.DynamicRequestMappingBeanPostProcessor; import org.springframework.integration.http.inbound.IntegrationRequestMappingHandlerMapping; /** @@ -58,16 +60,25 @@ public class HttpIntegrationConfigurationInitializer implements IntegrationConfi *
In addition, checks if the {@code javax.servlet.Servlet} class is present on the classpath. * When Spring Integration HTTP is used only as an HTTP client, there is no reason to use and register * the HTTP server components. + *
+ * Also registers a {@link DynamicRequestMappingBeanPostProcessor} for dynamically added HTTP inbound endpoints. */ private void registerRequestMappingHandlerMappingIfNecessary(BeanDefinitionRegistry registry) { - if (HttpContextUtils.WEB_MVC_PRESENT && - !registry.containsBeanDefinition(HttpContextUtils.HANDLER_MAPPING_BEAN_NAME)) { - BeanDefinitionBuilder requestMappingBuilder = - BeanDefinitionBuilder.genericBeanDefinition(IntegrationRequestMappingHandlerMapping.class) - .addPropertyValue("order", 0) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - registry.registerBeanDefinition(HttpContextUtils.HANDLER_MAPPING_BEAN_NAME, - requestMappingBuilder.getBeanDefinition()); + if (HttpContextUtils.WEB_MVC_PRESENT) { + if (!registry.containsBeanDefinition(HttpContextUtils.HANDLER_MAPPING_BEAN_NAME)) { + BeanDefinitionBuilder requestMappingBuilder = + BeanDefinitionBuilder.genericBeanDefinition(IntegrationRequestMappingHandlerMapping.class) + .addPropertyValue("order", 0) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(HttpContextUtils.HANDLER_MAPPING_BEAN_NAME, + requestMappingBuilder.getBeanDefinition()); + } + + BeanDefinitionReaderUtils.registerWithGeneratedName( + BeanDefinitionBuilder.genericBeanDefinition(DynamicRequestMappingBeanPostProcessor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) + .getBeanDefinition(), + registry); } } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/DynamicRequestMappingBeanPostProcessor.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/DynamicRequestMappingBeanPostProcessor.java new file mode 100644 index 0000000000..c82c38ae06 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/DynamicRequestMappingBeanPostProcessor.java @@ -0,0 +1,85 @@ +/* + * 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.http.inbound; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; + +/** + * A {@link DestructionAwareBeanPostProcessor} to register request mapping + * created at runtime (e.g. via + * {@link org.springframework.integration.dsl.context.IntegrationFlowContext}) + * by {@link HttpRequestHandlingEndpointSupport} instances + * into the {@link IntegrationRequestMappingHandlerMapping}. + * These mappings are also removed when respective {@link HttpRequestHandlingEndpointSupport} + * bean is destroyed. + * + * @author Artem Bilan + * + * @since 6.2.5 + */ +public class DynamicRequestMappingBeanPostProcessor + implements BeanFactoryAware, DestructionAwareBeanPostProcessor, SmartInitializingSingleton { + + private BeanFactory beanFactory; + + private IntegrationRequestMappingHandlerMapping integrationRequestMappingHandlerMapping; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public void afterSingletonsInstantiated() { + this.integrationRequestMappingHandlerMapping = + this.beanFactory.getBean(IntegrationRequestMappingHandlerMapping.class); + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (this.integrationRequestMappingHandlerMapping != null && isHandler(bean.getClass())) { + this.integrationRequestMappingHandlerMapping.detectHandlerMethods(bean); + } + return bean; + } + + @Override + public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { + if (isHandler(bean.getClass())) { + RequestMappingInfo mapping = + this.integrationRequestMappingHandlerMapping.getMappingForEndpoint((BaseHttpInboundEndpoint) bean); + if (mapping != null) { + this.integrationRequestMappingHandlerMapping.unregisterMapping(mapping); + } + } + } + + @Override + public boolean requiresDestruction(Object bean) { + return isHandler(bean.getClass()); + } + + private boolean isHandler(Class> beanType) { + return HttpRequestHandlingEndpointSupport.class.isAssignableFrom(beanType); + } + +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java index 88eebd3858..83ee5a4b36 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 the original author or authors. + * Copyright 2013-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. @@ -26,8 +26,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; @@ -73,11 +71,6 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl * {@link org.springframework.web.servlet.HandlerMapping} * compromise implementation between method-level annotations and component-level * (e.g. Spring Integration XML) configurations. - *
- * Starting with version 5.1, this class implements {@link DestructionAwareBeanPostProcessor} to - * register HTTP endpoints at runtime for dynamically declared beans, e.g. via - * {@link org.springframework.integration.dsl.context.IntegrationFlowContext}, and unregister - * them during the {@link BaseHttpInboundEndpoint} destruction. *
* This class extends the Spring MVC {@link RequestMappingHandlerMapping} class, inheriting
* most of its logic, especially {@link #handleNoMatch(java.util.Set, String, HttpServletRequest)},
@@ -96,7 +89,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
* @see RequestMappingHandlerMapping
*/
public final class IntegrationRequestMappingHandlerMapping extends RequestMappingHandlerMapping
- implements ApplicationListener