GH-3828: Initial Spring AOT support (#3832)

* GH-3828: Initial Spring AOT support

Fixes https://github.com/spring-projects/spring-integration/issues/3828

* Provide an infrastructure based on a new Spring AOT engine in the latest Spring Framework
* Introduce `RuntimeHintsRegistrar` impls into modules which require some reflection,
resources or proxies and serialization available in the native image
* Mark some framework method with the `@Reflective` to make their reflection
info available in the native image, for example for SpEL or JMX invocations
* Add a `GatewayProxyBeanRegistrationAotProcessor` to register proxy interfaces
info for messaging gateways (either instance of the `GatewayProxyFactoryBean`)
* Rework `ConverterRegistrar` to not use a `beanFactory.getBeansWithAnnotation()`
since it is not available after AOT phase.
Instead, register an intermediate `IntegrationConverterRegistration` bean definition
from the `IntegrationConverterInitializer`
* Refactor `GlobalChannelInterceptorInitializer` a bit according to a new logic in the
`IntegrationConverterInitializer`
* Remove `JsonNodeWrapperToJsonNodeConverter` bean registration from the
`DefaultConfiguringBeanFactoryPostProcessor` - it is added by the `ConverterRegistrar`
into the target `ConversionService`
* Fix `ParentContextTests` respectively a `JsonNodeWrapperToJsonNodeConverter` bean removal
* Refactor `XsltPayloadTransformer` to not load a `ServletContextResource`, but just use its
name for the `xslResource` condition

* * Rework AOT support according latest changes and requirements
* Remove `@Bean` reflection since it is not needed any more
* Add `AotDetector.useGeneratedArtifacts()` condition to not register beans
one more time at runtime after AOT build phase
* Fix deprecation in the WebFlux test from the latest SF
This commit is contained in:
Artem Bilan
2022-09-01 13:59:07 -04:00
committed by GitHub
parent 80eea1508e
commit 5080fc2f45
25 changed files with 533 additions and 175 deletions

View File

@@ -0,0 +1,151 @@
/*
* 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.beans.factory.config.BeanExpressionContext;
import org.springframework.context.SmartLifecycle;
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.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.support.management.ManageableSmartLifecycle;
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 CoreRuntimeHints 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,
ManageableSmartLifecycle.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, AbstractReplyProducingMessageHandler.RequestHandler.class);
registerSpringJdkProxy(proxyHints, IntegrationFlow.class, SmartLifecycle.class);
}
private static void registerSpringJdkProxy(ProxyHints proxyHints, Class<?>... proxiedInterfaces) {
proxyHints
.registerJdkProxy(builder ->
builder.proxiedInterfaces(proxiedInterfaces)
.proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class));
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.function.Predicate;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.DecoratingProxy;
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
import org.springframework.javapoet.CodeBlock;
/**
* {@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) {
if (GatewayProxyFactoryBean.class.isAssignableFrom(registeredBean.getBeanClass())) {
return BeanRegistrationAotContribution
.ofBeanRegistrationCodeFragmentsCustomizer(GatewayProxyBeanRegistrationCodeFragments::new);
}
return null;
}
private static class GatewayProxyBeanRegistrationCodeFragments extends BeanRegistrationCodeFragments {
GatewayProxyBeanRegistrationCodeFragments(BeanRegistrationCodeFragments codeFragments) {
super(codeFragments);
}
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
Predicate<String> attributeFilter) {
Class<?> serviceInterface = (Class<?>) beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
generationContext.getRuntimeHints().proxies()
.registerJdkProxy(serviceInterface, SpringProxy.class, Advised.class, DecoratingProxy.class);
return super.generateSetBeanDefinitionPropertiesCode(generationContext, beanRegistrationCode,
beanDefinition, FactoryBean.OBJECT_TYPE_ATTRIBUTE::equals);
}
}
}

View File

@@ -0,0 +1,6 @@
/**
* Provides classes to support Spring AOT.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.integration.aot;

View File

@@ -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.Advised;
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'");

View File

@@ -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 @@ import org.springframework.util.Assert;
*/
class ConverterRegistrar implements InitializingBean, ApplicationContextAware {
private final Set<Object> converters;
private ApplicationContext applicationContext;
ConverterRegistrar() {
this(new HashSet<>());
}
ConverterRegistrar(Set<Object> converters) {
this.converters = converters;
}
@Override
@@ -75,11 +67,27 @@ class ConverterRegistrar implements InitializingBean, ApplicationContextAware {
}
private void registerConverters(GenericConversionService conversionService) {
this.converters.addAll(this.applicationContext.getBeansWithAnnotation(IntegrationConverter.class).values());
Set<Object> 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) {
}
}

View File

@@ -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.
@@ -23,6 +23,7 @@ import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
import org.springframework.aot.AotDetector;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
@@ -45,7 +46,6 @@ import org.springframework.integration.channel.PublishSubscribeChannel;
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 +53,6 @@ import org.springframework.integration.support.channel.BeanFactoryChannelResolve
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;
@@ -108,26 +107,28 @@ public class DefaultConfiguringBeanFactoryPostProcessor
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
this.registry = registry;
this.beanFactory = (ConfigurableListableBeanFactory) registry;
if (!AotDetector.useGeneratedArtifacts()) {
this.registry = registry;
this.beanFactory = (ConfigurableListableBeanFactory) registry;
registerBeanFactoryChannelResolver();
registerMessagePublishingErrorHandler();
registerNullChannel();
registerErrorChannel();
registerIntegrationEvaluationContext();
registerTaskScheduler();
registerIdGeneratorConfigurer();
registerIntegrationProperties();
registerBuiltInBeans();
registerRoleController();
registerMessageBuilderFactory();
registerHeaderChannelRegistry();
registerGlobalChannelInterceptorProcessor();
registerDefaultDatatypeChannelMessageConverter();
registerArgumentResolverMessageConverter();
registerMessageHandlerMethodFactory();
registerListMessageHandlerMethodFactory();
registerBeanFactoryChannelResolver();
registerMessagePublishingErrorHandler();
registerNullChannel();
registerErrorChannel();
registerIntegrationEvaluationContext();
registerTaskScheduler();
registerIdGeneratorConfigurer();
registerIntegrationProperties();
registerBuiltInBeans();
registerRoleController();
registerMessageBuilderFactory();
registerHeaderChannelRegistry();
registerGlobalChannelInterceptorProcessor();
registerDefaultDatatypeChannelMessageConverter();
registerArgumentResolverMessageConverter();
registerMessageHandlerMethodFactory();
registerListMessageHandlerMethodFactory();
}
}
@Override
@@ -337,7 +338,6 @@ public class DefaultConfiguringBeanFactoryPostProcessor
int registryId = System.identityHashCode(this.registry);
jsonPath(registryId);
xpath(registryId);
jsonNodeToString(registryId);
REGISTRIES_PROCESSED.add(registryId);
}
@@ -365,19 +365,6 @@ public class DefaultConfiguringBeanFactoryPostProcessor
}
}
// 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.
*/

