diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aot/CoreRuntimeHintsRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/aot/CoreRuntimeHintsRegistrar.java new file mode 100644 index 00000000000..a688e854bf9 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/aot/CoreRuntimeHintsRegistrar.java @@ -0,0 +1,157 @@ +/* + * Copyright 2022 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.aot; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Properties; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.springframework.aop.SpringProxy; +import org.springframework.aop.framework.Advised; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ProxyHints; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.SerializationHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.aot.hint.support.RuntimeHintsUtils; +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.context.SmartLifecycle; +import org.springframework.context.annotation.Bean; +import org.springframework.core.DecoratingProxy; +import org.springframework.integration.context.IntegrationContextUtils; +import org.springframework.integration.core.GenericSelector; +import org.springframework.integration.core.Pausable; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.gateway.MethodArgsHolder; +import org.springframework.integration.gateway.RequestReplyExchanger; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.handler.DelayHandler; +import org.springframework.integration.handler.GenericHandler; +import org.springframework.integration.history.MessageHistory; +import org.springframework.integration.json.JsonPathUtils; +import org.springframework.integration.message.AdviceMessage; +import org.springframework.integration.routingslip.ExpressionEvaluatingRoutingSlipRouteStrategy; +import org.springframework.integration.store.MessageGroupMetadata; +import org.springframework.integration.store.MessageHolder; +import org.springframework.integration.store.MessageMetadata; +import org.springframework.integration.support.MutableMessage; +import org.springframework.integration.support.MutableMessageHeaders; +import org.springframework.integration.transformer.GenericTransformer; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.ErrorMessage; +import org.springframework.messaging.support.GenericMessage; + +/** + * {@link RuntimeHintsRegistrar} for Spring Integration core module. + * + * @author Artem Bilan + * + * @since 6.0 + */ +class CoreRuntimeHintsRegistrar implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ReflectionHints reflectionHints = hints.reflection(); + Stream.of( + GenericSelector.class, + GenericTransformer.class, + GenericHandler.class, + Function.class, + Supplier.class, + BeanExpressionContext.class, + IntegrationContextUtils.class, + MethodArgsHolder.class, + AbstractReplyProducingMessageHandler.RequestHandler.class, + ExpressionEvaluatingRoutingSlipRouteStrategy.RequestAndReply.class, + Pausable.class, + SmartLifecycle.class) + .forEach(type -> + reflectionHints.registerType(type, + builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS))); + + reflectionHints.registerType(JsonPathUtils.class, + builder -> + builder.onReachableType(TypeReference.of("com.jayway.jsonpath.JsonPath")) + .withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)); + + // For #xpath() SpEL function + reflectionHints.registerTypeIfPresent(classLoader, "org.springframework.integration.xml.xpath.XPathUtils", + builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)); + + Stream.of( + "kotlin.jvm.functions.Function0", + "kotlin.jvm.functions.Function1", + "kotlin.Unit") + .forEach(type -> + reflectionHints.registerTypeIfPresent(classLoader, type, + builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS))); + + hints.resources().registerPattern("META-INF/spring.integration.properties"); + + SerializationHints serializationHints = hints.serialization(); + Stream.of( + String.class, + Number.class, + Long.class, + Date.class, + ArrayList.class, + HashMap.class, + Properties.class, + Hashtable.class, + Exception.class, + UUID.class, + GenericMessage.class, + ErrorMessage.class, + MessageHeaders.class, + AdviceMessage.class, + MutableMessage.class, + MutableMessageHeaders.class, + MessageGroupMetadata.class, + MessageHolder.class, + MessageMetadata.class, + MessageHistory.class, + MessageHistory.Entry.class, + DelayHandler.DelayedMessageWrapper.class) + .forEach(serializationHints::registerType); + + ProxyHints proxyHints = hints.proxies(); + + registerSpringJdkProxy(proxyHints, RequestReplyExchanger.class); + registerSpringJdkProxy(proxyHints, AbstractReplyProducingMessageHandler.RequestHandler.class); + registerSpringJdkProxy(proxyHints, IntegrationFlow.class, SmartLifecycle.class); + + // For MessagingAnnotationPostProcessor + RuntimeHintsUtils.registerAnnotation(hints, Bean.class); + } + + private static void registerSpringJdkProxy(ProxyHints proxyHints, Class... proxiedInterfaces) { + proxyHints + .registerJdkProxy(builder -> + builder.proxiedInterfaces(proxiedInterfaces) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aot/GatewayProxyBeanRegistrationAotProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aot/GatewayProxyBeanRegistrationAotProcessor.java new file mode 100644 index 00000000000..bc79d2e12e8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/aot/GatewayProxyBeanRegistrationAotProcessor.java @@ -0,0 +1,57 @@ +/* + * Copyright 2022 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.aot; + +import org.springframework.aop.SpringProxy; +import org.springframework.aop.framework.Advised; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; +import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; +import org.springframework.beans.factory.support.RegisteredBean; +import org.springframework.core.DecoratingProxy; +import org.springframework.integration.gateway.GatewayProxyFactoryBean; +import org.springframework.integration.gateway.RequestReplyExchanger; + +/** + * {@link BeanRegistrationAotProcessor} for registering proxy interfaces of the {@link GatewayProxyFactoryBean} beans. + * + * @author Artem Bilan + * + * @since 6.0 + */ +class GatewayProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor { + + @Override + public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { + Class beanType = registeredBean.getBeanClass(); + if (GatewayProxyFactoryBean.class.isAssignableFrom(beanType)) { + GatewayProxyFactoryBean proxyFactoryBean = + registeredBean.getBeanFactory() + .getBean(BeanFactory.FACTORY_BEAN_PREFIX + registeredBean.getBeanName(), + GatewayProxyFactoryBean.class); + Class serviceInterface = proxyFactoryBean.getObjectType(); + if (!RequestReplyExchanger.class.equals(serviceInterface)) { + return (generationContext, beanRegistrationCode) -> + generationContext.getRuntimeHints().proxies() + .registerJdkProxy(serviceInterface, SpringProxy.class, Advised.class, + DecoratingProxy.class); + } + } + return null; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aot/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/aot/package-info.java new file mode 100644 index 00000000000..7949a5e0ad9 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/aot/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides classes to support Spring AOT. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.integration.aot; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ConsumerEndpointFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ConsumerEndpointFactoryBean.java index b4600aa2c1e..90a57dd84e2 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ConsumerEndpointFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/ConsumerEndpointFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -27,6 +27,7 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.NameMatchMethodPointcutAdvisor; +import org.springframework.aot.hint.annotation.Reflective; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -123,6 +124,7 @@ public class ConsumerEndpointFactoryBean private volatile boolean initialized; + @Reflective // The native image doesn't see this method because its type is not specific public void setHandler(Object handler) { Assert.isTrue(handler instanceof MessageHandler || handler instanceof ReactiveMessageHandler, "'handler' must be an instance of 'MessageHandler' or 'ReactiveMessageHandler'"); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ConverterRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ConverterRegistrar.java index 2f330913365..6db922da052 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ConverterRegistrar.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/ConverterRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -16,8 +16,8 @@ package org.springframework.integration.config; -import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; @@ -44,17 +44,9 @@ */ class ConverterRegistrar implements InitializingBean, ApplicationContextAware { - private final Set converters; - private ApplicationContext applicationContext; - ConverterRegistrar() { - this(new HashSet<>()); - } - - ConverterRegistrar(Set converters) { - this.converters = converters; } @Override @@ -75,11 +67,27 @@ public void afterPropertiesSet() { } private void registerConverters(GenericConversionService conversionService) { - this.converters.addAll(this.applicationContext.getBeansWithAnnotation(IntegrationConverter.class).values()); + Set converters = + this.applicationContext.getBeansOfType(IntegrationConverterRegistration.class) + .values() + .stream().map(IntegrationConverterRegistration::converter) + .collect(Collectors.toSet()); if (JacksonPresent.isJackson2Present()) { - this.converters.add(new JsonNodeWrapperToJsonNodeConverter()); + converters.add(new JsonNodeWrapperToJsonNodeConverter()); } - ConversionServiceFactory.registerConverters(this.converters, conversionService); + ConversionServiceFactory.registerConverters(converters, conversionService); + } + + /** + * A configuration supporting bean for converter with a {@link IntegrationConverter} + * annotation. + * + * @param converter the target converter bean with a {@link IntegrationConverter}. + * + * @since 6.0 + */ + record IntegrationConverterRegistration(Object converter) { + } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/DefaultConfiguringBeanFactoryPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/DefaultConfiguringBeanFactoryPostProcessor.java index 32131a9c646..2ad248a36dd 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/DefaultConfiguringBeanFactoryPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/DefaultConfiguringBeanFactoryPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -45,7 +45,6 @@ import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.context.IntegrationProperties; import org.springframework.integration.handler.LoggingHandler; -import org.springframework.integration.json.JsonNodeWrapperToJsonNodeConverter; import org.springframework.integration.json.JsonPathUtils; import org.springframework.integration.support.DefaultMessageBuilderFactory; import org.springframework.integration.support.SmartLifecycleRoleController; @@ -53,7 +52,6 @@ import org.springframework.integration.support.channel.ChannelResolverUtils; import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter; import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter; -import org.springframework.integration.support.json.JacksonPresent; import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.util.ClassUtils; @@ -337,7 +335,6 @@ private void registerBuiltInBeans() { int registryId = System.identityHashCode(this.registry); jsonPath(registryId); xpath(registryId); - jsonNodeToString(registryId); REGISTRIES_PROCESSED.add(registryId); } @@ -365,19 +362,6 @@ private void xpath(int registryId) throws LinkageError { } } - // TODO Remove in 6.0 - private void jsonNodeToString(int registryId) { - if (!this.beanFactory.containsBean( - IntegrationContextUtils.JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER) && - !REGISTRIES_PROCESSED.contains(registryId) && JacksonPresent.isJackson2Present()) { - - this.registry.registerBeanDefinition( - IntegrationContextUtils.JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER, - new RootBeanDefinition(JsonNodeWrapperToJsonNodeConverter.class, - JsonNodeWrapperToJsonNodeConverter::new)); - } - } - /** * Register a {@link SmartLifecycleRoleController} if necessary. */ diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorInitializer.java index 1471e9a1d1d..732d4c223ac 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorInitializer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -28,7 +28,7 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper; -import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; /** @@ -45,42 +45,38 @@ */ public class GlobalChannelInterceptorInitializer implements IntegrationConfigurationInitializer { - private ConfigurableListableBeanFactory beanFactory; - @Override public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + for (String beanName : registry.getBeanDefinitionNames()) { BeanDefinition beanDefinition = registry.getBeanDefinition(beanName); - if (beanDefinition instanceof AnnotatedBeanDefinition) { - AnnotationMetadata metadata = ((AnnotatedBeanDefinition) beanDefinition).getMetadata(); - Map annotationAttributes = - metadata.getAnnotationAttributes(GlobalChannelInterceptor.class.getName()); - if (CollectionUtils.isEmpty(annotationAttributes) - && beanDefinition.getSource() instanceof MethodMetadata) { - MethodMetadata beanMethod = (MethodMetadata) beanDefinition.getSource(); - annotationAttributes = - beanMethod.getAnnotationAttributes(GlobalChannelInterceptor.class.getName()); // NOSONAR not null - } - - if (!CollectionUtils.isEmpty(annotationAttributes)) { - BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorWrapper.class, - () -> createGlobalChannelInterceptorWrapper(beanName)) - .addConstructorArgReference(beanName) - .addPropertyValue("patterns", annotationAttributes.get("patterns")) - .addPropertyValue("order", annotationAttributes.get("order")); + Map interceptorAttributes = obtainGlobalChannelInterceptorAttributes(beanDefinition); + if (!CollectionUtils.isEmpty(interceptorAttributes)) { + BeanDefinitionBuilder builder = + BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorWrapper.class) + .addConstructorArgReference(beanName) + .addPropertyValue("patterns", interceptorAttributes.get("patterns")) + .addPropertyValue("order", interceptorAttributes.get("order")); - BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), registry); - } + BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), registry); } } } - private GlobalChannelInterceptorWrapper createGlobalChannelInterceptorWrapper(String interceptorBeanName) { - ChannelInterceptor interceptor = this.beanFactory.getBean(interceptorBeanName, ChannelInterceptor.class); - return new GlobalChannelInterceptorWrapper(interceptor); + @Nullable + private static Map obtainGlobalChannelInterceptorAttributes(BeanDefinition beanDefinition) { + Map annotationAttributes = null; + if (beanDefinition instanceof AnnotatedBeanDefinition annotatedBeanDefinition) { + AnnotationMetadata metadata = annotatedBeanDefinition.getMetadata(); + annotationAttributes = metadata.getAnnotationAttributes(GlobalChannelInterceptor.class.getName()); + if (CollectionUtils.isEmpty(annotationAttributes) + && beanDefinition.getSource() instanceof MethodMetadata beanMethod) { + + annotationAttributes = beanMethod.getAnnotationAttributes(GlobalChannelInterceptor.class.getName()); + } + } + return annotationAttributes; } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverterInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverterInitializer.java index 83ec13505a2..cf81a3e3982 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverterInitializer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverterInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -17,15 +17,28 @@ package org.springframework.integration.config; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +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.beans.factory.support.RootBeanDefinition; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.MethodMetadata; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.support.utils.IntegrationUtils; /** + * The {@link IntegrationConfigurationInitializer} to populate + * {@link ConverterRegistrar.IntegrationConverterRegistration} + * for converter beans marked with an {@link IntegrationConverter} annotation. + *

