Skip to content

Commit 5080fc2

Browse files
authored
GH-3828: Initial Spring AOT support (#3832)
* 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
1 parent 80eea15 commit 5080fc2

File tree

25 files changed

+534
-176
lines changed

25 files changed

+534
-176
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.aot;
18+
19+
import java.util.ArrayList;
20+
import java.util.Date;
21+
import java.util.HashMap;
22+
import java.util.Hashtable;
23+
import java.util.Properties;
24+
import java.util.UUID;
25+
import java.util.function.Function;
26+
import java.util.function.Supplier;
27+
import java.util.stream.Stream;
28+
29+
import org.springframework.aop.SpringProxy;
30+
import org.springframework.aop.framework.Advised;
31+
import org.springframework.aot.hint.MemberCategory;
32+
import org.springframework.aot.hint.ProxyHints;
33+
import org.springframework.aot.hint.ReflectionHints;
34+
import org.springframework.aot.hint.RuntimeHints;
35+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
36+
import org.springframework.aot.hint.SerializationHints;
37+
import org.springframework.aot.hint.TypeReference;
38+
import org.springframework.beans.factory.config.BeanExpressionContext;
39+
import org.springframework.context.SmartLifecycle;
40+
import org.springframework.core.DecoratingProxy;
41+
import org.springframework.integration.context.IntegrationContextUtils;
42+
import org.springframework.integration.core.GenericSelector;
43+
import org.springframework.integration.core.Pausable;
44+
import org.springframework.integration.dsl.IntegrationFlow;
45+
import org.springframework.integration.gateway.MethodArgsHolder;
46+
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
47+
import org.springframework.integration.handler.DelayHandler;
48+
import org.springframework.integration.handler.GenericHandler;
49+
import org.springframework.integration.history.MessageHistory;
50+
import org.springframework.integration.json.JsonPathUtils;
51+
import org.springframework.integration.message.AdviceMessage;
52+
import org.springframework.integration.routingslip.ExpressionEvaluatingRoutingSlipRouteStrategy;
53+
import org.springframework.integration.store.MessageGroupMetadata;
54+
import org.springframework.integration.store.MessageHolder;
55+
import org.springframework.integration.store.MessageMetadata;
56+
import org.springframework.integration.support.MutableMessage;
57+
import org.springframework.integration.support.MutableMessageHeaders;
58+
import org.springframework.integration.support.management.ManageableSmartLifecycle;
59+
import org.springframework.integration.transformer.GenericTransformer;
60+
import org.springframework.messaging.MessageHeaders;
61+
import org.springframework.messaging.support.ErrorMessage;
62+
import org.springframework.messaging.support.GenericMessage;
63+
64+
/**
65+
* {@link RuntimeHintsRegistrar} for Spring Integration core module.
66+
*
67+
* @author Artem Bilan
68+
*
69+
* @since 6.0
70+
*/
71+
class CoreRuntimeHints implements RuntimeHintsRegistrar {
72+
73+
@Override
74+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
75+
ReflectionHints reflectionHints = hints.reflection();
76+
Stream.of(
77+
GenericSelector.class,
78+
GenericTransformer.class,
79+
GenericHandler.class,
80+
Function.class,
81+
Supplier.class,
82+
BeanExpressionContext.class,
83+
IntegrationContextUtils.class,
84+
MethodArgsHolder.class,
85+
AbstractReplyProducingMessageHandler.RequestHandler.class,
86+
ExpressionEvaluatingRoutingSlipRouteStrategy.RequestAndReply.class,
87+
Pausable.class,
88+
ManageableSmartLifecycle.class)
89+
.forEach(type ->
90+
reflectionHints.registerType(type,
91+
builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)));
92+
93+
reflectionHints.registerType(JsonPathUtils.class,
94+
builder ->
95+
builder.onReachableType(TypeReference.of("com.jayway.jsonpath.JsonPath"))
96+
.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
97+
98+
// For #xpath() SpEL function
99+
reflectionHints.registerTypeIfPresent(classLoader, "org.springframework.integration.xml.xpath.XPathUtils",
100+
builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
101+
102+
Stream.of(
103+
"kotlin.jvm.functions.Function0",
104+
"kotlin.jvm.functions.Function1",
105+
"kotlin.Unit")
106+
.forEach(type ->
107+
reflectionHints.registerTypeIfPresent(classLoader, type,
108+
builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)));
109+
110+
hints.resources().registerPattern("META-INF/spring.integration.properties");
111+
112+
SerializationHints serializationHints = hints.serialization();
113+
Stream.of(
114+
String.class,
115+
Number.class,
116+
Long.class,
117+
Date.class,
118+
ArrayList.class,
119+
HashMap.class,
120+
Properties.class,
121+
Hashtable.class,
122+
Exception.class,
123+
UUID.class,
124+
GenericMessage.class,
125+
ErrorMessage.class,
126+
MessageHeaders.class,
127+
AdviceMessage.class,
128+
MutableMessage.class,
129+
MutableMessageHeaders.class,
130+
MessageGroupMetadata.class,
131+
MessageHolder.class,
132+
MessageMetadata.class,
133+
MessageHistory.class,
134+
MessageHistory.Entry.class,
135+
DelayHandler.DelayedMessageWrapper.class)
136+
.forEach(serializationHints::registerType);
137+
138+
ProxyHints proxyHints = hints.proxies();
139+
140+
registerSpringJdkProxy(proxyHints, AbstractReplyProducingMessageHandler.RequestHandler.class);
141+
registerSpringJdkProxy(proxyHints, IntegrationFlow.class, SmartLifecycle.class);
142+
}
143+
144+
private static void registerSpringJdkProxy(ProxyHints proxyHints, Class<?>... proxiedInterfaces) {
145+
proxyHints
146+
.registerJdkProxy(builder ->
147+
builder.proxiedInterfaces(proxiedInterfaces)
148+
.proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class));
149+
}
150+
151+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.aot;
18+
19+
import java.util.function.Predicate;
20+
21+
import org.springframework.aop.SpringProxy;
22+
import org.springframework.aop.framework.Advised;
23+
import org.springframework.aot.generate.GenerationContext;
24+
import org.springframework.beans.factory.FactoryBean;
25+
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
26+
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
27+
import org.springframework.beans.factory.aot.BeanRegistrationCode;
28+
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments;
29+
import org.springframework.beans.factory.support.RegisteredBean;
30+
import org.springframework.beans.factory.support.RootBeanDefinition;
31+
import org.springframework.core.DecoratingProxy;
32+
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
33+
import org.springframework.javapoet.CodeBlock;
34+
35+
/**
36+
* {@link BeanRegistrationAotProcessor} for registering proxy interfaces of the {@link GatewayProxyFactoryBean} beans.
37+
*
38+
* @author Artem Bilan
39+
*
40+
* @since 6.0
41+
*/
42+
class GatewayProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
43+
44+
@Override
45+
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
46+
if (GatewayProxyFactoryBean.class.isAssignableFrom(registeredBean.getBeanClass())) {
47+
return BeanRegistrationAotContribution
48+
.ofBeanRegistrationCodeFragmentsCustomizer(GatewayProxyBeanRegistrationCodeFragments::new);
49+
}
50+
return null;
51+
}
52+
53+
private static class GatewayProxyBeanRegistrationCodeFragments extends BeanRegistrationCodeFragments {
54+
55+
GatewayProxyBeanRegistrationCodeFragments(BeanRegistrationCodeFragments codeFragments) {
56+
super(codeFragments);
57+
}
58+
59+
@Override
60+
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
61+
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
62+
Predicate<String> attributeFilter) {
63+
64+
Class<?> serviceInterface = (Class<?>) beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
65+
generationContext.getRuntimeHints().proxies()
66+
.registerJdkProxy(serviceInterface, SpringProxy.class, Advised.class, DecoratingProxy.class);
67+
68+
return super.generateSetBeanDefinitionPropertiesCode(generationContext, beanRegistrationCode,
69+
beanDefinition, FactoryBean.OBJECT_TYPE_ATTRIBUTE::equals);
70+
}
71+
72+
}
73+
74+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Provides classes to support Spring AOT.
3+
*/
4+
@org.springframework.lang.NonNullApi
5+
@org.springframework.lang.NonNullFields
6+
package org.springframework.integration.aot;

