INT-3945: Add async to the @ServiceActivator

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

Adding test for async annotated ServiceActivator

fix whitespace

Address comments

Change copyright and author

* Some code style polishing
* Rename `Log4j2LevelAdjuster.level()` to more friendly `forLevel()`
factory method name.
* Add `@param level` to the `Log4j2LevelAdjuster.forLevel()` to fix
JavaDoc warning
This commit is contained in:
Yilin Wei
2018-01-15 20:05:12 +00:00
committed by Artem Bilan
parent 1bb4f86151
commit e6ec86c505
6 changed files with 77 additions and 23 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@@ -41,8 +41,9 @@ import java.lang.annotation.Target;
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @author Yilin Wei
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@@ -77,7 +78,7 @@ public @interface ServiceActivator {
* Only the handler is advised, not the downstream flow.
* @return the advice chain.
*/
String[] adviceChain() default {};
String[] adviceChain() default { };
/**
* Specify the maximum amount of time in milliseconds to wait when sending a reply
@@ -110,12 +111,19 @@ public @interface ServiceActivator {
*/
String phase() default "";
/**
* Specify whether the service method is async.
* This value is {@code false} by default.
* @return the async flag.
*/
String async() default "";
/**
* @return the {@link 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 Poller} element is allowed.
*/
Poller[] poller() default {};
Poller[] poller() default { };
}

View File

@@ -257,7 +257,7 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
* by setting an empty array on the custom annotation.
*/
if (adviceChainNames != null && adviceChainNames.length > 0) {
adviceChain = new ArrayList<Advice>();
adviceChain = new ArrayList<>();
for (String adviceChainName : adviceChainNames) {
Object adviceChainBean = this.beanFactory.getBean(adviceChainName);
if (adviceChainBean instanceof Advice) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -38,12 +38,13 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @author Yilin Wei
*/
public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<ServiceActivator> {
public ServiceActivatorAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
super(beanFactory);
this.messageHandlerAttributes.addAll(Arrays.<String>asList("outputChannel", "requiresReply", "adviceChain"));
this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", "requiresReply", "adviceChain"));
}
@@ -56,9 +57,9 @@ public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnot
if (serviceActivator == null) {
if (target instanceof MessageHandler) {
/*
* Return a reply-producing message handler so that we still get 'produced no reply' messages
* and the super class will inject the advice chain to advise the handler method if needed.
*/
* Return a reply-producing message handler so that we still get 'produced no reply' messages
* and the super class will inject the advice chain to advise the handler method if needed.
*/
return new ReplyProducingMessageHandlerWrapper((MessageHandler) target);
}
else {
@@ -79,6 +80,11 @@ public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnot
serviceActivator.setRequiresReply(Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(requiresReply)));
}
String isAsync = MessagingAnnotationUtils.resolveAttribute(annotations, "async", String.class);
if (StringUtils.hasText(isAsync)) {
serviceActivator.setAsync(Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(isAsync)));
}
this.setOutputChannelIfPresent(annotations, serviceActivator);
return serviceActivator;
}

View File

