INT-3356: Add InboundChannelAdapter Annotation

JIRA: https://jira.spring.io/browse/INT-3356

INT-3356 Polishing

Long lines and doc polish.
This commit is contained in:
Artem Bilan
2014-04-09 15:19:08 +03:00
committed by Gary Russell
parent ea41e12303
commit 55c4e26619
7 changed files with 385 additions and 88 deletions

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2014 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
*
* http://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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a method is capable of producing a {@link org.springframework.messaging.Message}
* or {@link org.springframework.messaging.Message} {@code payload}.
* <p>
* A method annotated with {@code @InboundChannelAdapter} can't accept any parameters.
* <p>
* Return values from the annotated method may be of any type. If the return
* value is not a {@link org.springframework.messaging.Message}, a {@link org.springframework.messaging.Message}
* will be created with that object as its {@code payload}.
* <p>
* The result {@link org.springframework.messaging.Message} will be sent to the provided {@link #value()}.
* <p>
* {@code @InboundChannelAdapter} is an analogue of {@code <int:inbound-channel-adapter/>}. With that
* the {@link org.springframework.integration.scheduling.PollerMetadata} is required to to initiate
* the method invocation. Or {@link #poller()} should be provided, or the
* {@link org.springframework.integration.scheduling.PollerMetadata#DEFAULT_POLLER} bean has to be configured
* in the application context.
*
*
* @author Artem Bilan
* @since 4.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface InboundChannelAdapter {
/**
* @return the 'channel' bean name to send the {@link org.springframework.messaging.Message}.
*/
String value();
/**
* @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.
*/
Poller[] poller() default {};
}

View File

@@ -37,6 +37,7 @@ import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.IntegrationConfigUtils;
import org.springframework.integration.context.Orderable;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.AbstractPollingEndpoint;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
@@ -159,74 +160,8 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
Assert.notNull(inputChannel, "failed to resolve inputChannel '" + inputChannelName + "'");
if (inputChannel instanceof PollableChannel) {
PollerMetadata pollerMetadata = null;
Poller[] pollers = (Poller[]) AnnotationUtils.getValue(annotation, "poller");
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];
String ref = poller.value();
String triggerRef = poller.trigger();
String executorRef = poller.taskExecutor();
String fixedDelayValue = this.environment.resolvePlaceholders(poller.fixedDelay());
String fixedRateValue = this.environment.resolvePlaceholders(poller.fixedRate());
String maxMessagesPerPollValue = this.environment.resolvePlaceholders(poller.maxMessagesPerPoll());
String cron = this.environment.resolvePlaceholders(poller.cron());
if (StringUtils.hasText(ref)) {
Assert.state(!StringUtils.hasText(triggerRef) && !StringUtils.hasText(executorRef) && !StringUtils.hasText(cron)
&& !StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue)
&& !StringUtils.hasText(maxMessagesPerPollValue),
"The '@Poller' 'ref' attribute is mutually exclusive with other attributes.");
pollerMetadata = this.beanFactory.getBean(ref, PollerMetadata.class);
}
else {
pollerMetadata = new PollerMetadata();
if (StringUtils.hasText(maxMessagesPerPollValue)) {
pollerMetadata.setMaxMessagesPerPoll(Long.parseLong(maxMessagesPerPollValue));
}
if (StringUtils.hasText(executorRef)) {
pollerMetadata.setTaskExecutor(this.beanFactory.getBean(executorRef, TaskExecutor.class));
}
Trigger trigger = null;
if (StringUtils.hasText(triggerRef)) {
Assert.state(!StringUtils.hasText(cron) && !StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue),
"The '@Poller' 'trigger' attribute is mutually exclusive with other attributes.");
trigger = this.beanFactory.getBean(triggerRef, Trigger.class);
}
else if (StringUtils.hasText(cron)) {
Assert.state(!StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue),
"The '@Poller' 'cron' attribute is mutually exclusive with other attributes.");
trigger = new CronTrigger(cron);
}
else if (StringUtils.hasText(fixedDelayValue)) {
Assert.state(!StringUtils.hasText(fixedRateValue),
"The '@Poller' 'fixedDelay' attribute is mutually exclusive with other attributes.");
trigger = new PeriodicTrigger(Long.parseLong(fixedDelayValue));
}
else if (StringUtils.hasText(fixedRateValue)) {
trigger = new PeriodicTrigger(Long.parseLong(fixedRateValue));
((PeriodicTrigger) trigger).setFixedRate(true);
}
//'Trigger' can be null. 'PollingConsumer' does fallback to the 'new PeriodicTrigger(10)'.
pollerMetadata.setTrigger(trigger);
}
}
else {
pollerMetadata = PollerMetadata.getDefaultPollerMetadata(this.beanFactory);
Assert.notNull(pollerMetadata, "No poller has been defined for Annotation-based endpoint, " +
"and no default poller is available within the context.");
}
PollingConsumer pollingConsumer = new PollingConsumer((PollableChannel) inputChannel, handler);
pollingConsumer.setTaskExecutor(pollerMetadata.getTaskExecutor());
pollingConsumer.setTrigger(pollerMetadata.getTrigger());
pollingConsumer.setAdviceChain(pollerMetadata.getAdviceChain());
pollingConsumer.setMaxMessagesPerPoll(pollerMetadata.getMaxMessagesPerPoll());
pollingConsumer.setErrorHandler(pollerMetadata.getErrorHandler());
pollingConsumer.setReceiveTimeout(pollerMetadata.getReceiveTimeout());
pollingConsumer.setTransactionSynchronizationFactory(pollerMetadata.getTransactionSynchronizationFactory());
this.configurePollingEndpoint(pollingConsumer, annotation);
endpoint = pollingConsumer;
}
else {
@@ -239,7 +174,79 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
return endpoint;
}
private String generateHandlerBeanName(String originalBeanName, Method method, Class<? extends Annotation> annotationType) {
protected void configurePollingEndpoint(AbstractPollingEndpoint pollingEndpoint, T annotation) {
PollerMetadata pollerMetadata = null;
Poller[] pollers = (Poller[]) AnnotationUtils.getValue(annotation, "poller");
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];
String ref = poller.value();
String triggerRef = poller.trigger();
String executorRef = poller.taskExecutor();
String fixedDelayValue = this.environment.resolvePlaceholders(poller.fixedDelay());
String fixedRateValue = this.environment.resolvePlaceholders(poller.fixedRate());
String maxMessagesPerPollValue = this.environment.resolvePlaceholders(poller.maxMessagesPerPoll());
String cron = this.environment.resolvePlaceholders(poller.cron());
if (StringUtils.hasText(ref)) {
Assert.state(!StringUtils.hasText(triggerRef) && !StringUtils.hasText(executorRef) && !StringUtils.hasText(cron)
&& !StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue)
&& !StringUtils.hasText(maxMessagesPerPollValue),
"The '@Poller' 'ref' attribute is mutually exclusive with other attributes.");
pollerMetadata = this.beanFactory.getBean(ref, PollerMetadata.class);
}
else {
pollerMetadata = new PollerMetadata();
if (StringUtils.hasText(maxMessagesPerPollValue)) {
pollerMetadata.setMaxMessagesPerPoll(Long.parseLong(maxMessagesPerPollValue));
}
if (StringUtils.hasText(executorRef)) {
pollerMetadata.setTaskExecutor(this.beanFactory.getBean(executorRef, TaskExecutor.class));
}
Trigger trigger = null;
if (StringUtils.hasText(triggerRef)) {
Assert.state(!StringUtils.hasText(cron) && !StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue),
"The '@Poller' 'trigger' attribute is mutually exclusive with other attributes.");
trigger = this.beanFactory.getBean(triggerRef, Trigger.class);
}
else if (StringUtils.hasText(cron)) {
Assert.state(!StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue),
"The '@Poller' 'cron' attribute is mutually exclusive with other attributes.");
trigger = new CronTrigger(cron);
}
else if (StringUtils.hasText(fixedDelayValue)) {
Assert.state(!StringUtils.hasText(fixedRateValue),
"The '@Poller' 'fixedDelay' attribute is mutually exclusive with other attributes.");
trigger = new PeriodicTrigger(Long.parseLong(fixedDelayValue));
}
else if (StringUtils.hasText(fixedRateValue)) {
trigger = new PeriodicTrigger(Long.parseLong(fixedRateValue));
((PeriodicTrigger) trigger).setFixedRate(true);
}
//'Trigger' can be null. 'PollingConsumer' does fallback to the 'new PeriodicTrigger(10)'.
pollerMetadata.setTrigger(trigger);
}
}
else {
pollerMetadata = PollerMetadata.getDefaultPollerMetadata(this.beanFactory);
Assert.notNull(pollerMetadata, "No poller has been defined for Annotation-based endpoint, " +
"and no default poller is available within the context.");
}
pollingEndpoint.setTaskExecutor(pollerMetadata.getTaskExecutor());
pollingEndpoint.setTrigger(pollerMetadata.getTrigger());
pollingEndpoint.setAdviceChain(pollerMetadata.getAdviceChain());
pollingEndpoint.setMaxMessagesPerPoll(pollerMetadata.getMaxMessagesPerPoll());
pollingEndpoint.setErrorHandler(pollerMetadata.getErrorHandler());
if (pollingEndpoint instanceof PollingConsumer) {
((PollingConsumer) pollingEndpoint).setReceiveTimeout(pollerMetadata.getReceiveTimeout());
}
pollingEndpoint.setTransactionSynchronizationFactory(pollerMetadata.getTransactionSynchronizationFactory());
}
protected String generateHandlerBeanName(String originalBeanName, Method method, Class<? extends Annotation> annotationType) {
String baseName = originalBeanName + "." + method.getName() + "." + ClassUtils.getShortNameAsProperty(annotationType);
String name = baseName;
int count = 1;

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2014 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
*
* http://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.config.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.config.IntegrationConfigUtils;
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
/**
* Post-processor for Methods annotated with {@link InboundChannelAdapter @InboundChannelAdapter}.
*
* @author Artem Bilan
* @since 4.0
*/
public class InboundChannelAdapterAnnotationPostProcessor extends
AbstractMethodAnnotationPostProcessor<InboundChannelAdapter> {
public InboundChannelAdapterAnnotationPostProcessor(ListableBeanFactory beanFactory, Environment environment) {
super(beanFactory, environment);
}
@Override
public Object postProcess(Object bean, String beanName, Method method, InboundChannelAdapter annotation) {
Assert.isTrue(!Void.class.isAssignableFrom(method.getReturnType()), "The method '" + method
+ "' for 'SourcePollingChannelAdapter' must not have 'void' return type.");
Assert.isTrue(method.getParameterTypes().length == 0, "The method '" + method
+ "' for 'SourcePollingChannelAdapter' must not have any parameters.");
String channelName = (String) AnnotationUtils.getValue(annotation);
Assert.hasText(channelName, "The channel ('value' attribute of @InboundChannelAdapter) can't be empty.");
MessageChannel channel = this.channelResolver.resolveDestination(channelName);
MethodInvokingMessageSource messageSource = new MethodInvokingMessageSource();
messageSource.setObject(bean);
messageSource.setMethod(method);
if (beanFactory instanceof ConfigurableListableBeanFactory) {
String handlerBeanName = this.generateHandlerBeanName(beanName, method, annotation.annotationType());
ConfigurableListableBeanFactory listableBeanFactory = (ConfigurableListableBeanFactory) beanFactory;
listableBeanFactory.registerSingleton(handlerBeanName, messageSource);
messageSource = (MethodInvokingMessageSource) listableBeanFactory
.initializeBean(messageSource, handlerBeanName);
}
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
adapter.setOutputChannel(channel);
adapter.setSource(messageSource);
this.configurePollingEndpoint(adapter, annotation);
return adapter;
}
@Override
protected String generateHandlerBeanName(String originalBeanName, Method method,
Class<? extends Annotation> annotationType) {
return super.generateHandlerBeanName(originalBeanName, method, annotationType)
.replaceFirst(IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX + "$", ".source");
}
@Override
protected MessageHandler createHandler(Object bean, Method method, InboundChannelAdapter annotation) {
throw new UnsupportedOperationException();
}
}

View File

@@ -34,7 +34,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -42,11 +41,11 @@ import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.Lifecycle;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.Filter;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Splitter;
@@ -104,6 +103,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
postProcessors.put(ServiceActivator.class, new ServiceActivatorAnnotationPostProcessor(this.beanFactory, this.environment));
postProcessors.put(Splitter.class, new SplitterAnnotationPostProcessor(this.beanFactory, this.environment));
postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor(this.beanFactory, this.environment));
postProcessors.put(InboundChannelAdapter.class, new InboundChannelAdapterAnnotationPostProcessor(this.beanFactory, this.environment));
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
@@ -126,27 +126,20 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
if (postProcessor != null && shouldCreateEndpoint(annotation)) {
Object result = postProcessor.postProcess(bean, beanName, method, annotation);
if (result != null && result instanceof AbstractEndpoint) {
AbstractEndpoint endpoint = (AbstractEndpoint) result;
String endpointBeanName = generateBeanName(beanName, method, annotation.annotationType());
if (result instanceof BeanNameAware) {
((BeanNameAware) result).setBeanName(endpointBeanName);
endpoint.setBeanName(endpointBeanName);
beanFactory.registerSingleton(endpointBeanName, endpoint);
endpoint.setBeanFactory(beanFactory);
try {
endpoint.afterPropertiesSet();
}
beanFactory.registerSingleton(endpointBeanName, result);
if (result instanceof BeanFactoryAware) {
((BeanFactoryAware) result).setBeanFactory(beanFactory);
catch (Exception e) {
throw new BeanInitializationException("failed to initialize annotated component", e);
}
if (result instanceof InitializingBean) {
try {
((InitializingBean) result).afterPropertiesSet();
}
catch (Exception e) {
throw new BeanInitializationException("failed to initialize annotated component", e);
}
}
if (result instanceof Lifecycle) {
lifecycles.add((Lifecycle) result);
if (result instanceof SmartLifecycle && ((SmartLifecycle) result).isAutoStartup()) {
((SmartLifecycle) result).start();
}
lifecycles.add(endpoint);
if (endpoint.isAutoStartup()) {
endpoint.start();
}
if (result instanceof ApplicationListener) {
listeners.add((ApplicationListener) result);
@@ -161,6 +154,9 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
private boolean shouldCreateEndpoint(Annotation annotation) {
Object inputChannel = AnnotationUtils.getValue(annotation, "inputChannel");
if (inputChannel == null && annotation instanceof InboundChannelAdapter) {
inputChannel = AnnotationUtils.getValue(annotation);
}
return (inputChannel != null && inputChannel instanceof String
&& StringUtils.hasText((String) inputChannel));
}
@@ -233,4 +229,5 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
}
this.running = false;
}
}

View File

@@ -26,6 +26,8 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Date;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.hamcrest.Matchers;
@@ -48,6 +50,7 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.GatewayHeader;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.MessagingGateway;
@@ -66,6 +69,7 @@ import org.springframework.integration.config.EnableMessageHistory;
import org.springframework.integration.config.EnablePublisher;
import org.springframework.integration.config.GlobalChannelInterceptor;
import org.springframework.integration.config.IntegrationConverter;
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.history.MessageHistoryConfigurer;
@@ -80,6 +84,7 @@ import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.stereotype.Component;
@@ -94,6 +99,7 @@ import org.springframework.test.context.support.AnnotationConfigContextLoader;
*/
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = {EnableIntegrationTests.ContextConfiguration.class, EnableIntegrationTests.ContextConfiguration2.class})
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class EnableIntegrationTests {
@Autowired
@@ -159,6 +165,15 @@ public class EnableIntegrationTests {
@Autowired
private MessageChannel bytesChannel;
@Autowired
private PollableChannel counterChannel;
@Autowired
private PollableChannel fooChannel;
@Autowired
private PollableChannel messageChannel;
@Test
public void testAnnotatedServiceActivator() {
assertEquals(10L, TestUtils.getPropertyValue(this.serviceActivatorEndpoint, "maxMessagesPerPoll"));
@@ -224,6 +239,27 @@ public class EnableIntegrationTests {
assertNull(this.wireTapChannel.receive(0));
assertThat(this.testChannelInterceptor.getInvoked(), Matchers.greaterThan(0));
assertThat(this.fbInterceptorCounter.get(), Matchers.greaterThan(0));
assertTrue(this.context.containsBean("enableIntegrationTests.AnnotationTestService.count.inboundChannelAdapter.source"));
Object messageSource = this.context.getBean("enableIntegrationTests.AnnotationTestService.count.inboundChannelAdapter.source");
assertThat(messageSource, Matchers.instanceOf(MethodInvokingMessageSource.class));
for (int i = 0; i < 10; i++) {
Message<?> message = this.counterChannel.receive(1000);
assertNotNull(message);
assertEquals(i + 1, message.getPayload());
}
Message<?> message = this.fooChannel.receive(1000);
assertNotNull(message);
assertEquals("foo", message.getPayload());
assertNull(this.fooChannel.receive(10));
message = this.messageChannel.receive(1000);
assertNotNull(message);
assertEquals("bar", message.getPayload());
assertTrue(message.getHeaders().containsKey("foo"));
assertEquals("FOO", message.getHeaders().get("foo"));
}
@Test
@@ -322,6 +358,19 @@ public class EnableIntegrationTests {
return new PeriodicTrigger(1000L);
}
@Bean
public Trigger onlyOnceTrigger() {
return new Trigger() {
private final AtomicBoolean invoked = new AtomicBoolean();
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
return this.invoked.getAndSet(true) ? null : new Date();
}
};
}
@Bean
public PollableChannel output() {
return new QueueChannel();
@@ -427,6 +476,21 @@ public class EnableIntegrationTests {
return new QueueChannel();
}
@Bean
public PollableChannel counterChannel() {
return new QueueChannel();
}
@Bean
public PollableChannel fooChannel() {
return new QueueChannel();
}
@Bean
public PollableChannel messageChannel() {
return new QueueChannel();
}
@Bean
public QueueChannel numberChannel() {
QueueChannel channel = new QueueChannel();
@@ -484,6 +548,8 @@ public class EnableIntegrationTests {
@MessageEndpoint
public static class AnnotationTestService {
private AtomicInteger counter = new AtomicInteger();
@ServiceActivator(inputChannel = "input", outputChannel = "output",
poller = @Poller(maxMessagesPerPoll = "${poller.maxMessagesPerPoll}", fixedDelay = "${poller.interval}"))
@Publisher
@@ -541,6 +607,37 @@ public class EnableIntegrationTests {
assertEquals("echo", message.getHeaders().get("calledMethod"));
return this.handle(message.getPayload());
}
@InboundChannelAdapter("counterChannel")
public Integer count() {
return this.counter.incrementAndGet();
}
@InboundChannelAdapter(value = "fooChannel", poller = @Poller(trigger = "onlyOnceTrigger", maxMessagesPerPoll = "1"))
public String foo() {
return "foo";
}
@InboundChannelAdapter(value = "messageChannel", poller = @Poller(fixedDelay = "${poller.interval}", maxMessagesPerPoll = "1"))
public Message<?> message() {
return MessageBuilder.withPayload("bar").setHeader("foo", "FOO").build();
}
/*
* This is an error because 'InboundChannelAdapter' method must not have any arguments.
*/
/*@InboundChannelAdapter("errorChannel")
public String error1(Object arg) {
return "foo";
}*/
/*
* This is an error because 'InboundChannelAdapter' return type must not be 'void'.
*/
/*@InboundChannelAdapter("errorChannel")
public void error2() {
}*/
}
@MessagingGateway(defaultRequestChannel = "gatewayChannel", defaultHeaders = @GatewayHeader(name = "foo", value = "FOO"))

View File

@@ -230,6 +230,7 @@ public class FooService {
<listitem>@ServiceActivator</listitem>
<listitem>@Splitter</listitem>
<listitem>@Transformer</listitem>
<listitem>@InboundChannelAdapter</listitem>
</itemizedlist>
</para>
<para>The behavior of each is described in its own chapter or section within
@@ -385,6 +386,37 @@ public PollerMetadata myPoller() {
}
}]]></programlisting>
</para>
<para>
<emphasis role="bold">@InboundChannelAdapter</emphasis>
</para>
<para>
Starting with <emphasis>version 4.0</emphasis>, the
<interfacename>@InboundChannelAdapter</interfacename> method annotation is available.
This produces a <classname>SourcePollingChannelAdapter</classname>
integration component based on a <classname>MethodInvokingMessageSource</classname> for the annotated method.
This annotation is an analogue of <code>&lt;int:inbound-channel-adapter&gt;</code>
XML component and has the same restrictions:
the method cannot have parameters, and the return type must not be <code>void</code>.
It has two attributes: <code>value</code> -
the required <interfacename>MessageChannel</interfacename> bean name and <code>poller</code> - an optional
<interfacename>@Poller</interfacename> annotation, as described above. If there
is need to provide some <interfacename>MessageHeaders</interfacename>, use a
<interfacename>Message&lt;?&gt;</interfacename> return type and
build the <interfacename>Message&lt;?&gt;</interfacename> within the method
using a <classname>MessageBuilder</classname> to
configure its <interfacename>MessageHeaders</interfacename>.
<programlisting language="java"><![CDATA[@InboundChannelAdapter("counterChannel")
public Integer count() {
return this.counter.incrementAndGet();
}
@InboundChannelAdapter(value = "fooChannel", poller = @Poller(fixed-rate = "5000"))
public String foo() {
return "foo";
}]]></programlisting>
The first example requires that the default poller has been declared elsewhere in the application
context.
</para>
<para>
Also see <xref linkend="advising-with-annotations"/>.
</para>

View File

@@ -154,6 +154,14 @@
For more information, see <xref linkend="annotations"/>.
</para>
</section>
<section id="4.0-inbound-channel-adapter-annotation">
<title>@InboundChannelAdapter</title>
<para>
The <interfacename>@InboundChannelAdapter</interfacename> method annotation is now available.
It is an analogue of the <code>&lt;int:inbound-channel-adapter&gt;</code> XML component.
For more information, see <xref linkend="annotations"/>.
</para>
</section>
</section>
<section id="4.0-general">