From 3f40c406414385abbd83812fcff6bef5d2a512f6 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Thu, 10 Apr 2014 15:08:42 +0300 Subject: [PATCH] INT-3365: Add `SmartLifecycle` for Annotations JIRA: https://jira.spring.io/browse/INT-3365 INT-3365 Polishing --- .../integration/annotation/Aggregator.java | 8 +++++ .../integration/annotation/Filter.java | 8 +++++ .../annotation/InboundChannelAdapter.java | 8 +++++ .../integration/annotation/Router.java | 8 +++++ .../annotation/ServiceActivator.java | 8 +++++ .../integration/annotation/Splitter.java | 8 +++++ .../integration/annotation/Transformer.java | 8 +++++ .../MessagingAnnotationPostProcessor.java | 29 ++++++++++++++++++ .../configuration/EnableIntegrationTests.java | 30 ++++++++++++++----- src/reference/docbook/configuration.xml | 29 +++++++++++++----- src/reference/docbook/whats-new.xml | 3 +- 11 files changed, 131 insertions(+), 16 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregator.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregator.java index ec59326970..6038d50aab 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregator.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregator.java @@ -65,6 +65,14 @@ public @interface Aggregator { */ boolean sendPartialResultsOnExpiry() default false; + /* + {@code SmartLifecycle} options. + Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. + */ + String autoStartup() default "true"; + + String phase() default "0"; + /** * @return the {@link Poller} options for a polled endpoint * ({@link org.springframework.integration.scheduling.PollerMetadata}). diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java index 2f20ea21cc..dc74a2a40a 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java @@ -52,6 +52,14 @@ public @interface Filter { boolean discardWithinAdvice() default true; + /* + {@code SmartLifecycle} options. + Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. + */ + String autoStartup() default "true"; + + String phase() default "0"; + /** * @return the {@link Poller} options for a polled endpoint * ({@link org.springframework.integration.scheduling.PollerMetadata}). diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapter.java index 0f352539dd..bf56090133 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapter.java @@ -56,6 +56,14 @@ public @interface InboundChannelAdapter { */ String value(); + /* + {@code SmartLifecycle} options. + Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. + */ + String autoStartup() default "true"; + + String phase() default "0"; + /** * @return the {@link org.springframework.integration.annotation.Poller} options for a polled endpoint * ({@link org.springframework.integration.scheduling.PollerMetadata}). diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java index 8821cf3441..e196418071 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java @@ -53,6 +53,14 @@ public @interface Router { String defaultOutputChannel() default ""; + /* + {@code SmartLifecycle} options. + Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. + */ + String autoStartup() default "true"; + + String phase() default "0"; + /** * @return the {@link Poller} options for a polled endpoint * ({@link org.springframework.integration.scheduling.PollerMetadata}). diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java index b86ba3c120..5ffcde9cf2 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java @@ -53,6 +53,14 @@ public @interface ServiceActivator { String[] adviceChain() default {}; + /* + {@code SmartLifecycle} options. + Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. + */ + String autoStartup() default "true"; + + String phase() default "0"; + /** * @return the {@link Poller} options for a polled endpoint * ({@link org.springframework.integration.scheduling.PollerMetadata}). diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java index d709e74604..555aaf2fd5 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java @@ -52,6 +52,14 @@ public @interface Splitter { String[] adviceChain() default {}; + /* + {@code SmartLifecycle} options. + Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. + */ + String autoStartup() default "true"; + + String phase() default "0"; + /** * @return the {@link Poller} options for a polled endpoint * ({@link org.springframework.integration.scheduling.PollerMetadata}). diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java index 07c6e9b158..b0c8bd17e4 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java @@ -43,6 +43,14 @@ public @interface Transformer { String[] adviceChain() default {}; + /* + {@code SmartLifecycle} options. + Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. + */ + String autoStartup() default "true"; + + String phase() default "0"; + /** * @return the {@link Poller} options for a polled endpoint * ({@link org.springframework.integration.scheduling.PollerMetadata}). diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java index dec6f2d04c..8f0dc6202e 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java @@ -84,6 +84,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean private volatile boolean running = true; + @Override public void setBeanFactory(BeanFactory beanFactory) { Assert.isAssignable(ConfigurableListableBeanFactory.class, beanFactory.getClass(), "a ConfigurableListableBeanFactory is required"); @@ -95,6 +96,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean this.environment = environment; } + @Override public void afterPropertiesSet() { Assert.notNull(this.beanFactory, "BeanFactory must not be null"); postProcessors.put(Filter.class, new FilterAnnotationPostProcessor(this.beanFactory, this.environment)); @@ -106,10 +108,12 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean postProcessors.put(InboundChannelAdapter.class, new InboundChannelAdapterAnnotationPostProcessor(this.beanFactory, this.environment)); } + @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } + @Override public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException { Assert.notNull(this.beanFactory, "BeanFactory must not be null"); final Class beanClass = this.getBeanClass(bean); @@ -118,6 +122,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean return bean; } ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() { + @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { Annotation[] annotations = AnnotationUtils.getAnnotations(method); @@ -127,6 +132,26 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean Object result = postProcessor.postProcess(bean, beanName, method, annotation); if (result != null && result instanceof AbstractEndpoint) { AbstractEndpoint endpoint = (AbstractEndpoint) result; + String autoStartup = (String) AnnotationUtils.getValue(annotation, "autoStartup"); + if (StringUtils.hasText(autoStartup)) { + if (environment != null) { + autoStartup = environment.resolvePlaceholders(autoStartup); + } + if (StringUtils.hasText(autoStartup)) { + endpoint.setAutoStartup(Boolean.parseBoolean(autoStartup)); + } + } + + String phase = (String) AnnotationUtils.getValue(annotation, "phase"); + if (StringUtils.hasText(phase)) { + if (environment != null) { + phase = environment.resolvePlaceholders(phase); + } + if (StringUtils.hasText(phase)) { + endpoint.setPhase(Integer.parseInt(phase)); + } + } + String endpointBeanName = generateBeanName(beanName, method, annotation.annotationType()); endpoint.setBeanName(endpointBeanName); beanFactory.registerSingleton(endpointBeanName, endpoint); @@ -192,6 +217,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean } + @Override public void onApplicationEvent(ApplicationEvent event) { for (ApplicationListener listener : listeners) { try { @@ -208,10 +234,12 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean // Lifecycle implementation + @Override public boolean isRunning() { return this.running; } + @Override public void start() { for (Lifecycle lifecycle : this.lifecycles) { if (!lifecycle.isRunning()) { @@ -221,6 +249,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean this.running = true; } + @Override public void stop() { for (Lifecycle lifecycle : this.lifecycles) { if (lifecycle.isRunning()) { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java index 800a80ea97..8b80a81ed1 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java @@ -39,6 +39,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.AbstractFactoryBean; import org.springframework.context.ApplicationContext; +import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -222,7 +223,8 @@ public class EnableIntegrationTests { assertNotNull(messageHistory); String messageHistoryString = messageHistory.toString(); assertThat(messageHistoryString, Matchers.containsString("input")); - assertThat(messageHistoryString, Matchers.containsString("AnnotationTestService.handle.serviceActivator.handler")); + assertThat(messageHistoryString, + Matchers.containsString("AnnotationTestService.handle.serviceActivator.handler")); assertThat(messageHistoryString, Matchers.not(Matchers.containsString("output"))); receive = this.publishedChannel.receive(1000); @@ -240,10 +242,20 @@ public class EnableIntegrationTests { 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"); + 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)); + assertNull(this.counterChannel.receive(10)); + + SmartLifecycle countSA = this.context.getBean("enableIntegrationTests.AnnotationTestService.count.inboundChannelAdapter", + SmartLifecycle.class); + assertFalse(countSA.isAutoStartup()); + assertEquals(23, countSA.getPhase()); + countSA.start(); + for (int i = 0; i < 10; i++) { Message message = this.counterChannel.receive(1000); assertNotNull(message); @@ -266,14 +278,14 @@ public class EnableIntegrationTests { @DirtiesContext public void testChangePatterns() { try { - this.configurer.setComponentNamePatterns(new String[]{"*"}); + this.configurer.setComponentNamePatterns(new String[] {"*"}); fail("ExpectedException"); } catch (IllegalStateException e) { assertThat(e.getMessage(), containsString("cannot be changed")); } this.configurer.stop(); - this.configurer.setComponentNamePatterns(new String[]{"*"}); + this.configurer.setComponentNamePatterns(new String[] {"*"}); assertEquals("*", TestUtils.getPropertyValue(this.configurer, "componentNamePatterns", String[].class)[0]); } @@ -548,7 +560,7 @@ public class EnableIntegrationTests { @MessageEndpoint public static class AnnotationTestService { - private AtomicInteger counter = new AtomicInteger(); + private final AtomicInteger counter = new AtomicInteger(); @ServiceActivator(inputChannel = "input", outputChannel = "output", poller = @Poller(maxMessagesPerPoll = "${poller.maxMessagesPerPoll}", fixedDelay = "${poller.interval}")) @@ -608,7 +620,8 @@ public class EnableIntegrationTests { return this.handle(message.getPayload()); } - @InboundChannelAdapter("counterChannel") + @InboundChannelAdapter(value = "counterChannel", autoStartup = "false", + phase = "23") public Integer count() { return this.counter.incrementAndGet(); } @@ -618,7 +631,8 @@ public class EnableIntegrationTests { return "foo"; } - @InboundChannelAdapter(value = "messageChannel", poller = @Poller(fixedDelay = "${poller.interval}", maxMessagesPerPoll = "1")) + @InboundChannelAdapter(value = "messageChannel", poller = @Poller(fixedDelay = "${poller.interval}", + maxMessagesPerPoll = "1")) public Message message() { return MessageBuilder.withPayload("bar").setHeader("foo", "FOO").build(); } diff --git a/src/reference/docbook/configuration.xml b/src/reference/docbook/configuration.xml index 0036aaa8cb..081641b2f7 100644 --- a/src/reference/docbook/configuration.xml +++ b/src/reference/docbook/configuration.xml @@ -317,13 +317,28 @@ public class FooService { be added: ]]> - The processing of these annotations creates the same beans (EventDrivenConsumers and - MessageHandlers) as with similar xml components. The bean names are generated - with this pattern: - [componentName].[methodName].[annotationClassShortName] for the EventDrivenConsumer - endpoint and the same name with an additional .handler suffix for - the MessageHandler bean. The - MessageHandlers are also eligible to be tracked by . + The processing of these annotations creates the same beans (AbstractEndpoints and + MessageHandlers (or MessageSources for the + inbound channel adapter - see below) as with + similar xml components. The bean names are generated with this pattern: + [componentName].[methodName].[decapitalizedAnnotationClassShortName] for the + AbstractEndpoint and the same name with an additional .handler + (.source) suffix for the MessageHandler + (MessageSource) bean. The MessageHandlers + (MessageSources) are also eligible to be tracked by + . + + + Starting with version 4.0, all Messaging Annotations provide + SmartLifecycle options - autoStartup and phase to + allow endpoint lifecycle control on application context initialization. + They default to true and 0 respectively. + To change the state of an endpoint (e.g + start()/stop()) obtain a reference to the endpoint bean using the + BeanFactory (or autowiring) and invoke the method(s), + or send a command message to the + Control Bus (). For these purposes you should use the + beanName mentioned above. @Poller diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 9a5da506fb..30b31638a3 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -155,10 +155,11 @@
- @InboundChannelAdapter + @InboundChannelAdapter and SmartLifecycle for Annotated Endpoints The @InboundChannelAdapter method annotation is now available. It is an analogue of the <int:inbound-channel-adapter> XML component. + In addition, all Messaging Annotations now provide SmartLifecycle options. For more information, see .