Skip to content

Commit

Permalink
GH-3828: Initial Spring AOT support (#3832)
Browse files Browse the repository at this point in the history
* GH-3828: Initial Spring AOT support

Fixes #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
  • Loading branch information
artembilan committed Sep 1, 2022
1 parent 80eea15 commit 5080fc2
Show file tree
Hide file tree
Showing 25 changed files with 534 additions and 176 deletions.
Original file line number Diff line number Diff line change
@@ -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));
}

}
Original file line number Diff line number Diff line change
@@ -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);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Provides classes to support Spring AOT.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.integration.aot;
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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'");
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -44,17 +44,9 @@
*/
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
Expand All @@ -75,11 +67,27 @@ public void afterPropertiesSet() {
}

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) {

}

}

0 comments on commit 5080fc2

Please sign in to comment.