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