GH-3386: Add method signature for gateway proxy (#3387)
* GH-3386: Add method signature for gateway proxy Fixes https://github.com/spring-projects/spring-integration/issues/3386 All the gateway proxy method invokers are supplied with the same bean name inherited from the proxy. * Add method signature for proxy method bean to fine-grain the management for those bean in the logs, message history and metrics * * Use `Class.getSimpleName()` instead to make gateway names less verbose
This commit is contained in:
@@ -19,6 +19,7 @@ package org.springframework.integration.gateway;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -32,6 +33,7 @@ import java.util.concurrent.Future;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
@@ -103,7 +105,7 @@ import reactor.core.publisher.Mono;
|
||||
*/
|
||||
public class GatewayProxyFactoryBean extends AbstractEndpoint
|
||||
implements TrackableComponent, FactoryBean<Object>, MethodInterceptor, BeanClassLoaderAware,
|
||||
IntegrationManagement {
|
||||
IntegrationManagement {
|
||||
|
||||
private final Object initializationMonitor = new Object();
|
||||
|
||||
@@ -864,7 +866,13 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
|
||||
|
||||
timeouts(requestTimeout, replyTimeout, messageMapper, gateway);
|
||||
|
||||
gateway.setBeanName(getComponentName());
|
||||
String gatewayMethodBeanName =
|
||||
getComponentName() + '#' + method.getName() +
|
||||
'(' + Arrays.stream(method.getParameterTypes())
|
||||
.map(Class::getSimpleName)
|
||||
.collect(Collectors.joining(", ")) + ')';
|
||||
|
||||
gateway.setBeanName(gatewayMethodBeanName);
|
||||
gateway.setBeanFactory(getBeanFactory());
|
||||
gateway.setShouldTrack(this.shouldTrack);
|
||||
gateway.registerMetricsCaptor(this.metricsCaptor);
|
||||
@@ -1059,8 +1067,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
|
||||
public IntegrationPatternType getIntegrationPatternType() {
|
||||
return this.pollable ? IntegrationPatternType.outbound_channel_adapter
|
||||
: this.isVoidReturn
|
||||
? IntegrationPatternType.inbound_channel_adapter
|
||||
: IntegrationPatternType.inbound_gateway;
|
||||
? IntegrationPatternType.inbound_channel_adapter
|
||||
: IntegrationPatternType.inbound_gateway;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -153,12 +153,19 @@ public class GatewayDslTests {
|
||||
@Autowired
|
||||
private RoutingGateway routingGateway;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("&routingGateway")
|
||||
private GatewayProxyFactoryBean routingGatewayProxy;
|
||||
|
||||
@Test
|
||||
void testRoutingGateway() {
|
||||
String result = this.routingGateway.route1("test1");
|
||||
assertThat(result).isEqualTo("route1");
|
||||
result = this.routingGateway.route2("test2");
|
||||
assertThat(result).isEqualTo("route2");
|
||||
MessagingGatewaySupport gatewayMethod = this.routingGatewayProxy.getGateways().values().iterator().next();
|
||||
assertThat(gatewayMethod.getComponentName())
|
||||
.isEqualTo("routingGateway#route1(Object)");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -211,9 +218,9 @@ public class GatewayDslTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IntegrationFlow routingGateway() {
|
||||
public IntegrationFlow routingGatewayFlow() {
|
||||
return IntegrationFlows.from(RoutingGateway.class,
|
||||
(gateway) -> gateway.header("gatewayMethod", MethodArgsHolder::getMethod))
|
||||
(gateway) -> gateway.beanName("routingGateway").header("gatewayMethod", MethodArgsHolder::getMethod))
|
||||
.route(Message.class, (message) ->
|
||||
message.getHeaders().get("gatewayMethod", Method.class).getName(),
|
||||
(router) -> router
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -18,13 +18,11 @@ package org.springframework.integration.handler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -44,7 +42,7 @@ import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.ErrorMessage;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
|
||||
@@ -57,7 +55,7 @@ import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
*
|
||||
* @since 2.0.1
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringJUnitConfig
|
||||
public class ServiceActivatorDefaultFrameworkMethodTests {
|
||||
|
||||
@Autowired
|
||||
@@ -106,7 +104,8 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
|
||||
this.gatewayTestInputChannel.send(message);
|
||||
Message<?> reply = replyChannel.receive(0);
|
||||
assertThat(reply.getHeaders().get("history").toString())
|
||||
.isEqualTo("gatewayTestInputChannel,gatewayTestService,gateway,requestChannel,replyChannel");
|
||||
.isEqualTo("gatewayTestInputChannel,gatewayTestService," +
|
||||
"gateway#exchange(Message),requestChannel,replyChannel");
|
||||
|
||||
Message<?> message2 = MessageBuilder.withPayload("foo").setReplyChannel(replyChannel).build();
|
||||
|
||||
@@ -197,19 +196,11 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
|
||||
|
||||
@Test
|
||||
public void testFailOnDoubleReference() {
|
||||
try {
|
||||
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail-context.xml",
|
||||
this.getClass()).close();
|
||||
fail("Expected exception due to 2 endpoints referencing the same bean");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e).isInstanceOf(BeanCreationException.class);
|
||||
assertThat(e.getCause()).isInstanceOf(BeanCreationException.class);
|
||||
assertThat(e.getCause().getCause()).isInstanceOf(IllegalArgumentException.class);
|
||||
assertThat(e.getCause().getCause().getMessage())
|
||||
.contains("An AbstractMessageProducingMessageHandler may only be referenced once");
|
||||
}
|
||||
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-fail-context.xml", getClass()))
|
||||
.withMessageContaining("An AbstractMessageProducingMessageHandler may only be referenced once")
|
||||
.withRootCauseExactlyInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -228,7 +219,7 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
|
||||
@Test
|
||||
public void testAsyncWithDirectReply() {
|
||||
DirectChannel replyChannel = new DirectChannel();
|
||||
final AtomicReference<Message<?>> reply = new AtomicReference<Message<?>>();
|
||||
final AtomicReference<Message<?>> reply = new AtomicReference<>();
|
||||
replyChannel.subscribe(reply::set);
|
||||
|
||||
Message<?> message = MessageBuilder.withPayload("testing").setReplyChannel(replyChannel).build();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.history;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
@@ -24,8 +25,8 @@ import java.util.Properties;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
@@ -77,7 +78,7 @@ public class MessageHistoryIntegrationTests {
|
||||
.get(MessageHistory.HEADER_NAME, MessageHistory.class).iterator();
|
||||
|
||||
Properties event = historyIterator.next();
|
||||
assertThat(event.getProperty(MessageHistory.NAME_PROPERTY)).isEqualTo("sampleGateway");
|
||||
assertThat(event.getProperty(MessageHistory.NAME_PROPERTY)).isEqualTo("sampleGateway#echo(String)");
|
||||
assertThat(event.getProperty(MessageHistory.TYPE_PROPERTY)).isEqualTo("gateway");
|
||||
|
||||
event = historyIterator.next();
|
||||
@@ -208,7 +209,7 @@ public class MessageHistoryIntegrationTests {
|
||||
.get(MessageHistory.HEADER_NAME, MessageHistory.class).iterator();
|
||||
assertThat(historyIterator.hasNext()).isTrue();
|
||||
Properties gatewayHistory = historyIterator.next();
|
||||
assertThat(gatewayHistory.get("name")).isEqualTo("sampleGateway");
|
||||
assertThat(gatewayHistory.get("name")).isEqualTo("sampleGateway#echo(String)");
|
||||
assertThat(historyIterator.hasNext()).isTrue();
|
||||
Properties chainHistory = historyIterator.next();
|
||||
assertThat(chainHistory.get("name")).isEqualTo("sampleChain");
|
||||
@@ -223,14 +224,16 @@ public class MessageHistoryIntegrationTests {
|
||||
ac.close();
|
||||
}
|
||||
|
||||
@Test(expected = BeanCreationException.class)
|
||||
@Test
|
||||
public void testMessageHistoryMoreThanOneNamespaceFail() {
|
||||
new ClassPathXmlApplicationContext("messageHistoryWithHistoryWriterNamespace-fail.xml",
|
||||
MessageHistoryIntegrationTests.class).close();
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext("messageHistoryWithHistoryWriterNamespace-fail.xml",
|
||||
MessageHistoryIntegrationTests.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
@Disabled
|
||||
public void testMessageHistoryWithHistoryPerformance() {
|
||||
ConfigurableApplicationContext acWithHistory = new ClassPathXmlApplicationContext("perfWithMessageHistory.xml",
|
||||
MessageHistoryIntegrationTests.class);
|
||||
|
||||
@@ -221,15 +221,23 @@ public class MicrometerMetricsTests {
|
||||
this.gates.oneWay("foo");
|
||||
this.gates.twoWay("bar");
|
||||
assertThat(registry.get("spring.integration.send")
|
||||
.tag("name", "gates")
|
||||
.tag("name", "gates#oneWay(String)")
|
||||
.tag("result", "success")
|
||||
.timer().count()).isEqualTo(2);
|
||||
.timer().count()).isEqualTo(1);
|
||||
assertThat(registry.get("spring.integration.send")
|
||||
.tag("name", "gates#twoWay(String)")
|
||||
.tag("result", "success")
|
||||
.timer().count()).isEqualTo(1);
|
||||
this.gatesFlow.oneWay("foo");
|
||||
this.gatesFlow.twoWay("bar");
|
||||
assertThat(registry.get("spring.integration.send")
|
||||
.tag("name", "gatesFlow.gateway")
|
||||
.tag("name", "gatesFlow.gateway#oneWay(String)")
|
||||
.tag("result", "success")
|
||||
.timer().count()).isEqualTo(2);
|
||||
.timer().count()).isEqualTo(1);
|
||||
assertThat(registry.get("spring.integration.send")
|
||||
.tag("name", "gatesFlow.gateway#twoWay(String)")
|
||||
.tag("result", "success")
|
||||
.timer().count()).isEqualTo(1);
|
||||
assertThat(registry.get("spring.integration.send")
|
||||
.tag("name", "customGw")
|
||||
.tag("result", "success")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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,10 +17,9 @@
|
||||
package org.springframework.integration.jmx;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -36,8 +35,7 @@ import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
/**
|
||||
* See INT-1688 for background.
|
||||
@@ -45,10 +43,10 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
*
|
||||
* @since 2.0.1
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
public class ServiceActivatorDefaultFrameworkMethodTests {
|
||||
|
||||
@@ -86,7 +84,8 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
|
||||
this.gatewayTestInputChannel.send(message);
|
||||
Message<?> reply = replyChannel.receive(0);
|
||||
assertThat(reply.getHeaders().get("history").toString())
|
||||
.isEqualTo("gatewayTestInputChannel,gatewayTestService,gateway,requestChannel,bridge,replyChannel");
|
||||
.isEqualTo("gatewayTestInputChannel,gatewayTestService," +
|
||||
"gateway#exchange(Message),requestChannel,bridge,replyChannel");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -170,19 +169,11 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
|
||||
|
||||
@Test
|
||||
public void testFailOnDoubleReference() {
|
||||
try {
|
||||
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail-context.xml",
|
||||
this.getClass()).close();
|
||||
fail("Expected exception due to 2 endpoints referencing the same bean");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e).isInstanceOf(BeanCreationException.class);
|
||||
assertThat(e.getCause()).isInstanceOf(BeanCreationException.class);
|
||||
assertThat(e.getCause().getCause()).isInstanceOf(IllegalArgumentException.class);
|
||||
assertThat(e.getCause().getCause().getMessage())
|
||||
.contains("An AbstractMessageProducingMessageHandler may only be referenced once");
|
||||
}
|
||||
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-fail-context.xml", getClass()))
|
||||
.withMessageContaining("An AbstractMessageProducingMessageHandler may only be referenced once")
|
||||
.withRootCauseExactlyInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
private interface Foo {
|
||||
|
||||
@@ -53,6 +53,10 @@ See <<./zeromq.adoc#zeromq,ZeroMQ Support>> for more information.
|
||||
The one-way messaging gateway (the `void` method return type) now sets a `nullChannel` explicitly into the `replyChannel` header to ignore any possible downstream replies.
|
||||
See <<./gateway.adoc#gateway-default-reply-channel,Setting the Default Reply Channel>> for more information.
|
||||
|
||||
Also the gateway method invokers (`GatewayProxyFactoryBean.MethodInvocationGateway`) are now supplied with the managed bean name as a combination of gateway proxy bean name plus method signature.
|
||||
For example: `sampleGateway#echo(String)`.
|
||||
This effects message history and metrics exposed for the gateway method calls and also give fine-grained logs during start and close of application context.
|
||||
|
||||
The aggregator (and resequencer) can now expire orphaned groups (groups in a persistent store where no new messages arrive after an application restart).
|
||||
See <<./aggregator.adoc#aggregator-expiring-groups, Aggregator Expiring Groups>> for more information.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user