View File

@@ -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.beans.factory.support.BeanDefinitionRegistry;
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 @@ import org.springframework.util.CollectionUtils;
*/
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<String, Object> 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
}
Map<String, Object> 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"));
if (!CollectionUtils.isEmpty(annotationAttributes)) {
BeanDefinitionBuilder builder =
BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorWrapper.class,
() -> createGlobalChannelInterceptorWrapper(beanName))
.addConstructorArgReference(beanName)
.addPropertyValue("patterns", annotationAttributes.get("patterns"))
.addPropertyValue("order", annotationAttributes.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<String, Object> obtainGlobalChannelInterceptorAttributes(BeanDefinition beanDefinition) {
Map<String, Object> 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;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 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.
@@ -18,6 +18,7 @@ package org.springframework.integration.config;
import java.util.List;
import org.springframework.aot.AotDetector;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -31,20 +32,23 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
*
* @author Artem Bilan
* @author Gary Russell
*
* @since 4.0
*/
public class IntegrationConfigurationBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry;
if (!AotDetector.useGeneratedArtifacts()) {
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry;
List<IntegrationConfigurationInitializer> initializers =
SpringFactoriesLoader.loadFactories(IntegrationConfigurationInitializer.class,
beanFactory.getBeanClassLoader());
List<IntegrationConfigurationInitializer> initializers =
SpringFactoriesLoader.loadFactories(IntegrationConfigurationInitializer.class,
beanFactory.getBeanClassLoader());
for (IntegrationConfigurationInitializer initializer : initializers) {
initializer.initialize(beanFactory);
for (IntegrationConfigurationInitializer initializer : initializers) {
initializer.initialize(beanFactory);
}
}
}

View File

@@ -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.
* <p>
* {@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 class IntegrationConverterInitializer implements IntegrationConfiguration
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;
}
}

View File

@@ -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 @@ public class ConverterParser extends AbstractBeanDefinitionParser {
return null;
}
@SuppressWarnings("unchecked")
private static void registerConverter(BeanDefinitionRegistry registry,
BeanMetadataElement converterBeanDefinition) {
Set<BeanMetadataElement> 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<BeanMetadataElement>) 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);
}
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.integration.aot.CoreRuntimeHints
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=org.springframework.integration.aot.GatewayProxyBeanRegistrationAotProcessor

View File

@@ -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.BeanFactoryAware;
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.config.IntegrationEvaluationContextFactor
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 class ParentContextTests {
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);

View File

@@ -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 FileRuntimeHints 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);
}
}

View File

@@ -0,0 +1,6 @@
/**
* Provides classes to support Spring AOT.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.integration.file.aot;

View File

@@ -0,0 +1 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.integration.file.aot.FileRuntimeHints

View File

@@ -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 HttpRuntimeHints 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));
}
}

View File

@@ -0,0 +1,6 @@
/**
* Provides classes to support Spring AOT.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.integration.http.aot;

View File

@@ -0,0 +1 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.integration.http.aot.HttpRuntimeHints

View File

@@ -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 JdbcRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.resources().registerPattern("org/springframework/integration/jdbc/schema-*.sql");
}
}

View File

@@ -0,0 +1,6 @@
/**
* Provides classes to support Spring AOT.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.integration.jdbc.aot;

View File

@@ -0,0 +1 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.integration.jdbc.aot.JdbcRuntimeHints

View File

@@ -275,12 +275,11 @@ class WebFluxRequestExecutingMessageHandlerTests {
StepVerifier.create(
response.getBody()
.map(dataBuffer -> new String(dataBuffer.asByteBuffer().array())))
.map(dataBuffer -> new String(dataBuffer.toByteBuffer().array())))
.expectNext("foo", "bar", "baz")
.verifyComplete();
}
@Test
void testClientHttpResponseErrorAsReply() {
ClientHttpConnector httpConnector = new HttpHandlerConnector((request, response) -> {

View File

@@ -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 class XsltPayloadTransformer extends AbstractXmlTransformer implements Be
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 @@ public class XsltPayloadTransformer extends AbstractXmlTransformer implements Be
else {
payload = message.getPayload();
}
Object transformedPayload = null;
Object transformedPayload;
if (this.alwaysUseResultFactory) {
transformedPayload = transformUsingResultFactory(payload, transformer);
}