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:
Artem Bilan
2020-09-18 15:40:32 -04:00
committed by GitHub
parent e223d341f3
commit e714310dae
7 changed files with 74 additions and 62 deletions

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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);

View File

@@ -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")

View File

@@ -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 {

View File

@@ -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.