Skip to content

Commit

Permalink
spring-projectsGH-3828: Initial Spring AOT support
Browse files Browse the repository at this point in the history
Fixes spring-projects#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
  • Loading branch information
artembilan committed Aug 31, 2022
1 parent 56aaa4a commit 9e3f042
Show file tree
Hide file tree
Showing 23 changed files with 498 additions and 147 deletions.
Original file line number Diff line number Diff line change
@@ -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));
}

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

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

}

}
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 @@ -45,15 +45,13 @@
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;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
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;
Expand Down Expand Up @@ -337,7 +335,6 @@ private void registerBuiltInBeans() {
int registryId = System.identityHashCode(this.registry);
jsonPath(registryId);
xpath(registryId);
jsonNodeToString(registryId);
REGISTRIES_PROCESSED.add(registryId);
}

Expand Down Expand Up @@ -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.
*/
Expand Down
Loading

0 comments on commit 9e3f042

Please sign in to comment.