GH-3506: No array for poller messaging anns attr

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

Revise the `@Poller` usage in the messaging annotations from an array value
to a single entry with a `ValueConstants.DEFAULT_NONE` for default value

Code review feedback

- Make annotation utils check for NONE more generic
- Move docs to whats-new.adoc
- Polish javadoc

Code review feedback
- simplify docs
- simplify isProvided checks
This commit is contained in:
Chris Bono
2022-03-09 20:54:05 -05:00
committed by Artem Bilan
parent ecb9ac3fc3
commit f3d3694836
15 changed files with 91 additions and 68 deletions

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.
@@ -34,6 +34,7 @@ import org.springframework.messaging.handler.annotation.ValueConstants;
* @author Marius Bogoevici
* @author Oleg Zhurakousky
* @author Artem Bilan
* @author Chris Bono
*/
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@@ -105,10 +106,9 @@ public @interface Aggregator {
/**
* @return the {@link Poller} options for a polled endpoint
* ({@link org.springframework.integration.scheduling.PollerMetadata}).
* This attribute is an {@code array} just to allow an empty default (no poller).
* Mutually exclusive with {@link #reactive()}.
*/
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
/**
* @return the {@link Reactive} marker for a consumer endpoint.

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.
@@ -38,6 +38,7 @@ import org.springframework.messaging.handler.annotation.ValueConstants;
* is used as the {@code outputChannel} of the {@link org.springframework.integration.handler.BridgeHandler}.
*
* @author Artem Bilan
* @author Chris Bono
*
* @since 4.0
*/
@@ -70,10 +71,9 @@ public @interface BridgeFrom {
/**
* @return the {@link Poller} options for a polled endpoint
* ({@link org.springframework.integration.scheduling.PollerMetadata}).
* This attribute is an {@code array} just to allow an empty default (no poller).
* Mutually exclusive with {@link #reactive()}.
*/
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
/**
* @return the {@link Reactive} marker for a consumer endpoint.

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.
@@ -43,6 +43,7 @@ import org.springframework.messaging.handler.annotation.ValueConstants;
* If no output channel is provided and no reply-channel exists, an exception is thrown.
*
* @author Artem Bilan
* @author Chris Bono
*
* @since 4.0
*/
@@ -77,10 +78,9 @@ public @interface BridgeTo {
/**
* @return the {@link Poller} options for a polled endpoint
* ({@link org.springframework.integration.scheduling.PollerMetadata}).
* This attribute is an {@code array} just to allow an empty default (no poller).
* Mutually exclusive with {@link #reactive()}.
*/
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
/**
* @return the {@link Reactive} marker for a consumer endpoint.

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.
@@ -40,6 +40,7 @@ import org.springframework.messaging.handler.annotation.ValueConstants;
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @author Chris Bono
*
* @since 2.0
*/
@@ -128,10 +129,9 @@ public @interface Filter {
/**
* @return the {@link Poller} options for a polled endpoint
* ({@link org.springframework.integration.scheduling.PollerMetadata}).
* This attribute is an {@code array} just to allow an empty default (no poller).
* Mutually exclusive with {@link #reactive()}.
*/
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
/**
* @return the {@link Reactive} marker for a consumer endpoint.

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.
@@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.messaging.handler.annotation.ValueConstants;
/**
* Indicates that a method is capable of producing a {@link org.springframework.messaging.Message}
@@ -45,6 +46,7 @@ import org.springframework.core.annotation.AliasFor;
*
* @author Artem Bilan
* @author Gary Russell
* @author Chris Bono
*
* @since 4.0
*/
@@ -86,10 +88,8 @@ public @interface InboundChannelAdapter {
/**
* @return the {@link org.springframework.integration.annotation.Poller} options for a polled endpoint
* ({@link org.springframework.integration.scheduling.PollerMetadata}).
* This attribute is an {@code array} just to allow an empty default (no poller).
* Only one {@link org.springframework.integration.annotation.Poller} element is allowed.
* NOTE: a {@link Poller} here has {@link Poller#maxMessagesPerPoll()} set to 1 by default.
*/
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
}

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.
@@ -45,6 +45,7 @@ import org.springframework.messaging.handler.annotation.ValueConstants;
*
* @author Mark Fisher
* @author Artem Bilan
* @author Chris Bono
*/
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@@ -151,10 +152,9 @@ public @interface Router {
/**
* @return the {@link Poller} options for a polled endpoint
* ({@link org.springframework.integration.scheduling.PollerMetadata}).
* This attribute is an {@code array} just to allow an empty default (no poller).
* Mutually exclusive with {@link #reactive()}.
*/
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
/**
* @return the {@link Reactive} marker for a consumer endpoint.

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.
@@ -43,6 +43,7 @@ import org.springframework.messaging.handler.annotation.ValueConstants;
* @author Gary Russell
* @author Artem Bilan
* @author Yilin Wei
* @author Chris Bono
*/
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@@ -121,11 +122,9 @@ public @interface ServiceActivator {
/**
* @return the {@link Poller} options for a polled endpoint
* ({@link org.springframework.integration.scheduling.PollerMetadata}).
* This attribute is an {@code array} just to allow an empty default (no poller).
* Only one {@link Poller} element is allowed.
* Mutually exclusive with {@link #reactive()}.
*/
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
/**
* @return the {@link Reactive} marker for a consumer endpoint.

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.
@@ -43,6 +43,7 @@ import org.springframework.messaging.handler.annotation.ValueConstants;
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @author Chris Bono
*/
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@@ -116,10 +117,9 @@ public @interface Splitter {
/**
* @return the {@link Poller} options for a polled endpoint
* ({@link org.springframework.integration.scheduling.PollerMetadata}).
* This attribute is an {@code array} just to allow an empty default (no poller).
* Mutually exclusive with {@link #reactive()}.
*/
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
/**
* @return the {@link Reactive} marker for a consumer endpoint.

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.
@@ -31,6 +31,7 @@ import org.springframework.messaging.handler.annotation.ValueConstants;
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @author Chris Bono
*/
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@@ -92,10 +93,9 @@ public @interface Transformer {
/**
* @return the {@link Poller} options for a polled endpoint
* ({@link org.springframework.integration.scheduling.PollerMetadata}).
* This attribute is an {@code array} just to allow an empty default (no poller).
* Mutually exclusive with {@link #reactive()}.
*/
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
/**
* @return the {@link Reactive} marker for a consumer endpoint.

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.
@@ -84,7 +84,6 @@ import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.core.DestinationResolutionException;
import org.springframework.messaging.core.DestinationResolver;
import org.springframework.messaging.handler.annotation.ValueConstants;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.PeriodicTrigger;
@@ -103,6 +102,7 @@ import reactor.core.publisher.Flux;
* @author Mark Fisher
* @author Artem Bilan
* @author Gary Russell
* @author Chris Bono
*/
public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation>
implements MethodAnnotationPostProcessor<T> {
@@ -363,24 +363,24 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
protected AbstractEndpoint doCreateEndpoint(MessageHandler handler, MessageChannel inputChannel,
List<Annotation> annotations) {
Poller[] pollers = MessagingAnnotationUtils.resolveAttribute(annotations, "poller", Poller[].class);
Reactive reactive = MessagingAnnotationUtils.resolveAttribute(annotations, "reactive", Reactive.class);
boolean reactiveProvided = reactive != null && !ValueConstants.DEFAULT_NONE.equals(reactive.value());
Poller poller = MessagingAnnotationUtils.resolveAttribute(annotations, "poller", Poller.class);
Assert.state(!reactiveProvided || ObjectUtils.isEmpty(pollers),
Reactive reactive = MessagingAnnotationUtils.resolveAttribute(annotations, "reactive", Reactive.class);
Assert.state(reactive == null || poller == null,
"The 'poller' and 'reactive' are mutually exclusive.");
if (inputChannel instanceof Publisher || handler instanceof ReactiveMessageHandlerAdapter || reactiveProvided) {
return reactiveStreamsConsumer(inputChannel, handler, reactiveProvided ? reactive : null);
if (inputChannel instanceof Publisher || handler instanceof ReactiveMessageHandlerAdapter || reactive != null) {
return reactiveStreamsConsumer(inputChannel, handler, reactive);
}
else if (inputChannel instanceof SubscribableChannel) {
Assert.state(ObjectUtils.isEmpty(pollers), () ->
Assert.state(poller == null, () ->
"A '@Poller' should not be specified for Annotation-based " +
"endpoint, since '" + inputChannel + "' is a SubscribableChannel (not pollable).");
return new EventDrivenConsumer((SubscribableChannel) inputChannel, handler);
}
else if (inputChannel instanceof PollableChannel) {
return pollingConsumer(inputChannel, handler, pollers);
return pollingConsumer(inputChannel, handler, poller);
}
else {
throw new IllegalArgumentException("Unsupported 'inputChannel' type: '"
@@ -414,19 +414,15 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
return reactiveStreamsConsumer;
}
private PollingConsumer pollingConsumer(MessageChannel inputChannel, MessageHandler handler, Poller[] pollers) {
private PollingConsumer pollingConsumer(MessageChannel inputChannel, MessageHandler handler, Poller poller) {
PollingConsumer pollingConsumer = new PollingConsumer((PollableChannel) inputChannel, handler);
configurePollingEndpoint(pollingConsumer, pollers);
configurePollingEndpoint(pollingConsumer, poller);
return pollingConsumer;
}
protected void configurePollingEndpoint(AbstractPollingEndpoint pollingEndpoint, Poller[] pollers) {
protected void configurePollingEndpoint(AbstractPollingEndpoint pollingEndpoint, Poller poller) {
PollerMetadata pollerMetadata;
if (!ObjectUtils.isEmpty(pollers)) {
Assert.state(pollers.length == 1,
"The 'poller' for an Annotation-based endpoint can have only one '@Poller'.");
Poller poller = pollers[0];
if (poller != null) {
String ref = poller.value();
String triggerRef = poller.trigger();
String executorRef = poller.taskExecutor();

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.
@@ -44,6 +44,7 @@ import org.springframework.util.Assert;
* @author Artem Bilan
* @author Gary Russell
* @author Oleg Zhurakousky
* @author Chris Bono
*
* @since 4.0
*/
@@ -82,8 +83,8 @@ public class InboundChannelAdapterAnnotationPostProcessor extends
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
adapter.setOutputChannelName(channelName);
adapter.setSource(messageSource);
Poller[] pollers = MessagingAnnotationUtils.resolveAttribute(annotations, "poller", Poller[].class);
configurePollingEndpoint(adapter, pollers);
Poller poller = MessagingAnnotationUtils.resolveAttribute(annotations, "poller", Poller.class);
configurePollingEndpoint(adapter, poller);
return adapter;
}

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.
@@ -30,8 +30,9 @@ import org.springframework.messaging.MessagingException;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.ValueConstants;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Utility methods to support annotation processing.
@@ -41,17 +42,16 @@ import org.springframework.util.StringUtils;
* @author Gunnar Hillert
* @author Soby Chacko
* @author Artem Bilan
* @author Chris Bono
*
* @since 4.0
*/
public final class MessagingAnnotationUtils {
/**
* Get the attribute value from the annotation hierarchy, returning the first non-empty
* value closest to the annotated method. While traversing up the hierarchy, for string-valued
* attributes, an empty string is ignored. For array-valued attributes, an empty
* array is ignored.
* The overridden attribute must be the same type.
* Get the attribute value from the annotation hierarchy, returning the first
* {@link MessagingAnnotationUtils#hasValue non-empty}) value closest to the annotated method.
*
* @param annotations The meta-annotations in order (closest first).
* @param name The attribute name.
* @param requiredType The expected type.
@@ -69,9 +69,30 @@ public final class MessagingAnnotationUtils {
return null;
}
public static boolean hasValue(Object value) {
return value != null && (!(value instanceof String) || (StringUtils.hasText((String) value))) // NOSONAR
&& (!value.getClass().isArray() || ((Object[]) value).length > 0);
/**
* Determine if the value of a named attribute from an annotation instance contains an actual value.
* @param annotationValue the value of the annotation attribute
* @return {@code false} when {@code annotationValue} is null, an empty string, an empty array, or any annotation
* whose 'value' field is set to {@link ValueConstants#DEFAULT_NONE} - {@code true} otherwise
*/
public static boolean hasValue(Object annotationValue) {
if (annotationValue == null) {
return false;
}
// Empty array
if (ObjectUtils.isArray(annotationValue) && ObjectUtils.isEmpty(annotationValue)) {
return false;
}
// Empty string
if ((annotationValue instanceof String) && ObjectUtils.isEmpty(annotationValue)) {
return false;
}
// Annotation with 'value' set to special 'none' string
if ((annotationValue instanceof Annotation) &&
ValueConstants.DEFAULT_NONE.equals(AnnotationUtils.getValue((Annotation) annotationValue))) {
return false;
}
return true;
}
public static Method findAnnotatedMethod(Object target, final Class<? extends Annotation> annotationType) {

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.
@@ -127,6 +127,7 @@ import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.ValueConstants;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.scheduling.Trigger;
@@ -148,6 +149,7 @@ import reactor.core.publisher.Mono;
* @author Artem Bilan
* @author Gary Russell
* @author Michael Wiles
* @author Chris Bono
*
* @since 4.0
*/
@@ -1506,7 +1508,7 @@ public class EnableIntegrationTests {
String phase() default "";
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
}
@@ -1525,7 +1527,7 @@ public class EnableIntegrationTests {
String phase() default "";
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
}
@@ -1631,7 +1633,7 @@ public class EnableIntegrationTests {
String phase() default "";
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
}
@@ -1664,7 +1666,7 @@ public class EnableIntegrationTests {
String phase() default "";
Poller[] poller() default { };
Poller poller() default @Poller(ValueConstants.DEFAULT_NONE);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 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.
@@ -56,6 +56,7 @@ import java.util.stream.Collectors
/**
* @author Artem Bilan
* @author Chris Bono
*
* @since 5.1
*/
@@ -171,7 +172,7 @@ class FunctionsTests {
@Bean
@InboundChannelAdapter(value = "counterChannel", autoStartup = "false",
poller = [Poller(fixedRate = "10", maxMessagesPerPoll = "1")])
poller = Poller(fixedRate = "10", maxMessagesPerPoll = "1"))
@EndpointId("kotlinSupplierChannelAdapter")
fun kotlinSupplier(): () -> String {
return { "baz" }

View File

@@ -26,6 +26,9 @@ See <<./graphql.adoc#graphql,GraphQL Support>> for more information.
[[x6.0-general]]
=== General Changes
The messaging annotations don't require a poller attribute as an array of @Poller any more.
See <<./configuration.adoc#configuration-using-poller-annotation,Using the @Poller Annotation>> for more information.
[[x6.0-http]]
=== HTTP Changes
@@ -36,4 +39,4 @@ See <<./http.adoc#http,HTTP Support>> for more information.
=== RMI Removal
The `spring-integration-rmi` module has been removed altogether after being deprecated in previous versions.
There is no replacement: it is recommended to migrate to more secure network and application protocols, such as WebSockets, RSockets, gRPC or REST.
There is no replacement: it is recommended to migrate to more secure network and application protocols, such as WebSockets, RSockets, gRPC or REST.