@@ -19,10 +19,20 @@
<beans:bean id="annotatedEndpoint2"
class="org.springframework.integration.config.annotation.AnnotatedEndpointActivationTests.AnnotatedEndpoint2"/>
<beans:bean id="annotatedEndpoint3"
class="org.springframework.integration.config.annotation.AnnotatedEndpointActivationTests.AnnotatedEndpoint3"/>
<channel id="input"/>
<channel id="output">
<queue capacity="10"/>
</channel>
<channel id="inputAsync"/>
<channel id="outputAsync">
<queue capacity="10"/>
</channel>
</beans:beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2018 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.
@@ -35,17 +35,18 @@ import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;
/**
* @author Dave Syer
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @author Yilin Wei
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@DirtiesContext
public class AnnotatedEndpointActivationTests {
@@ -57,6 +58,14 @@ public class AnnotatedEndpointActivationTests {
@Qualifier("output")
private PollableChannel output;
@Autowired
@Qualifier("inputAsync")
private MessageChannel inputAsync;
@Autowired
@Qualifier("outputAsync")
private PollableChannel outputAsync;
@Autowired
private AbstractApplicationContext applicationContext;
@@ -72,7 +81,7 @@ public class AnnotatedEndpointActivationTests {
@Test
public void sendAndReceive() {
this.input.send(new GenericMessage<String>("foo"));
this.input.send(new GenericMessage<>("foo"));
Message<?> message = this.output.receive(100);
assertNotNull(message);
assertEquals("foo: 1", message.getPayload());
@@ -82,10 +91,19 @@ public class AnnotatedEndpointActivationTests {
assertTrue(this.applicationContext.containsBean("annotatedEndpoint2.process.serviceActivator"));
}
@Test
public void sendAndReceiveAsync() {
this.inputAsync.send(new GenericMessage<>("foo"));
Message<?> message = this.outputAsync.receive(100);
assertNotNull(message);
assertEquals("foo", message.getPayload());
assertTrue(this.applicationContext.containsBean("annotatedEndpoint3.process.serviceActivator"));
}
@Test
public void sendAndReceiveImplicitInputChannel() {
MessageChannel input = this.applicationContext.getBean("inputImplicit", MessageChannel.class);
input.send(new GenericMessage<String>("foo"));
input.send(new GenericMessage<>("foo"));
Message<?> message = this.output.receive(100);
assertNotNull(message);
assertEquals("foo: 1", message.getPayload());
@@ -96,7 +114,7 @@ public class AnnotatedEndpointActivationTests {
@DirtiesContext
public void stopContext() {
applicationContext.stop();
this.input.send(new GenericMessage<String>("foo"));
this.input.send(new GenericMessage<>("foo"));
}
@Test
@@ -104,7 +122,7 @@ public class AnnotatedEndpointActivationTests {
public void stopAndRestartContext() {
applicationContext.stop();
applicationContext.start();
this.input.send(new GenericMessage<String>("foo"));
this.input.send(new GenericMessage<>("foo"));
Message<?> message = this.output.receive(100);
assertNotNull(message);
assertEquals("foo: 1", message.getPayload());
@@ -139,5 +157,16 @@ public class AnnotatedEndpointActivationTests {
}
@SuppressWarnings("unused")
private static class AnnotatedEndpoint3 {
@ServiceActivator(inputChannel = "inputAsync", outputChannel = "outputAsync", async = "true")
public ListenableFuture<String> process(String message) {
SettableListenableFuture<String> future = new SettableListenableFuture<>();
future.set(message);
return future;
}
}
}

View File

@@ -183,7 +183,7 @@ public class Log4j2LevelAdjuster implements MethodRule {
* @return the Log4j2LevelAdjuster instance
*/
public static Log4j2LevelAdjuster trace() {
return level(Level.TRACE);
return forLevel(Level.TRACE);
}
/**
@@ -192,7 +192,7 @@ public class Log4j2LevelAdjuster implements MethodRule {
* @return the Log4j2LevelAdjuster instance
*/
public static Log4j2LevelAdjuster debug() {
return level(Level.DEBUG);
return forLevel(Level.DEBUG);
}
/**
@@ -201,15 +201,16 @@ public class Log4j2LevelAdjuster implements MethodRule {
* @return the Log4j2LevelAdjuster instance
*/
public static Log4j2LevelAdjuster info() {
return level(Level.INFO);
return forLevel(Level.INFO);
}
/**
* The factory to produce Log4j2LevelAdjuster instances for arbitrary logging {@link Level}
* with the {@code org.springframework.integration} as default category.
* @param level the {@link Level} to use for logging
* @return the Log4j2LevelAdjuster instance
*/
public static Log4j2LevelAdjuster level(Level level) {
public static Log4j2LevelAdjuster forLevel(Level level) {
return new Log4j2LevelAdjuster(level);
}