INT-4134: Add IntegrationFlows.from(Class<?>)
JIRA: https://jira.spring.io/browse/INT-4134 * Introduce `AnnotationGatewayProxyFactoryBean` to parse `@MessagingGateway` annotation directly in the bean. Useful for Java (`@Bean`) configuration variant * Use `AnnotationGatewayProxyFactoryBean` for the newly introduced `IntegrationFlows.from(Class<?>)` * Override `requestChannel` in the result gateways to the auto-created `DirectChannel` in the current `IntegrationFlowDefinition` * Register `AnnotationGatewayProxyFactoryBean` from the `IntegrationFlows.from(Class<?>)` as a bean with the name like: `IntegrationFlow` bean name plus `.gateway` suffix * Merge `GatewayCompletableFutureProxyFactoryBean` to `GatewayProxyFactoryBean`, since we are based on the Java 8 already anyway
This commit is contained in:
committed by
Gary Russell
parent
d58b94fb9e
commit
e2dd2c52d2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2017 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.
|
||||
@@ -24,7 +24,6 @@ import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeanMetadataAttribute;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
@@ -39,12 +38,10 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.annotation.AnnotationConstants;
|
||||
import org.springframework.integration.annotation.MessagingGateway;
|
||||
import org.springframework.integration.gateway.GatewayCompletableFutureProxyFactoryBean;
|
||||
import org.springframework.integration.gateway.GatewayMethodMetadata;
|
||||
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
|
||||
import org.springframework.integration.util.MessagingAnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -56,21 +53,16 @@ import org.springframework.util.StringUtils;
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @author Andy Wilksinson
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
}
|
||||
public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
if (importingClassMetadata != null && importingClassMetadata.isAnnotated(MessagingGateway.class.getName())) {
|
||||
Assert.isTrue(importingClassMetadata.isInterface(), "@MessagingGateway can only be specified on an interface");
|
||||
Assert.isTrue(importingClassMetadata.isInterface(),
|
||||
"@MessagingGateway can only be specified on an interface");
|
||||
List<MultiValueMap<String, Object>> valuesHierarchy = captureMetaAnnotationValues(importingClassMetadata);
|
||||
Map<String, Object> annotationAttributes =
|
||||
importingClassMetadata.getAnnotationAttributes(MessagingGateway.class.getName());
|
||||
@@ -82,14 +74,6 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
}
|
||||
|
||||
public BeanDefinitionHolder parse(Map<String, Object> gatewayAttributes) {
|
||||
boolean completableFutureCapable = true;
|
||||
try {
|
||||
ClassUtils.forName("java.util.concurrent.CompletableFuture", this.beanClassLoader);
|
||||
}
|
||||
catch (Exception e1) {
|
||||
completableFutureCapable = false;
|
||||
}
|
||||
|
||||
String defaultPayloadExpression = (String) gatewayAttributes.get("defaultPayloadExpression");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -107,14 +91,15 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
"'defaultPayloadExpression' is not allowed when a 'mapper' is provided");
|
||||
|
||||
boolean hasDefaultHeaders = !ObjectUtils.isEmpty(defaultHeaders);
|
||||
Assert.state(!hasMapper || !hasDefaultHeaders, "'defaultHeaders' are not allowed when a 'mapper' is provided");
|
||||
Assert.state(!hasMapper || !hasDefaultHeaders,
|
||||
"'defaultHeaders' are not allowed when a 'mapper' is provided");
|
||||
|
||||
BeanDefinitionBuilder gatewayProxyBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
completableFutureCapable ? GatewayCompletableFutureProxyFactoryBean.class
|
||||
: GatewayProxyFactoryBean.class);
|
||||
BeanDefinitionBuilder gatewayProxyBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(GatewayProxyFactoryBean.class);
|
||||
|
||||
if (hasDefaultHeaders || hasDefaultPayloadExpression) {
|
||||
BeanDefinitionBuilder methodMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(GatewayMethodMetadata.class);
|
||||
BeanDefinitionBuilder methodMetadataBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(GatewayMethodMetadata.class);
|
||||
if (hasDefaultPayloadExpression) {
|
||||
methodMetadataBuilder.addPropertyValue("payloadExpression", defaultPayloadExpression);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
* Copyright 2016-2017 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.
|
||||
@@ -51,6 +51,7 @@ import org.springframework.integration.dsl.IntegrationFlows;
|
||||
import org.springframework.integration.dsl.SourcePollingChannelAdapterSpec;
|
||||
import org.springframework.integration.dsl.StandardIntegrationFlow;
|
||||
import org.springframework.integration.dsl.support.MessageChannelReference;
|
||||
import org.springframework.integration.gateway.AnnotationGatewayProxyFactoryBean;
|
||||
import org.springframework.integration.support.context.NamedComponent;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -231,6 +232,9 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
|
||||
BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + subFlowNameIndex++;
|
||||
registerComponent(component, subFlowBeanName, beanName, registerSingleton);
|
||||
}
|
||||
else if (component instanceof AnnotationGatewayProxyFactoryBean) {
|
||||
registerComponent(component, flowNamePrefix + "gateway", beanName, registerSingleton);
|
||||
}
|
||||
else {
|
||||
String generateBeanName = generateBeanName(component);
|
||||
registerComponent(component, generateBeanName, beanName, registerSingleton);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
* Copyright 2016-2017 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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.integration.dsl;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.integration.annotation.MessagingGateway;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.dsl.channel.MessageChannelSpec;
|
||||
@@ -26,6 +27,8 @@ import org.springframework.integration.dsl.support.FixedSubscriberChannelPrototy
|
||||
import org.springframework.integration.dsl.support.MessageChannelReference;
|
||||
import org.springframework.integration.endpoint.MessageProducerSupport;
|
||||
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
|
||||
import org.springframework.integration.gateway.AnnotationGatewayProxyFactoryBean;
|
||||
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
|
||||
import org.springframework.integration.gateway.MessagingGatewaySupport;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -276,6 +279,36 @@ public final class IntegrationFlows {
|
||||
return from(inboundGateway, (IntegrationFlowBuilder) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the {@link MessageChannel} to the new {@link IntegrationFlowBuilder} chain,
|
||||
* which becomes as a {@code requestChannel} for the Messaging Gateway(s) built on the provided
|
||||
* service interface.
|
||||
* <p> A gateway proxy bean for provided service interface is registered
|
||||
* under a name of the {@link IntegrationFlow} bean plus {@code .gateway} suffix.
|
||||
* @param serviceInterface the class with a {@link MessagingGateway} annotation.
|
||||
* @return new {@link IntegrationFlowBuilder}.
|
||||
*/
|
||||
public static IntegrationFlowBuilder from(Class<?> serviceInterface) {
|
||||
final DirectChannel gatewayRequestChannel = new DirectChannel();
|
||||
|
||||
GatewayProxyFactoryBean gatewayProxyFactoryBean =
|
||||
new AnnotationGatewayProxyFactoryBean(serviceInterface) {
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
super.onInit();
|
||||
getGateways()
|
||||
.values()
|
||||
.forEach(gateway ->
|
||||
gateway.setRequestChannel(gatewayRequestChannel));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return from(gatewayRequestChannel)
|
||||
.addComponent(gatewayProxyFactoryBean);
|
||||
}
|
||||
|
||||
private static IntegrationFlowBuilder from(MessagingGatewaySupport inboundGateway,
|
||||
IntegrationFlowBuilder integrationFlowBuilder) {
|
||||
MessageChannel outputChannel = inboundGateway.getRequestChannel();
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2017 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.gateway;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.annotation.AnnotationConstants;
|
||||
import org.springframework.integration.annotation.MessagingGateway;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link GatewayProxyFactoryBean} extension for Java configuration,
|
||||
* when service interface is marked with the {@link MessagingGateway} annotation.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public class AnnotationGatewayProxyFactoryBean extends GatewayProxyFactoryBean {
|
||||
|
||||
private final AnnotationAttributes gatewayAttributes;
|
||||
|
||||
public AnnotationGatewayProxyFactoryBean(Class<?> serviceInterface) {
|
||||
super(serviceInterface);
|
||||
this.gatewayAttributes = AnnotatedElementUtils.getMergedAnnotationAttributes(serviceInterface,
|
||||
MessagingGateway.class.getName(), false, true);
|
||||
Assert.notNull(this.gatewayAttributes,
|
||||
"The Messaging Gateway interface must be annotated with the @MessagingGateway");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) getBeanFactory();
|
||||
|
||||
String defaultPayloadExpression =
|
||||
beanFactory.resolveEmbeddedValue(
|
||||
this.gatewayAttributes.getString("defaultPayloadExpression"));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object>[] defaultHeaders = (Map<String, Object>[]) this.gatewayAttributes.get("defaultHeaders");
|
||||
|
||||
String mapper = beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("mapper"));
|
||||
|
||||
boolean hasMapper = StringUtils.hasText(mapper);
|
||||
boolean hasDefaultPayloadExpression = StringUtils.hasText(defaultPayloadExpression);
|
||||
Assert.state(!hasMapper || !hasDefaultPayloadExpression,
|
||||
"'defaultPayloadExpression' is not allowed when a 'mapper' is provided");
|
||||
|
||||
boolean hasDefaultHeaders = !ObjectUtils.isEmpty(defaultHeaders);
|
||||
Assert.state(!hasMapper || !hasDefaultHeaders,
|
||||
"'defaultHeaders' are not allowed when a 'mapper' is provided");
|
||||
|
||||
|
||||
String defaultRequestChannel =
|
||||
beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("defaultRequestChannel"));
|
||||
setDefaultRequestChannelName(defaultRequestChannel);
|
||||
|
||||
String defaultReplyChannel =
|
||||
beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("defaultReplyChannel"));
|
||||
setDefaultReplyChannelName(defaultReplyChannel);
|
||||
|
||||
String errorChannel = beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("errorChannel"));
|
||||
setErrorChannelName(errorChannel);
|
||||
|
||||
String asyncExecutor = beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("asyncExecutor"));
|
||||
if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) {
|
||||
setAsyncExecutor(null);
|
||||
}
|
||||
else if (StringUtils.hasText(asyncExecutor)) {
|
||||
setAsyncExecutor(beanFactory.getBean(asyncExecutor, Executor.class));
|
||||
}
|
||||
|
||||
if (hasDefaultHeaders || hasDefaultPayloadExpression) {
|
||||
GatewayMethodMetadata gatewayMethodMetadata = new GatewayMethodMetadata();
|
||||
|
||||
if (hasDefaultPayloadExpression) {
|
||||
gatewayMethodMetadata.setPayloadExpression(defaultPayloadExpression);
|
||||
}
|
||||
|
||||
Map<String, Expression> headerExpressions = new HashMap<>();
|
||||
for (Map<String, Object> header : defaultHeaders) {
|
||||
String headerValue = beanFactory.resolveEmbeddedValue((String) header.get("value"));
|
||||
boolean hasValue = StringUtils.hasText(headerValue);
|
||||
|
||||
String headerExpression = beanFactory.resolveEmbeddedValue((String) header.get("expression"));
|
||||
|
||||
Assert.state(!(hasValue == StringUtils.hasText(headerExpression)),
|
||||
"exactly one of 'value' or 'expression' is required on a gateway's header.");
|
||||
|
||||
Expression expression = hasValue ?
|
||||
new LiteralExpression(headerValue) :
|
||||
EXPRESSION_PARSER.parseExpression(headerExpression);
|
||||
|
||||
String headerName = beanFactory.resolveEmbeddedValue((String) header.get("name"));
|
||||
headerExpressions.put(headerName, expression);
|
||||
}
|
||||
|
||||
gatewayMethodMetadata.setHeaderExpressions(headerExpressions);
|
||||
|
||||
setGlobalMethodMetadata(gatewayMethodMetadata);
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(mapper)) {
|
||||
setMapper(beanFactory.getBean(mapper, MethodArgsMessageMapper.class));
|
||||
}
|
||||
|
||||
String defaultRequestTimeout =
|
||||
beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("defaultRequestTimeout"));
|
||||
setDefaultRequestTimeout(Long.parseLong(defaultRequestTimeout));
|
||||
|
||||
String defaultReplyTimeout =
|
||||
beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("defaultReplyTimeout"));
|
||||
setDefaultReplyTimeout(Long.parseLong(defaultReplyTimeout));
|
||||
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.gateway;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
|
||||
|
||||
/**
|
||||
* A gateway proxy factory that can handle JDK8 {@link CompletableFuture}s. If a
|
||||
* gateway method returns {@link CompletableFuture} exactly, one will be returned and the
|
||||
* results of the
|
||||
* {@link CompletableFuture#supplyAsync(Supplier, java.util.concurrent.Executor)} method
|
||||
* call will be returned.
|
||||
* <p>
|
||||
* If you wish your integration flow to return a {@link CompletableFuture} to the gateway
|
||||
* in a reply message, the async executor must be set to {@code null}.
|
||||
* <p>
|
||||
* If the return type is a subclass of {@link CompletableFuture},
|
||||
* it must be returned by the integration flow and the async executor (if present) is not
|
||||
* used.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 4.2
|
||||
*
|
||||
*/
|
||||
public class GatewayCompletableFutureProxyFactoryBean extends GatewayProxyFactoryBean {
|
||||
|
||||
public GatewayCompletableFutureProxyFactoryBean() {
|
||||
super();
|
||||
}
|
||||
|
||||
public GatewayCompletableFutureProxyFactoryBean(Class<?> serviceInterface) {
|
||||
super(serviceInterface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
final Class<?> returnType = invocation.getMethod().getReturnType();
|
||||
if (CompletableFuture.class.equals(returnType)) { // exact
|
||||
AsyncTaskExecutor asyncExecutor = getAsyncExecutor();
|
||||
if (asyncExecutor != null) {
|
||||
return CompletableFuture.supplyAsync(new Invoker(invocation), asyncExecutor);
|
||||
}
|
||||
}
|
||||
return super.invoke(invocation);
|
||||
}
|
||||
|
||||
private final class Invoker implements Supplier<Object> {
|
||||
|
||||
private final MethodInvocation invocation;
|
||||
|
||||
Invoker(MethodInvocation methodInvocation) {
|
||||
this.invocation = methodInvocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get() {
|
||||
try {
|
||||
return doInvoke(this.invocation, false);
|
||||
}
|
||||
catch (Error e) { //NOSONAR
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable t) { //NOSONAR
|
||||
if (t instanceof RuntimeException) {
|
||||
throw (RuntimeException) t;
|
||||
}
|
||||
throw new MessagingException("asynchronous gateway invocation failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -24,8 +24,10 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
@@ -111,7 +113,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
|
||||
|
||||
private volatile Object serviceProxy;
|
||||
|
||||
private final Map<Method, MethodInvocationGateway> gatewayMap = new HashMap<Method, MethodInvocationGateway>();
|
||||
private final Map<Method, MethodInvocationGateway> gatewayMap = new HashMap<>();
|
||||
|
||||
private volatile AsyncTaskExecutor asyncExecutor = new SimpleAsyncTaskExecutor();
|
||||
|
||||
@@ -304,7 +306,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
|
||||
* @since 4.3
|
||||
*/
|
||||
public Map<Method, MessagingGatewaySupport> getGateways() {
|
||||
return Collections.<Method, MessagingGatewaySupport>unmodifiableMap(this.gatewayMap);
|
||||
return Collections.unmodifiableMap(this.gatewayMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -323,7 +325,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
|
||||
MethodInvocationGateway gateway = this.createGatewayForMethod(method);
|
||||
this.gatewayMap.put(method, gateway);
|
||||
}
|
||||
this.serviceProxy = new ProxyFactory(proxyInterface, this).getProxy(this.beanClassLoader);
|
||||
this.serviceProxy = new ProxyFactory(proxyInterface, this)
|
||||
.getProxy(this.beanClassLoader);
|
||||
if (this.asyncExecutor != null) {
|
||||
Callable<String> task = () -> null;
|
||||
Future<String> submitType = this.asyncExecutor.submit(task);
|
||||
@@ -368,11 +371,15 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
|
||||
public Object invoke(final MethodInvocation invocation) throws Throwable {
|
||||
final Class<?> returnType = invocation.getMethod().getReturnType();
|
||||
if (this.asyncExecutor != null && !Object.class.equals(returnType)) {
|
||||
Invoker invoker = new Invoker(invocation);
|
||||
if (returnType.isAssignableFrom(this.asyncSubmitType)) {
|
||||
return this.asyncExecutor.submit(new AsyncInvocationTask(invocation));
|
||||
return this.asyncExecutor.submit(invoker::get);
|
||||
}
|
||||
else if (returnType.isAssignableFrom(this.asyncSubmitListenableType)) {
|
||||
return ((AsyncListenableTaskExecutor) this.asyncExecutor).submitListenable(new AsyncInvocationTask(invocation));
|
||||
return ((AsyncListenableTaskExecutor) this.asyncExecutor).submitListenable(invoker::get);
|
||||
}
|
||||
else if (CompletableFuture.class.equals(returnType)) { // exact
|
||||
return CompletableFuture.supplyAsync(invoker, this.asyncExecutor);
|
||||
}
|
||||
else if (Future.class.isAssignableFrom(returnType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
@@ -383,7 +390,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
|
||||
}
|
||||
}
|
||||
if (Mono.class.isAssignableFrom(returnType)) {
|
||||
return Mono.fromCallable(new AsyncInvocationTask(invocation));
|
||||
return Mono.fromSupplier(new Invoker(invocation));
|
||||
}
|
||||
return this.doInvoke(invocation, true);
|
||||
}
|
||||
@@ -661,17 +668,16 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
|
||||
|
||||
}
|
||||
|
||||
|
||||
private final class AsyncInvocationTask implements Callable<Object> {
|
||||
private final class Invoker implements Supplier<Object> {
|
||||
|
||||
private final MethodInvocation invocation;
|
||||
|
||||
AsyncInvocationTask(MethodInvocation invocation) {
|
||||
this.invocation = invocation;
|
||||
Invoker(MethodInvocation methodInvocation) {
|
||||
this.invocation = methodInvocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
public Object get() {
|
||||
try {
|
||||
return doInvoke(this.invocation, false);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -169,7 +169,7 @@ public class GatewayParserTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPromiseGateway() throws Exception {
|
||||
public void testMonoGateway() throws Exception {
|
||||
PollableChannel requestChannel = context.getBean("requestChannel", PollableChannel.class);
|
||||
MessageChannel replyChannel = context.getBean("replyChannel", MessageChannel.class);
|
||||
this.startResponder(requestChannel, replyChannel);
|
||||
@@ -280,7 +280,7 @@ public class GatewayParserTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncCompletableMessge() throws Exception {
|
||||
public void testAsyncCompletableMessage() throws Exception {
|
||||
QueueChannel requestChannel = (QueueChannel) context.getBean("requestChannel");
|
||||
final AtomicReference<Thread> thread = new AtomicReference<>();
|
||||
requestChannel.addInterceptor(new ChannelInterceptorAdapter() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
* Copyright 2016-2017 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.
|
||||
@@ -46,7 +46,6 @@ import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.MessageDispatchingException;
|
||||
import org.springframework.integration.MessageRejectedException;
|
||||
import org.springframework.integration.annotation.IntegrationComponentScan;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.MessagingGateway;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
@@ -82,7 +81,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Service;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
@@ -91,8 +90,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@RunWith(SpringRunner.class)
|
||||
@DirtiesContext
|
||||
public class IntegrationFlowTests {
|
||||
|
||||
@@ -418,7 +416,7 @@ public class IntegrationFlowTests {
|
||||
}
|
||||
|
||||
|
||||
@MessagingGateway(defaultRequestChannel = "controlBus")
|
||||
@MessagingGateway
|
||||
public interface ControlBusGateway {
|
||||
|
||||
void send(String command);
|
||||
@@ -426,12 +424,13 @@ public class IntegrationFlowTests {
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
@IntegrationComponentScan
|
||||
public static class ContextConfiguration {
|
||||
|
||||
@Bean
|
||||
public IntegrationFlow controlBusFlow() {
|
||||
return IntegrationFlows.from("controlBus").controlBus().get();
|
||||
return IntegrationFlows.from(ControlBusGateway.class)
|
||||
.controlBus()
|
||||
.get();
|
||||
}
|
||||
|
||||
@Bean(name = PollerMetadata.DEFAULT_POLLER)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.gateway;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -35,6 +36,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -59,9 +61,11 @@ import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.annotation.AnnotationConstants;
|
||||
import org.springframework.integration.annotation.BridgeTo;
|
||||
import org.springframework.integration.annotation.Gateway;
|
||||
import org.springframework.integration.annotation.GatewayHeader;
|
||||
import org.springframework.integration.annotation.IntegrationComponentScan;
|
||||
import org.springframework.integration.annotation.MessagingGateway;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
@@ -114,6 +118,7 @@ public class GatewayInterfaceTests {
|
||||
@Autowired
|
||||
@Qualifier("&gatewayInterfaceTests$NoExecGateway")
|
||||
private GatewayProxyFactoryBean noExecGatewayFB;
|
||||
|
||||
@Autowired
|
||||
private SimpleAsyncTaskExecutor exec;
|
||||
|
||||
@@ -123,6 +128,18 @@ public class GatewayInterfaceTests {
|
||||
@Autowired(required = false)
|
||||
private NotAGatewayByScanFilter notAGatewayByScanFilter;
|
||||
|
||||
@Autowired(required = false)
|
||||
private GatewayByAnnotationGPFB gatewayByAnnotationGPFB;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("&annotationGatewayProxyFactoryBean")
|
||||
private GatewayProxyFactoryBean annotationGatewayProxyFactoryBean;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel gatewayChannel;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel errorChannel;
|
||||
|
||||
@Test
|
||||
public void testWithServiceSuperclassAnnotatedMethod() throws Exception {
|
||||
@@ -403,6 +420,40 @@ public class GatewayInterfaceTests {
|
||||
assertEquals("foo", this.autoCreateChannelService.service("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void testAnnotationGatewayProxyFactoryBean() {
|
||||
assertNotNull(this.gatewayByAnnotationGPFB);
|
||||
|
||||
assertSame(this.exec, this.annotationGatewayProxyFactoryBean.getAsyncExecutor());
|
||||
assertEquals(1111L,
|
||||
TestUtils.getPropertyValue(this.annotationGatewayProxyFactoryBean, "defaultRequestTimeout"));
|
||||
assertEquals(222L,
|
||||
TestUtils.getPropertyValue(this.annotationGatewayProxyFactoryBean, "defaultReplyTimeout"));
|
||||
|
||||
Collection<MessagingGatewaySupport> messagingGateways =
|
||||
this.annotationGatewayProxyFactoryBean.getGateways().values();
|
||||
assertEquals(1, messagingGateways.size());
|
||||
|
||||
MessagingGatewaySupport gateway = messagingGateways.iterator().next();
|
||||
assertSame(this.gatewayChannel, gateway.getRequestChannel());
|
||||
assertSame(this.gatewayChannel, gateway.getReplyChannel());
|
||||
assertSame(this.errorChannel, gateway.getErrorChannel());
|
||||
Object requestMapper = TestUtils.getPropertyValue(gateway, "requestMapper");
|
||||
|
||||
assertEquals("@foo",
|
||||
TestUtils.getPropertyValue(requestMapper, "payloadExpression.expression"));
|
||||
|
||||
Map globalHeaderExpressions = TestUtils.getPropertyValue(requestMapper, "globalHeaderExpressions", Map.class);
|
||||
assertEquals(1, globalHeaderExpressions.size());
|
||||
|
||||
Object barHeaderExpression = globalHeaderExpressions.get("bar");
|
||||
assertNotNull(barHeaderExpression);
|
||||
assertThat(barHeaderExpression, instanceOf(LiteralExpression.class));
|
||||
assertEquals("baz", ((LiteralExpression) barHeaderExpression).getValue());
|
||||
}
|
||||
|
||||
|
||||
public interface Foo {
|
||||
|
||||
@Gateway(requestChannel = "requestChannelFoo")
|
||||
@@ -506,6 +557,11 @@ public class GatewayInterfaceTests {
|
||||
return new BridgeHandler();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public GatewayProxyFactoryBean annotationGatewayProxyFactoryBean() {
|
||||
return new AnnotationGatewayProxyFactoryBean(GatewayByAnnotationGPFB.class);
|
||||
}
|
||||
}
|
||||
|
||||
@MessagingGateway
|
||||
@@ -560,6 +616,24 @@ public class GatewayInterfaceTests {
|
||||
|
||||
}
|
||||
|
||||
@MessagingGateway(
|
||||
defaultRequestChannel = "${gateway.channel:gatewayChannel}",
|
||||
defaultReplyChannel = "${gateway.channel:gatewayChannel}",
|
||||
defaultPayloadExpression = "${gateway.payload:@foo}",
|
||||
errorChannel = "${gateway.channel:errorChannel}",
|
||||
asyncExecutor = "${gateway.executor:exec}",
|
||||
defaultRequestTimeout = "${gateway.timeout:1111}",
|
||||
defaultReplyTimeout = "${gateway.timeout:222}",
|
||||
defaultHeaders = {
|
||||
@GatewayHeader(name = "${gateway.header.name:bar}",
|
||||
value = "${gateway.header.value:baz}")
|
||||
})
|
||||
public interface GatewayByAnnotationGPFB {
|
||||
|
||||
String foo(String payload);
|
||||
|
||||
}
|
||||
|
||||
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface TestMessagingGateway {
|
||||
|
||||
Reference in New Issue
Block a user