Skip to content

Expose more reflection hints #3951

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.ReactiveMessageHandler;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.util.ReflectionUtils;
Expand All @@ -83,6 +84,7 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
GenericSelector.class,
GenericTransformer.class,
GenericHandler.class,
ReactiveMessageHandler.class,
Function.class,
Supplier.class,
BeanExpressionContext.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,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();

/**
Expand All @@ -51,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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import org.springframework.web.server.WebHandler;

/**
* {@link RuntimeHintsRegistrar} for Spring Integration core module.
* {@link RuntimeHintsRegistrar} for Spring Integration HTTP module.
*
* @author Artem Bilan
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.kafka.aot;

import java.util.stream.Stream;

import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.integration.kafka.inbound.KafkaInboundGateway;
import org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter;
import org.springframework.integration.kafka.inbound.KafkaMessageSource;
import org.springframework.util.ReflectionUtils;

/**
* {@link RuntimeHintsRegistrar} for Spring Integration Kafka module.
*
* @author Artem Bilan
*
* @since 6.0
*/
class KafkaRuntimeHints implements RuntimeHintsRegistrar {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename to KafkaIntegrationRuntimeHints to avoid using the same class name?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was thinking about, but there is a package in a class name either way.
Plus this one is public as any library RuntimeHintsRegistrar impls registered in the aot.factories file.
See other Spring Integration impls: HttpRuntimeHints, JdbcRuntimeHints etc.

I really decided that integration in a name is going to be redundant if we have it already in a package name, plus this one is not public API to be used from end-user perspective.


@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
ReflectionHints reflectionHints = hints.reflection();

// Java DSL does not register beans during AOT phase, so @Reflective is not reachable from Pausable
Stream.of("pause", "resume", "isPaused")
.flatMap((name) ->
Stream.of(KafkaMessageDrivenChannelAdapter.class,
KafkaInboundGateway.class,
KafkaMessageSource.class)
.filter((type) -> reflectionHints.getTypeHint(type) == null)
.flatMap((type) -> Stream.ofNullable(ReflectionUtils.findMethod(type, name))))
.forEach(method -> reflectionHints.registerMethod(method, ExecutableMode.INVOKE));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.integration.kafka.aot.KafkaRuntimeHints
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Map;
import java.util.function.BiFunction;

import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.io.buffer.DataBuffer;
Expand Down Expand Up @@ -138,6 +139,7 @@ void registerHandleConnectionSetupMethod() {
}

@SuppressWarnings("unused")
@Reflective
private void handleConnectionSetup(Message<DataBuffer> connectMessage) {
DataBuffer dataBuffer = connectMessage.getPayload();
MessageHeaders messageHeaders = connectMessage.getHeaders();
Expand Down
2 changes: 2 additions & 0 deletions src/reference/asciidoc/index-single.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ include::./system-management.adoc[]

include::./reactive-streams.adoc[]

include::./native-aot.adoc[]

include::./endpoint-summary.adoc[]

include::./amqp.adoc[]
Expand Down
1 change: 1 addition & 0 deletions src/reference/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Welcome to the Spring Integration reference documentation!
<<./kotlin-dsl.adoc#kotlin-dsl,Kotlin DSL>> :: Details about Kotlin DSL for EIP
<<./system-management.adoc#system-management-chapter,System Management>> :: Message store, control bus, integration graph, metrics, JMX
<<./reactive-streams.adoc#reactive-streams,Reactive Streams Support>> :: Details about Reactive Streams support: message channels, channel adapters etc.
<<./native-aot.adoc#native-images-support,Native Images Support>> :: GraalVM native images and Spring AOT support

[horizontal]
**Integration Endpoints** ::
Expand Down
57 changes: 57 additions & 0 deletions src/reference/asciidoc/native-aot.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[[native-images-support]]
=== Native Images Support

Starting with version 6.0, GraalVM compilation of Spring Integration applications to native images is supported by https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aot[Spring AOT] native hints.
For most common use cases, such as endpoint definitions with `@Bean` methods, Java DSL configuration with lambdas and `@MessagingGateway` interface scanning (importing), the framework provides respective reflection, proxy and serialization hints.
If configuration uses messaging annotations (`@ServiceActivator`, `@Splitter` etc.) on POJO methods, or POJO methods are used with the `IntegrationFlowBuilder.handle(Object service, String methodName)` API, they have to be also marked with a `@Reflective` annotation since they are invoked by the framework reflectively.

IMPORTANT: XML configuration is not supported for native images.

As stated before, service interfaces with the `@MessagingGateway` annotation, when they are scanned by the `@IntegrationComponentScan` or used in an `@Import` annotation, are processed by the framework and the respective proxy hint is exposed into the AOT contribution.
When gateways are declared using the `IntegrationFlow.from(Class<?> serviceInterface)` API, the proxy configured for such interfaces have to be exposed manually:

====
[source,java]
----
@Configuration
@EnableIntegration
@ImportRuntimeHints(GatewayRuntimeHints.class)
public class IntegrationConfiguration {

@Bean
IntegrationFlow someFlow() {
return IntegrationFlow.from(SomeGateway)
// ...
.get();
}

public interface SomeGateway {

void doSomething(Object payload);

}

private static class GatewayRuntimeHints implements RuntimeHintsRegistrar {

@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.proxies().registerJdkProxy(
AopProxyUtils.completeJdkProxyInterfaces(SomeGateway));
}

}

}
----
====

NOTE: The `IntegrationFlow` content is not processed during the AOT processing phase.
Therefore, some hints, such as the one mentioned above for a gateway proxy, must be provided by the target application.

Of course, configuration is just a piece of an integration solution.
The most important part is data transferring over the network as well as persistent storage.
That's where serialization comes handy for many use-cases.
Spring Integration exposes serialization hints into a native image configuration for these types used by the framework internally: `String`, `Number`, `Long`, `Date`, `ArrayList`, `HashMap`, `Properties`, `Hashtable`, `Exception`, `UUID`, `GenericMessage`, `ErrorMessage`, `MessageHeaders`, `AdviceMessage`, `MutableMessage`, `MutableMessageHeaders`, `MessageGroupMetadata`, `MessageHolder`, `MessageMetadata`, `MessageHistory`, `MessageHistory.Entry`, `DelayHandler.DelayedMessageWrapper`.
For user specific data, mostly present as a message payload, the serialization hint must be exposed manually via a `RuntimeHintsRegistrar` implementation, as is shown above for a gateway proxy, and the respective `RuntimeHints.serialization().registerType()` API.

NOTE: It is recommended that native integration applications are developed with Spring Boot, using its respective build tools.
13 changes: 12 additions & 1 deletion src/reference/asciidoc/whats-new.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The Java DSL (see `org.springframework.integration.smb.dsl.Smb` factory) also ha
An `SmbStreamingMessageSource` and `SmbOutboundGateway` implementation are introduced.
See <<./smb.adoc#smb,SMB Support>> for more information.

[[x6.0-jdbc]]
[[x6.0-postgres]]
==== PostgreSQL Push Notification

A `PostgresSubscribableChannel` allows to receive push notifications via `PostgresChannelMessageTableSubscriber` upon new messages add to the `JdbcChannelMessageStore`.
Expand Down Expand Up @@ -98,6 +98,12 @@ Kotlin Coroutines support has been introduced to the framework.

See <<./kotlin-functions.adoc#kotlin-coroutines,Kotlin Coroutines>> for more information.

[[x6.0-aot]]
==== Native Images

Support for creating GraalVM native images is provided.
See <<./native-aot.adoc#native-images-support,Native Images Support>> for more information.

[[x6.0-general]]
=== General Changes

Expand Down Expand Up @@ -160,6 +166,7 @@ The `spring-integration-gemfire` module has been removed altogether since there
The `#cookies` variable for expression evaluation context, exposed in the `HttpRequestHandlingEndpointSupport`, is now a `MultiValueMap` to carry all the values for cookies set by the client.
See <<./http.adoc#http,HTTP Support>> for more information.

[[x6.0-kafka]]
=== Apache Kafka Changes

When providing a `RetryTemplate` on the inbound gateway or message-driven channel adapter, if an `errorChannel` is also provided, an `ErrorMessageSendingRecoverer` is automatically configured.
Expand All @@ -168,24 +175,28 @@ In addition, the new `KafkaErrorMessageSendingRecoverer` is provided; this can b

See <<./kafka.adoc#kafka,Spring for Apache Kafka Support>> for more information.

[[x6.0-jdbc]]
=== JDBC Changes

The `DefaultLockRepository` can now be supplied with a `PlatformTransactionManager` instead of relying on the primary bean from the application context.

See <<./jdbc.adoc#jdbc-lock-registry,JDBC Lock Registry>> for more information.

[[x6.0-tcp]]
=== TCP/IP Changes

The `lookupHost` property of the `AbstractConnectionFactory` and `DatagramPacketMessageMapper` is now set to `false` by default to avoid delays in the environments where DNS is not configured.

See <<./ip.adoc#ip,TCP and UDP Support>> for more information.

[[x6.0-jms]]
=== JMS Changes

The `JmsOutboundGateway` now creates a `TemporaryTopic` instead of `TemporaryQueue` if `replyPubSubDomain` option is set to `true`.

See <<./jms.adoc#jms,JMS Support>> for more information.

[[x6.0-security]]
=== Security Changes

The `ChannelSecurityInterceptor` and its annotation `@SecuredChannel` and XML `<secured-channels>` configurations have been deprecated in favor of `AuthorizationChannelInterceptor`.
Expand Down