Introduce a ReceiveMessageAdvice (#3265)
* Introduce a `ReceiveMessageAdvice`
* Deprecate an `AbstractMessageSourceAdvice` in favor of
`default` method in the `MessageSourceMutator`
* Move a `applyReceiveOnlyAdviceChain()` logic into the `AbstractPollingEndpoint`:
now both `PollingConsumer` and `SourcePollingChannelAdapter` can use
`ReceiveMessageAdvice`
* Introduce a `SimpleActiveIdleReceiveMessageAdvice` based already
on the `ReceiveMessageAdvice` and deprecate a `SimpleActiveIdleMessageSourceAdvice`
which is fully replaceable with newly introduced `SimpleActiveIdleReceiveMessageAdvice`
* Add `@SuppressWarnings("deprecation")` for those out-of-the-box `ReceiveMessageAdvice`
implementation which still use an `AbstractMessageSourceAdvice` for
backward compatibility
* Document a new feature and give the `MessageSourceMutator` a new meaning
* * Fix language in the `polling-consumer.adoc`
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2019 the original author or authors.
|
||||
* Copyright 2015-2020 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.
|
||||
@@ -16,34 +16,20 @@
|
||||
|
||||
package org.springframework.integration.aop;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* Advice for a {@link MessageSource#receive()} method to decide whether a poll
|
||||
* should be ignored and/or take action after the receive.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 4.2
|
||||
*
|
||||
* @deprecated since 5.3 in favor of {@link MessageSourceMutator}.
|
||||
*/
|
||||
public abstract class AbstractMessageSourceAdvice implements MethodInterceptor, MessageSourceMutator {
|
||||
|
||||
@Override
|
||||
public final Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
Object target = invocation.getThis();
|
||||
if (!(target instanceof MessageSource)) {
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
Message<?> result = null;
|
||||
if (beforeReceive((MessageSource<?>) target)) {
|
||||
result = (Message<?>) invocation.proceed();
|
||||
}
|
||||
return afterReceive(result, (MessageSource<?>) target);
|
||||
}
|
||||
@Deprecated
|
||||
public abstract class AbstractMessageSourceAdvice implements MessageSourceMutator {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2019 the original author or authors.
|
||||
* Copyright 2015-2020 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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.integration.aop;
|
||||
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.util.CompoundTrigger;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -35,7 +36,10 @@ import org.springframework.util.Assert;
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
public class CompoundTriggerAdvice extends AbstractMessageSourceAdvice {
|
||||
@SuppressWarnings("deprecation")
|
||||
public class CompoundTriggerAdvice
|
||||
extends AbstractMessageSourceAdvice
|
||||
implements ReceiveMessageAdvice {
|
||||
|
||||
private final CompoundTrigger compoundTrigger;
|
||||
|
||||
@@ -47,8 +51,21 @@ public class CompoundTriggerAdvice extends AbstractMessageSourceAdvice {
|
||||
this.override = overrideTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param result the received message.
|
||||
* @param source the message source.
|
||||
* @return the message or null
|
||||
* @deprecated since 5.3 in favor of {@link #afterReceive(Message, Object)}
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public Message<?> afterReceive(Message<?> result, MessageSource<?> source) {
|
||||
return afterReceive(result, (Object) source);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Message<?> afterReceive(@Nullable Message<?> result, Object source) {
|
||||
if (result == null) {
|
||||
this.compoundTrigger.setOverride(this.override);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2019 the original author or authors.
|
||||
* Copyright 2018-2020 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.
|
||||
@@ -17,19 +17,31 @@
|
||||
package org.springframework.integration.aop;
|
||||
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* An object that can mutate a {@link MessageSource} before and/or after
|
||||
* A {@link ReceiveMessageAdvice} extension that can mutate a {@link MessageSource} before and/or after
|
||||
* {@link MessageSource#receive()} is called.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0.7.
|
||||
*
|
||||
* @since 5.0.7
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MessageSourceMutator {
|
||||
public interface MessageSourceMutator extends ReceiveMessageAdvice {
|
||||
|
||||
@Override
|
||||
default boolean beforeReceive(Object source) {
|
||||
if (source instanceof MessageSource<?>) {
|
||||
return beforeReceive((MessageSource<?>) source);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"The 'MessageSourceMutator' supports only a 'MessageSource' in the before/after hooks: " + source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can decide whether to proceed with this poll.
|
||||
@@ -40,6 +52,18 @@ public interface MessageSourceMutator {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
default Message<?> afterReceive(@Nullable Message<?> result, Object source) {
|
||||
if (source instanceof MessageSource<?>) {
|
||||
return afterReceive(result, (MessageSource<?>) source);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"The 'MessageSourceMutator' supports only a 'MessageSource' in the before/after hooks: " + source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can take actions based on the result of the poll; e.g.
|
||||
* adjust the {@code trigger}. The message can also be replaced with a new one.
|
||||
@@ -47,6 +71,7 @@ public interface MessageSourceMutator {
|
||||
* @param source the message source.
|
||||
* @return a message to continue to process the result, null to discard whatever the poll returned.
|
||||
*/
|
||||
Message<?> afterReceive(Message<?> result, MessageSource<?> source);
|
||||
@Nullable
|
||||
Message<?> afterReceive(@Nullable Message<?> result, MessageSource<?> source);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2020 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.aop;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
|
||||
/**
|
||||
* An AOP advice to perform hooks before and/or after a {@code receive()} contract is called.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReceiveMessageAdvice extends MethodInterceptor {
|
||||
|
||||
/**
|
||||
* Subclasses can decide whether to {@link MethodInvocation#proceed()} or not.
|
||||
* @param source the source of the message to receive.
|
||||
* @return true to proceed (default).
|
||||
*/
|
||||
default boolean beforeReceive(Object source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
default Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
Object target = invocation.getThis();
|
||||
if (!(target instanceof MessageSource) && !(target instanceof PollableChannel)) {
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
Message<?> result = null;
|
||||
if (beforeReceive(target)) {
|
||||
result = (Message<?>) invocation.proceed();
|
||||
}
|
||||
return afterReceive(result, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can take actions based on the result of the {@link MethodInvocation#proceed()}; e.g.
|
||||
* adjust the {@code trigger}. The message can also be replaced with a new one.
|
||||
* @param result the received message.
|
||||
* @param source the source of the message to receive.
|
||||
* @return a message to continue to process the result, null to discard whatever
|
||||
* the {@link MethodInvocation#proceed()} returned.
|
||||
*/
|
||||
@Nullable
|
||||
Message<?> afterReceive(@Nullable Message<?> result, Object source);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2019 the original author or authors.
|
||||
* Copyright 2015-2020 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,7 +31,11 @@ import org.springframework.messaging.Message;
|
||||
* @since 4.2
|
||||
*
|
||||
* @see DynamicPeriodicTrigger
|
||||
*
|
||||
* @deprecated since 5.3 in favor of {@link SimpleActiveIdleReceiveMessageAdvice} with the same
|
||||
* (but more common) functionality.
|
||||
*/
|
||||
@Deprecated
|
||||
public class SimpleActiveIdleMessageSourceAdvice extends AbstractMessageSourceAdvice {
|
||||
|
||||
private final DynamicPeriodicTrigger trigger;
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2020 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.aop;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.integration.util.DynamicPeriodicTrigger;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
A simple advice that polls at one rate when messages exist and another when
|
||||
* there are no messages.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.3
|
||||
*
|
||||
* @see DynamicPeriodicTrigger
|
||||
*/
|
||||
public class SimpleActiveIdleReceiveMessageAdvice implements ReceiveMessageAdvice {
|
||||
|
||||
private final DynamicPeriodicTrigger trigger;
|
||||
|
||||
private volatile Duration idlePollPeriod;
|
||||
|
||||
private volatile Duration activePollPeriod;
|
||||
|
||||
public SimpleActiveIdleReceiveMessageAdvice(DynamicPeriodicTrigger trigger) {
|
||||
Assert.notNull(trigger, "'trigger' must not be null");
|
||||
this.trigger = trigger;
|
||||
this.idlePollPeriod = trigger.getDuration();
|
||||
this.activePollPeriod = trigger.getDuration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the poll period when messages are not returned. Defaults to the
|
||||
* trigger's period.
|
||||
* @param idlePollPeriod the period in milliseconds.
|
||||
*/
|
||||
public void setIdlePollPeriod(long idlePollPeriod) {
|
||||
this.idlePollPeriod = Duration.ofMillis(idlePollPeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the poll period when messages are returned. Defaults to the
|
||||
* trigger's period.
|
||||
* @param activePollPeriod the period in milliseconds.
|
||||
*/
|
||||
public void setActivePollPeriod(long activePollPeriod) {
|
||||
this.activePollPeriod = Duration.ofMillis(activePollPeriod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> afterReceive(Message<?> result, Object source) {
|
||||
if (result == null) {
|
||||
this.trigger.setDuration(this.idlePollPeriod);
|
||||
}
|
||||
else {
|
||||
this.trigger.setDuration(this.activePollPeriod);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.springframework.integration.endpoint;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -28,10 +29,14 @@ import java.util.stream.Collectors;
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.reactivestreams.Subscription;
|
||||
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.integration.aop.ReceiveMessageAdvice;
|
||||
import org.springframework.integration.channel.ChannelUtils;
|
||||
import org.springframework.integration.channel.MessagePublishingErrorHandler;
|
||||
import org.springframework.integration.support.MessagingExceptionWrapper;
|
||||
@@ -72,6 +77,8 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
|
||||
*/
|
||||
public static final long DEFAULT_POLLING_PERIOD = 10;
|
||||
|
||||
private final Collection<Advice> appliedAdvices = new HashSet<>();
|
||||
|
||||
private final Object initializationMonitor = new Object();
|
||||
|
||||
private Executor taskExecutor = new SyncTaskExecutor();
|
||||
@@ -173,7 +180,7 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
|
||||
* @return true to only advise the receive operation.
|
||||
*/
|
||||
protected boolean isReceiveOnlyAdvice(Advice advice) {
|
||||
return false;
|
||||
return advice instanceof ReceiveMessageAdvice;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,6 +188,37 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
|
||||
* @param chain the advice chain {@code Collection}.
|
||||
*/
|
||||
protected void applyReceiveOnlyAdviceChain(Collection<Advice> chain) {
|
||||
if (!CollectionUtils.isEmpty(chain)) {
|
||||
Object source = getReceiveMessageSource();
|
||||
if (source != null) {
|
||||
if (AopUtils.isAopProxy(source)) {
|
||||
Advised advised = (Advised) source;
|
||||
this.appliedAdvices.forEach(advised::removeAdvice);
|
||||
chain.forEach(advice -> advised.addAdvisor(adviceToReceiveAdvisor(advice)));
|
||||
}
|
||||
else {
|
||||
ProxyFactory proxyFactory = new ProxyFactory(source);
|
||||
chain.forEach(advice -> proxyFactory.addAdvisor(adviceToReceiveAdvisor(advice)));
|
||||
source = proxyFactory.getProxy(getBeanClassLoader());
|
||||
}
|
||||
this.appliedAdvices.clear();
|
||||
this.appliedAdvices.addAll(chain);
|
||||
if (!(isSyncExecutor()) && logger.isWarnEnabled()) {
|
||||
logger.warn(getComponentName() + ": A task executor is supplied and " + chain.size()
|
||||
+ "ReceiveMessageAdvice(s) is/are provided. If an advice mutates the source, such "
|
||||
+ "mutations are not thread safe and could cause unexpected results, especially with "
|
||||
+ "high frequency pollers. Consider using a downstream ExecutorChannel instead of "
|
||||
+ "adding an executor to the poller");
|
||||
}
|
||||
setReceiveMessageSource(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NameMatchMethodPointcutAdvisor adviceToReceiveAdvisor(Advice advice) {
|
||||
NameMatchMethodPointcutAdvisor sourceAdvisor = new NameMatchMethodPointcutAdvisor(advice);
|
||||
sourceAdvisor.addMethodName("receive");
|
||||
return sourceAdvisor;
|
||||
}
|
||||
|
||||
protected boolean isReactive() {
|
||||
@@ -191,6 +229,14 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
|
||||
return this.pollingFlux;
|
||||
}
|
||||
|
||||
protected Object getReceiveMessageSource() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void setReceiveMessageSource(Object source) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
synchronized (this.initializationMonitor) {
|
||||
|
||||
@@ -57,13 +57,13 @@ public class PollingConsumer extends AbstractPollingEndpoint implements Integrat
|
||||
*/
|
||||
public static final long DEFAULT_RECEIVE_TIMEOUT = 1000;
|
||||
|
||||
private final PollableChannel inputChannel;
|
||||
|
||||
private final MessageHandler handler;
|
||||
|
||||
private final List<ChannelInterceptor> channelInterceptors;
|
||||
|
||||
private volatile long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT;
|
||||
private PollableChannel inputChannel;
|
||||
|
||||
private long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT;
|
||||
|
||||
public PollingConsumer(PollableChannel inputChannel, MessageHandler handler) {
|
||||
Assert.notNull(inputChannel, "inputChannel must not be null");
|
||||
@@ -110,6 +110,16 @@ public class PollingConsumer extends AbstractPollingEndpoint implements Integrat
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getReceiveMessageSource() {
|
||||
return this.inputChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setReceiveMessageSource(Object source) {
|
||||
this.inputChannel = (PollableChannel) source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReactive() {
|
||||
return getOutputChannel() instanceof ReactiveStreamsSubscribableChannel &&
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -16,21 +16,12 @@
|
||||
|
||||
package org.springframework.integration.endpoint;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.integration.StaticMessageHeaderAccessor;
|
||||
import org.springframework.integration.acks.AckUtils;
|
||||
import org.springframework.integration.acks.AcknowledgmentCallback;
|
||||
import org.springframework.integration.aop.MessageSourceMutator;
|
||||
import org.springframework.integration.channel.ReactiveStreamsSubscribableChannel;
|
||||
import org.springframework.integration.context.ExpressionCapable;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
@@ -43,7 +34,6 @@ import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* A Channel Adapter implementation for connecting a
|
||||
@@ -59,9 +49,7 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint
|
||||
|
||||
private final MessagingTemplate messagingTemplate = new MessagingTemplate();
|
||||
|
||||
private final Collection<Advice> appliedAdvices = new HashSet<>();
|
||||
|
||||
private volatile MessageSource<?> originalSource;
|
||||
private MessageSource<?> originalSource;
|
||||
|
||||
private volatile MessageSource<?> source;
|
||||
|
||||
@@ -136,45 +124,19 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint
|
||||
((NamedComponent) this.source).getComponentType() : "inbound-channel-adapter";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReceiveOnlyAdvice(Advice advice) {
|
||||
return advice instanceof MessageSourceMutator;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyReceiveOnlyAdviceChain(Collection<Advice> chain) {
|
||||
if (!CollectionUtils.isEmpty(chain)) {
|
||||
if (AopUtils.isAopProxy(this.source)) {
|
||||
Advised advised = (Advised) this.source;
|
||||
this.appliedAdvices.forEach(advised::removeAdvice);
|
||||
chain.forEach(advice -> advised.addAdvisor(adviceToReceiveAdvisor(advice)));
|
||||
}
|
||||
else {
|
||||
ProxyFactory proxyFactory = new ProxyFactory(this.source);
|
||||
chain.forEach(advice -> proxyFactory.addAdvisor(adviceToReceiveAdvisor(advice)));
|
||||
this.source = (MessageSource<?>) proxyFactory.getProxy(getBeanClassLoader());
|
||||
}
|
||||
this.appliedAdvices.clear();
|
||||
this.appliedAdvices.addAll(chain);
|
||||
if (!(isSyncExecutor()) && logger.isWarnEnabled()) {
|
||||
logger.warn(getComponentName() + ": A task executor is supplied and " + chain.size()
|
||||
+ "MessageSourceMutator(s) is/are provided. If an advice mutates the source, such "
|
||||
+ "mutations are not thread safe and could cause unexpected results, especially with "
|
||||
+ "high frequency pollers. Consider using a downstream ExecutorChannel instead of "
|
||||
+ "adding an executor to the poller");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReactive() {
|
||||
return getOutputChannel() instanceof ReactiveStreamsSubscribableChannel;
|
||||
}
|
||||
|
||||
private NameMatchMethodPointcutAdvisor adviceToReceiveAdvisor(Advice advice) {
|
||||
NameMatchMethodPointcutAdvisor sourceAdvisor = new NameMatchMethodPointcutAdvisor(advice);
|
||||
sourceAdvisor.addMethodName("receive");
|
||||
return sourceAdvisor;
|
||||
@Override
|
||||
protected Object getReceiveMessageSource() {
|
||||
return getMessageSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void setReceiveMessageSource(Object source) {
|
||||
this.source = (MessageSource<?>) source;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2020 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.
|
||||
@@ -35,8 +35,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.aopalliance.intercept.Joinpoint;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
@@ -48,9 +47,9 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.aop.AbstractMessageSourceAdvice;
|
||||
import org.springframework.integration.aop.CompoundTriggerAdvice;
|
||||
import org.springframework.integration.aop.SimpleActiveIdleMessageSourceAdvice;
|
||||
import org.springframework.integration.aop.ReceiveMessageAdvice;
|
||||
import org.springframework.integration.aop.SimpleActiveIdleReceiveMessageAdvice;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.NullChannel;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
@@ -64,14 +63,14 @@ import org.springframework.integration.util.CompoundTrigger;
|
||||
import org.springframework.integration.util.DynamicPeriodicTrigger;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.scheduling.TriggerContext;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.scheduling.support.PeriodicTrigger;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
@@ -80,8 +79,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
* @since 4.1
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
public class PollerAdviceTests {
|
||||
|
||||
@@ -194,10 +192,10 @@ public class PollerAdviceTests {
|
||||
});
|
||||
|
||||
final AtomicInteger count = new AtomicInteger();
|
||||
class TestSourceAdvice extends AbstractMessageSourceAdvice {
|
||||
class TestSourceAdvice implements ReceiveMessageAdvice {
|
||||
|
||||
@Override
|
||||
public boolean beforeReceive(MessageSource<?> target) {
|
||||
public boolean beforeReceive(Object target) {
|
||||
count.incrementAndGet();
|
||||
callOrder.add("b");
|
||||
latch.get().countDown();
|
||||
@@ -205,7 +203,7 @@ public class PollerAdviceTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> afterReceive(Message<?> result, MessageSource<?> target) {
|
||||
public Message<?> afterReceive(Message<?> result, Object target) {
|
||||
callOrder.add("d");
|
||||
latch.get().countDown();
|
||||
return result;
|
||||
@@ -267,7 +265,7 @@ public class PollerAdviceTests {
|
||||
latch.countDown();
|
||||
return m;
|
||||
});
|
||||
SimpleActiveIdleMessageSourceAdvice toggling = new SimpleActiveIdleMessageSourceAdvice(trigger);
|
||||
SimpleActiveIdleReceiveMessageAdvice toggling = new SimpleActiveIdleReceiveMessageAdvice(trigger);
|
||||
toggling.setActivePollPeriod(11);
|
||||
toggling.setIdlePollPeriod(12);
|
||||
adapter.setAdviceChain(Collections.singletonList(toggling));
|
||||
@@ -282,6 +280,56 @@ public class PollerAdviceTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActiveIdleAdviceOnQueueChannel() throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(5);
|
||||
final LinkedList<Long> triggerPeriods = new LinkedList<>();
|
||||
final DynamicPeriodicTrigger trigger = new DynamicPeriodicTrigger(10);
|
||||
|
||||
PollingConsumer pollingConsumer =
|
||||
new PollingConsumer(new PollableChannel() {
|
||||
|
||||
@Override
|
||||
public Message<?> receive() {
|
||||
synchronized (triggerPeriods) {
|
||||
triggerPeriods.add(trigger.getDuration().toMillis());
|
||||
}
|
||||
Message<Object> m = null;
|
||||
if (latch.getCount() % 2 == 0) {
|
||||
m = new GenericMessage<>("foo");
|
||||
}
|
||||
latch.countDown();
|
||||
return m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> receive(long timeout) {
|
||||
return receive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean send(Message<?> message, long timeout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}, m -> { });
|
||||
|
||||
SimpleActiveIdleReceiveMessageAdvice toggling = new SimpleActiveIdleReceiveMessageAdvice(trigger);
|
||||
toggling.setActivePollPeriod(11);
|
||||
toggling.setIdlePollPeriod(12);
|
||||
pollingConsumer.setAdviceChain(Collections.singletonList(toggling));
|
||||
pollingConsumer.setTrigger(trigger);
|
||||
pollingConsumer.setBeanFactory(this.beanFactory);
|
||||
pollingConsumer.setTaskScheduler(this.threadPoolTaskScheduler);
|
||||
pollingConsumer.afterPropertiesSet();
|
||||
pollingConsumer.start();
|
||||
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
pollingConsumer.stop();
|
||||
synchronized (triggerPeriods) {
|
||||
assertThat(triggerPeriods.subList(0, 5)).containsExactly(10L, 12L, 11L, 12L, 11L);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompoundTriggerAdvice() throws Exception {
|
||||
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
|
||||
@@ -361,18 +409,18 @@ public class PollerAdviceTests {
|
||||
|
||||
}
|
||||
|
||||
public static class OtherAdvice extends AbstractMessageSourceAdvice {
|
||||
public static class OtherAdvice implements ReceiveMessageAdvice {
|
||||
|
||||
private int calls;
|
||||
|
||||
@Override
|
||||
public boolean beforeReceive(MessageSource<?> source) {
|
||||
public boolean beforeReceive(Object source) {
|
||||
this.calls++;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> afterReceive(Message<?> result, MessageSource<?> source) {
|
||||
public Message<?> afterReceive(Message<?> result, Object source) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user