INT-3365: Add SmartLifecycle for Annotations

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

INT-3365 Polishing
This commit is contained in:
Artem Bilan
2014-04-10 15:08:42 +03:00
committed by Gary Russell
parent 55c4e26619
commit 3f40c40641
11 changed files with 131 additions and 16 deletions

View File

@@ -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}).

View File

@@ -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}).

View File

@@ -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}).

View File

@@ -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}).

View File

@@ -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}).

View File

@@ -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}).

View File

@@ -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}).

View File

@@ -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<ApplicationEvent> 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()) {

View File

@@ -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();
}

View File

@@ -317,13 +317,28 @@ public class FooService {
be added: <programlisting language="xml"><![CDATA[<int:annotation-config/>]]></programlisting>
</para>
<para>
The processing of these annotations creates the same beans (<classname>EventDrivenConsumer</classname>s and
<interfacename>MessageHandler</interfacename>s) as with similar xml components. The bean names are generated
with this pattern:
<code>[componentName].[methodName].[annotationClassShortName]</code> for the <classname>EventDrivenConsumer</classname>
endpoint and the same name with an additional <code>.handler</code> suffix for
the <interfacename>MessageHandler</interfacename> bean. The
<interfacename>MessageHandler</interfacename>s are also eligible to be tracked by <xref linkend="message-history"/>.
The processing of these annotations creates the same beans (<classname>AbstractEndpoint</classname>s and
<interfacename>MessageHandler</interfacename>s (or <interfacename>MessageSource</interfacename>s for the
inbound channel adapter - see below) as with
similar xml components. The bean names are generated with this pattern:
<code>[componentName].[methodName].[decapitalizedAnnotationClassShortName]</code> for the
<classname>AbstractEndpoint</classname> and the same name with an additional <code>.handler</code>
(<code>.source</code>) suffix for the <interfacename>MessageHandler</interfacename>
(<interfacename>MessageSource</interfacename>) bean. The <interfacename>MessageHandler</interfacename>s
(<interfacename>MessageSource</interfacename>s) are also eligible to be tracked by
<xref linkend="message-history"/>.
</para>
<para>
Starting with <emphasis>version 4.0</emphasis>, all Messaging Annotations provide
<interfacename>SmartLifecycle</interfacename> options - <code>autoStartup</code> and <code>phase</code> to
allow endpoint lifecycle control on application context initialization.
They default to <code>true</code> and <code>0</code> respectively.
To change the state of an endpoint (e.g
<code>start()/stop()</code>) obtain a reference to the endpoint bean using the
<interfacename>BeanFactory</interfacename> (or autowiring) and invoke the method(s),
or send a <emphasis>command message</emphasis> to the
<code>Control Bus</code> (<xref linkend="control-bus"/>). For these purposes you should use the
<code>beanName</code> mentioned above.
</para>
<para>
<emphasis role="bold">@Poller</emphasis>

View File

@@ -155,10 +155,11 @@
</para>
</section>
<section id="4.0-inbound-channel-adapter-annotation">
<title>@InboundChannelAdapter</title>
<title>@InboundChannelAdapter and SmartLifecycle for Annotated Endpoints</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.
In addition, all Messaging Annotations now provide <interfacename>SmartLifecycle</interfacename> options.
For more information, see <xref linkend="annotations"/>.
</para>
</section>