+ * {@link org.springframework.context.annotation.Bean} methods are also processed. + * * @author Artem Bilan * @author Gary Russell + * * @since 4.0 */ public class IntegrationConverterInitializer implements IntegrationConfigurationInitializer { @@ -34,6 +47,17 @@ public class IntegrationConverterInitializer implements IntegrationConfiguration public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + for (String beanName : registry.getBeanDefinitionNames()) { + BeanDefinition beanDefinition = registry.getBeanDefinition(beanName); + if (isIntegrationConverter(beanDefinition)) { + BeanDefinitionBuilder builder = + BeanDefinitionBuilder.genericBeanDefinition( + ConverterRegistrar.IntegrationConverterRegistration.class) + .addConstructorArgReference(beanName); + BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), registry); + } + } + if (!registry.containsBeanDefinition(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME)) { registry.registerBeanDefinition(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME, new RootBeanDefinition(ConverterRegistrar.class, ConverterRegistrar::new)); @@ -44,6 +68,19 @@ public void initialize(ConfigurableListableBeanFactory beanFactory) throws Beans new RootBeanDefinition(CustomConversionServiceFactoryBean.class, CustomConversionServiceFactoryBean::new)); } + + } + + private static boolean isIntegrationConverter(BeanDefinition beanDefinition) { + boolean hasIntegrationConverter = false; + if (beanDefinition instanceof AnnotatedBeanDefinition annotatedBeanDefinition) { + AnnotationMetadata metadata = annotatedBeanDefinition.getMetadata(); + hasIntegrationConverter = metadata.hasAnnotation(IntegrationConverter.class.getName()); + if (!hasIntegrationConverter && beanDefinition.getSource() instanceof MethodMetadata beanMethodMetadata) { + hasIntegrationConverter = beanMethodMetadata.isAnnotated(IntegrationConverter.class.getName()); + } + } + return hasIntegrationConverter; } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ConverterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ConverterParser.java index 881e91afae0..e901b615c52 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ConverterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ConverterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -16,18 +16,15 @@ package org.springframework.integration.config.xml; -import java.util.Set; - import org.w3c.dom.Element; import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.ManagedSet; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.context.IntegrationContextUtils; @@ -61,32 +58,13 @@ protected AbstractBeanDefinition parseInternal(Element element, ParserContext pa return null; } - @SuppressWarnings("unchecked") - private static void registerConverter(BeanDefinitionRegistry registry, - BeanMetadataElement converterBeanDefinition) { - - Set converters = new ManagedSet<>(); - if (!registry.containsBeanDefinition(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME)) { - BeanDefinitionBuilder converterRegistrarBuilder = - BeanDefinitionBuilder.genericBeanDefinition( - IntegrationContextUtils.BASE_PACKAGE + ".config.ConverterRegistrar") - .addConstructorArgValue(converters); - registry.registerBeanDefinition(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME, - converterRegistrarBuilder.getBeanDefinition()); - } - else { - BeanDefinition converterRegistrarBeanDefinition = registry - .getBeanDefinition(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME); - converters = (Set) converterRegistrarBeanDefinition - .getConstructorArgumentValues() - .getIndexedArgumentValues() - .values() - .iterator() - .next() - .getValue(); - } - - converters.add(converterBeanDefinition); // NOSONAR never null + private static void registerConverter(BeanDefinitionRegistry registry, BeanMetadataElement targetBeanDefinition) { + BeanDefinitionBuilder builder = + BeanDefinitionBuilder.genericBeanDefinition( + IntegrationContextUtils.BASE_PACKAGE + + ".config.ConverterRegistrar.IntegrationConverterRegistration") + .addConstructorArgValue(targetBeanDefinition); + BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), registry); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/core/Pausable.java b/spring-integration-core/src/main/java/org/springframework/integration/core/Pausable.java index cde8fd6ee7a..68d6b685c78 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/core/Pausable.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/core/Pausable.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -16,6 +16,7 @@ package org.springframework.integration.core; +import org.springframework.aot.hint.annotation.Reflective; import org.springframework.integration.support.management.ManageableLifecycle; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; @@ -26,6 +27,8 @@ * messages. * * @author Gary Russell + * @author Artem Bilan + * * @since 5.0.3 * */ @@ -35,12 +38,14 @@ public interface Pausable extends ManageableLifecycle { * Pause the endpoint. */ @ManagedOperation(description = "Pause the component") + @Reflective void pause(); /** * Resume the endpoint if paused. */ @ManagedOperation(description = "Resume the component") + @Reflective void resume(); /** @@ -49,6 +54,7 @@ public interface Pausable extends ManageableLifecycle { * @since 5.4 */ @ManagedAttribute(description = "Is the component paused?") + @Reflective default boolean isPaused() { throw new UnsupportedOperationException("This component does not implement this method"); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/management/ManageableLifecycle.java b/spring-integration-core/src/main/java/org/springframework/integration/support/management/ManageableLifecycle.java index c8100e8677a..ba6f990762c 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/management/ManageableLifecycle.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/management/ManageableLifecycle.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors. + * Copyright 2020-2022 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. @@ -16,6 +16,7 @@ package org.springframework.integration.support.management; +import org.springframework.aot.hint.annotation.Reflective; import org.springframework.context.Lifecycle; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; @@ -30,14 +31,17 @@ public interface ManageableLifecycle extends Lifecycle { @ManagedOperation(description = "Start the component") + @Reflective @Override void start(); @ManagedOperation(description = "Stop the component") + @Reflective @Override void stop(); @ManagedAttribute(description = "Is the component running?") + @Reflective @Override boolean isRunning(); diff --git a/spring-integration-core/src/main/resources/META-INF/spring/aot.factories b/spring-integration-core/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..312888dc319 --- /dev/null +++ b/spring-integration-core/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.integration.aot.CoreRuntimeHintsRegistrar +org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=org.springframework.integration.aot.GatewayProxyBeanRegistrationAotProcessor diff --git a/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContextTests.java b/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContextTests.java index 4960f677ce9..d1e9525a6c9 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContextTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 the original author or authors. + * Copyright 2013-2022 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. @@ -17,14 +17,13 @@ package org.springframework.integration.expression; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import org.junit.jupiter.api.Test; @@ -34,6 +33,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.convert.ConversionService; import org.springframework.expression.EvaluationContext; import org.springframework.expression.PropertyAccessor; import org.springframework.integration.channel.QueueChannel; @@ -41,12 +41,17 @@ import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.json.JsonPathUtils; import org.springframework.integration.json.TestPerson; +import org.springframework.integration.support.MutableMessage; import org.springframework.integration.support.MutableMessageBuilder; +import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.PollableChannel; import org.springframework.messaging.support.GenericMessage; +import org.springframework.util.ClassUtils; + +import com.fasterxml.jackson.databind.JsonNode; /** * @author Gary Russell @@ -74,7 +79,7 @@ public class ParentContextTests { */ @Test @SuppressWarnings("unchecked") - public void testSpelBeanReferencesInChildAndParent() { + public void testSpelBeanReferencesInChildAndParent() throws ClassNotFoundException { AbstractApplicationContext parent = new ClassPathXmlApplicationContext("ParentContext-context.xml", this.getClass()); @@ -153,40 +158,21 @@ public void testSpelBeanReferencesInChildAndParent() { IntegrationEvaluationContextFactoryBean evaluationContextFactoryBean = child.getBean("&" + IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME, IntegrationEvaluationContextFactoryBean.class); - try { - evaluationContextFactoryBean.setPropertyAccessors(Collections.emptyMap()); - fail("IllegalArgumentException expected."); - } - catch (Exception e) { - assertThat(e).isInstanceOf(IllegalArgumentException.class); - } + + assertThatIllegalArgumentException() + .isThrownBy(() -> evaluationContextFactoryBean.setPropertyAccessors(Collections.emptyMap())); parent.getBean("fromParentToChild", MessageChannel.class).send(new GenericMessage<>("foo")); out = child.getBean("output", QueueChannel.class).receive(0); assertThat(out).isNotNull(); - assertThat(out.getClass().getName()).isEqualTo("org.springframework.integration.support.MutableMessage"); + assertThat(out).isInstanceOf(MutableMessage.class); assertThat(out.getPayload()).isEqualTo("FOO"); - assertThat(parent - .containsBean(IntegrationContextUtils.JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER)) - .isTrue(); - - assertThat(child - .containsBean(IntegrationContextUtils.JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER)) - .isTrue(); - - Object converterRegistrar = parent.getBean(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME); - assertThat(converterRegistrar).isNotNull(); - Set converters = TestUtils.getPropertyValue(converterRegistrar, "converters", Set.class); - boolean jsonNodeWrapperToJsonNodeConverterPresent = false; - for (Object converter : converters) { - if ("JsonNodeWrapperToJsonNodeConverter".equals(converter.getClass().getSimpleName())) { - jsonNodeWrapperToJsonNodeConverterPresent = true; - break; - } - } - - assertThat(jsonNodeWrapperToJsonNodeConverterPresent).isTrue(); + ConversionService conversionService = IntegrationUtils.getConversionService(parent); + Class jsonNodeWrapperClass = + ClassUtils.forName("org.springframework.integration.json.JsonPropertyAccessor.JsonNodeWrapper", + ClassUtils.getDefaultClassLoader()); + assertThat(conversionService.canConvert(jsonNodeWrapperClass, JsonNode.class)).isTrue(); MessageChannel input = parent.getBean("testJsonNodeToStringConverterInputChannel", MessageChannel.class); PollableChannel output = parent.getBean("testJsonNodeToStringConverterOutputChannel", PollableChannel.class); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/aot/FileRuntimeHintsRegistrar.java b/spring-integration-file/src/main/java/org/springframework/integration/file/aot/FileRuntimeHintsRegistrar.java new file mode 100644 index 00000000000..821c95590b3 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/aot/FileRuntimeHintsRegistrar.java @@ -0,0 +1,42 @@ +/* + * Copyright 2022 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.file.aot; + +import java.util.stream.Stream; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.SerializationHints; +import org.springframework.integration.file.splitter.FileSplitter; + +/** + * {@link RuntimeHintsRegistrar} for Spring Integration file module. + * + * @author Artem Bilan + * + * @since 6.0 + */ +class FileRuntimeHintsRegistrar implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + SerializationHints serializationHints = hints.serialization(); + Stream.of(FileSplitter.FileMarker.class, FileSplitter.FileMarker.Mark.class) + .forEach(serializationHints::registerType); + } + +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/aot/package-info.java b/spring-integration-file/src/main/java/org/springframework/integration/file/aot/package-info.java new file mode 100644 index 00000000000..6b0ba74a128 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/aot/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides classes to support Spring AOT. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.integration.file.aot; diff --git a/spring-integration-file/src/main/resources/META-INF/spring/aot.factories b/spring-integration-file/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..73e0521b876 --- /dev/null +++ b/spring-integration-file/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.integration.file.aot.FileRuntimeHintsRegistrar diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/aot/HttpRuntimeHintsRegistrar.java b/spring-integration-http/src/main/java/org/springframework/integration/http/aot/HttpRuntimeHintsRegistrar.java new file mode 100644 index 00000000000..854908a25e8 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/aot/HttpRuntimeHintsRegistrar.java @@ -0,0 +1,47 @@ +/* + * Copyright 2022 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.aot; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.server.WebHandler; + +/** + * {@link RuntimeHintsRegistrar} for Spring Integration core module. + * + * @author Artem Bilan + * + * @since 6.0 + */ +class HttpRuntimeHintsRegistrar implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ReflectionHints reflectionHints = hints.reflection(); + reflectionHints.registerType(WebHandler.class, builder -> + builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)); + + reflectionHints.registerType(HttpRequestHandler.class, builder -> + builder.onReachableType(TypeReference.of("jakarta.servlet.http.HttpServletRequest")) + .withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)); + } + +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/aot/package-info.java b/spring-integration-http/src/main/java/org/springframework/integration/http/aot/package-info.java new file mode 100644 index 00000000000..0722aa47c7e --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/aot/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides classes to support Spring AOT. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.integration.http.aot; diff --git a/spring-integration-http/src/main/resources/META-INF/spring/aot.factories b/spring-integration-http/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..0d9628f0ff6 --- /dev/null +++ b/spring-integration-http/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.integration.http.aot.HttpRuntimeHintsRegistrar diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/aot/JdbcRuntimeHintsRegistrar.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/aot/JdbcRuntimeHintsRegistrar.java new file mode 100644 index 00000000000..417ce7c49b8 --- /dev/null +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/aot/JdbcRuntimeHintsRegistrar.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 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.jdbc.aot; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * {@link RuntimeHintsRegistrar} for Spring Integration JDBC module. + * + * @author Artem Bilan + * + * @since 6.0 + */ +class JdbcRuntimeHintsRegistrar implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.resources().registerPattern("org/springframework/integration/jdbc/schema-*.sql"); + } + +} diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/aot/package-info.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/aot/package-info.java new file mode 100644 index 00000000000..fe086ff602d --- /dev/null +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/aot/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides classes to support Spring AOT. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.integration.jdbc.aot; diff --git a/spring-integration-jdbc/src/main/resources/META-INF/spring/aot.factories b/spring-integration-jdbc/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..0fb62260208 --- /dev/null +++ b/spring-integration-jdbc/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.integration.jdbc.aot.JdbcRuntimeHintsRegistrar diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XsltPayloadTransformer.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XsltPayloadTransformer.java index e0223e2fc71..59370f1665a 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XsltPayloadTransformer.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XsltPayloadTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -110,24 +110,6 @@ public class XsltPayloadTransformer extends AbstractXmlTransformer implements Be private String[] xsltParamHeaders; - private static final Class SERVLET_CONTEXT_RESOURCE_CLASS; - - static { - Class aClass = null; - try { - aClass = - ClassUtils.forName("org.springframework.web.context.support.ServletContextResource", - ClassUtils.getDefaultClassLoader()); - } - - catch (ClassNotFoundException e) { - // No 'ServletContextResource' class present - ignoring - } - finally { - SERVLET_CONTEXT_RESOURCE_CLASS = aClass; - } - } - public XsltPayloadTransformer(Templates templates) { this(templates, null); } @@ -160,8 +142,8 @@ public XsltPayloadTransformer(Resource xslResource, ResultTransformer resultTran Assert.isTrue(xslResource instanceof ClassPathResource || xslResource instanceof FileSystemResource || xslResource instanceof VfsResource || // NOSONAR boolean complexity - (SERVLET_CONTEXT_RESOURCE_CLASS != null - && SERVLET_CONTEXT_RESOURCE_CLASS.isInstance(xslResource)), + xslResource.getClass().getName() + .equals("org.springframework.web.context.support.ServletContextResource"), "Only 'ClassPathResource', 'FileSystemResource', 'ServletContextResource' or 'VfsResource'" + " are supported directly in this transformer. For any other 'Resource' implementations" + " consider to use a 'Templates'-based constructor instantiation."); @@ -288,7 +270,7 @@ protected Object doTransform(Message message) { else { payload = message.getPayload(); } - Object transformedPayload = null; + Object transformedPayload; if (this.alwaysUseResultFactory) { transformedPayload = transformUsingResultFactory(payload, transformer); }