spring-integration-core/src/main/java/org/springframework/integration/config/ConsumerEndpointFactoryBean.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
import org.springframework.aop.framework.ProxyFactory;
2828
import org.springframework.aop.support.AopUtils;
2929
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
30+
import org.springframework.aot.hint.annotation.Reflective;
3031
import org.springframework.beans.factory.BeanClassLoaderAware;
3132
import org.springframework.beans.factory.BeanFactory;
3233
import org.springframework.beans.factory.BeanFactoryAware;
@@ -123,6 +124,7 @@ public class ConsumerEndpointFactoryBean
123124

124125
private volatile boolean initialized;
125126

127+
@Reflective // The native image doesn't see this method because its type is not specific
126128
public void setHandler(Object handler) {
127129
Assert.isTrue(handler instanceof MessageHandler || handler instanceof ReactiveMessageHandler,
128130
"'handler' must be an instance of 'MessageHandler' or 'ReactiveMessageHandler'");

spring-integration-core/src/main/java/org/springframework/integration/config/ConverterRegistrar.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,8 @@
1616

1717
package org.springframework.integration.config;
1818

19-
import java.util.HashSet;
2019
import java.util.Set;
20+
import java.util.stream.Collectors;
2121

2222
import org.springframework.beans.BeansException;
2323
import org.springframework.beans.factory.InitializingBean;
@@ -44,17 +44,9 @@
4444
*/
4545
class ConverterRegistrar implements InitializingBean, ApplicationContextAware {
4646

47-
private final Set<Object> converters;
48-
4947
private ApplicationContext applicationContext;
5048

51-
5249
ConverterRegistrar() {
53-
this(new HashSet<>());
54-
}
55-
56-
ConverterRegistrar(Set<Object> converters) {
57-
this.converters = converters;
5850
}
5951

6052
@Override
@@ -75,11 +67,27 @@ public void afterPropertiesSet() {
7567
}
7668

7769
private void registerConverters(GenericConversionService conversionService) {
78-
this.converters.addAll(this.applicationContext.getBeansWithAnnotation(IntegrationConverter.class).values());
70+
Set<Object> converters =
71+
this.applicationContext.getBeansOfType(IntegrationConverterRegistration.class)
72+
.values()
73+
.stream().map(IntegrationConverterRegistration::converter)
74+
.collect(Collectors.toSet());
7975
if (JacksonPresent.isJackson2Present()) {
80-
this.converters.add(new JsonNodeWrapperToJsonNodeConverter());
76+
converters.add(new JsonNodeWrapperToJsonNodeConverter());
8177
}
82-
ConversionServiceFactory.registerConverters(this.converters, conversionService);
78+
ConversionServiceFactory.registerConverters(converters, conversionService);
79+
}
80+
81+
/**
82+
* A configuration supporting bean for converter with a {@link IntegrationConverter}
83+
* annotation.
84+
*
85+
* @param converter the target converter bean with a {@link IntegrationConverter}.
86+
*
87+
* @since 6.0
88+
*/
89+
record IntegrationConverterRegistration(Object converter) {
90+
8391
}
8492

8593
}

0 commit comments

Comments
 (0)