INT-3365: Add SmartLifecycle for Annotations
JIRA: https://jira.spring.io/browse/INT-3365 INT-3365 Polishing
This commit is contained in:
committed by
Gary Russell
parent
55c4e26619
commit
3f40c40641
@@ -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}).
|
||||
|
||||
@@ -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}).
|
||||
|
||||
@@ -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}).
|
||||
|
||||
@@ -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}).
|
||||
|
||||
@@ -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}).
|
||||
|
||||
@@ -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}).
|
||||
|
||||
@@ -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}).
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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><int:inbound-channel-adapter></code> XML component.
|
||||
In addition, all Messaging Annotations now provide <interfacename>SmartLifecycle</interfacename> options.
|
||||
For more information, see <xref linkend="annotations"/>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user