Spring-Integration:在异常时不发送Tcp服务器响应
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring-Integration:在异常时不发送Tcp服务器响应相关的知识,希望对你有一定的参考价值。
我将旧的tcp服务器代码迁移到spring-boot并添加了spring-integration(基于注释)依赖项来处理tcp套接字连接。
我的入站通道是tcpIn(),出站通道是serviceChannel(),我创建了一个自定义通道[exceptionEventChannel()]来保存异常事件消息。
我有一个自定义序列化器/ Deserialier方法(ByteArrayLengthPrefixSerializer()扩展AbstractPooledBufferByteArraySerializer),以及一个MessageHandler @ServiceActivator方法将响应发送回tcp客户端。
//SpringBoot 2.0.3.RELEASE, Spring Integration 5.0.6.RELEASE
package com.test.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.event.inbound.ApplicationEventListeningMessageProducer;
import org.springframework.integration.ip.IpHeaders;
import org.springframework.integration.ip.tcp.TcpReceivingChannelAdapter;
import org.springframework.integration.ip.tcp.TcpSendingMessageHandler;
import org.springframework.integration.ip.tcp.connection.*;
import org.springframework.integration.ip.tcp.serializer.TcpDeserializationExceptionEvent;
import org.springframework.integration.router.ErrorMessageExceptionTypeRouter;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.MessagingException;
import java.io.IOException;
@Configuration
@IntegrationComponentScan
public class TcpConfiguration {
@SuppressWarnings("unused")
@Value("${tcp.connection.port}")
private int tcpPort;
@Bean
TcpConnectionEventListener customerTcpListener() {
return new TcpConnectionEventListener();
}
@Bean
public MessageChannel tcpIn() {
return new DirectChannel();
}
@Bean
public MessageChannel serviceChannel() {
return new DirectChannel();
}
@ConditionalOnMissingBean(name = "errorChannel")
@Bean
public MessageChannel errorChannel() {
return new DirectChannel();
}
@Bean
public MessageChannel exceptionEventChannel() {
return new DirectChannel();
}
@Bean
public ByteArrayLengthPrefixSerializer byteArrayLengthPrefixSerializer() {
ByteArrayLengthPrefixSerializer byteArrayLengthPrefixSerializer = new ByteArrayLengthPrefixSerializer();
byteArrayLengthPrefixSerializer.setMaxMessageSize(98304); //max allowed size set to 96kb
return byteArrayLengthPrefixSerializer;
}
@Bean
public AbstractServerConnectionFactory tcpNetServerConnectionFactory() {
TcpNetServerConnectionFactory tcpServerCf = new TcpNetServerConnectionFactory(tcpPort);
tcpServerCf.setSerializer(byteArrayLengthPrefixSerializer());
tcpServerCf.setDeserializer(byteArrayLengthPrefixSerializer());
return tcpServerCf;
}
@Bean
public TcpReceivingChannelAdapter tcpReceivingChannelAdapter() {
TcpReceivingChannelAdapter adapter = new TcpReceivingChannelAdapter();
adapter.setConnectionFactory(tcpNetServerConnectionFactory());
adapter.setOutputChannel(tcpIn());
adapter.setErrorChannel(exceptionEventChannel());
return adapter;
}
@ServiceActivator(inputChannel = "exceptionEventChannel", outputChannel = "serviceChannel")
public String handle(Message<MessagingException> msg) {
//String unfilteredMessage = new String(byteMessage, StandardCharsets.US_ASCII);
System.out.println("-----------------EXCEPTION ==> " + msg);
return msg.toString();
}
@Transformer(inputChannel = "errorChannel", outputChannel = "serviceChannel")
public String transformer(String msg) {
//String unfilteredMessage = new String(byteMessage, StandardCharsets.US_ASCII);
System.out.println("-----------------ERROR ==> " + msg);
return msg.toString();
}
@ServiceActivator(inputChannel = "serviceChannel")
@Bean
public TcpSendingMessageHandler out(AbstractServerConnectionFactory cf) {
TcpSendingMessageHandler tcpSendingMessageHandler = new TcpSendingMessageHandler();
tcpSendingMessageHandler.setConnectionFactory(cf);
return tcpSendingMessageHandler;
}
@Bean
public ApplicationListener<TcpDeserializationExceptionEvent> listener() {
return new ApplicationListener<TcpDeserializationExceptionEvent>() {
@Override
public void onApplicationEvent(TcpDeserializationExceptionEvent tcpDeserializationExceptionEvent) {
exceptionEventChannel().send(MessageBuilder.withPayload(tcpDeserializationExceptionEvent.getCause())
.build());
}
};
}
}
tcpIn()中的消息被发送到单独的@Component类中的@ServiceActivator方法,其结构如下:
@Component
public class TcpServiceActivator {
@Autowired
public TcpServiceActivator() {
}
@ServiceActivator(inputChannel = "tcpIn", outputChannel = "serviceChannel")
public String service(byte[] byteMessage) {
// Business Logic returns String Ack Response
}
我没有运行成功方案的问题。我的Tcp TestClient按预期获得Ack响应。
但是,当我尝试模拟异常时,比如Deserializer Exception,异常消息不会作为对Tcp Client的响应发回。我可以看到我的Application Listener获取TcpDeserializationExceptionEvent并将消息发送到exceptionEventChannel。 @ServiceActivator方法句柄(Message msg)也会打印我的异常消息。但它永远不会到达MessageHandler方法out(AbstractServerConnectionFactory cf)中的断点(在调试模式下)。
我很难理解什么是错的。在此先感谢您的帮助。
更新:我注意到在响应可以发送之前,Socket因异常而关闭。我正试图想办法解决这个问题
解决方案更新(2019年3月12日):
由Gary提供,我编辑了我的反序列化器以返回一条消息,该消息可以通过@Router方法跟踪并重定向到errorChannel。监听errorchannel的ServiceActivator然后将所需的错误消息发送到outputChannel。这个解决方案似乎有效。
我的反序列化方法在ByteArrayLengthPrefixSerializer中返回一个“特殊值”作为Gary推荐,而不是原始的inputStream消息。
public byte[] doDeserialize(InputStream inputStream, byte[] buffer) throws IOException {
boolean isValidMessage = false;
try {
int messageLength = this.readPrefix(inputStream);
if (messageLength > 0 && fillUntilMaxDeterminedSize(inputStream, buffer, messageLength)) {
return this.copyToSizedArray(buffer, messageLength);
}
return EventType.MSG_INVALID.getName().getBytes();
} catch (SoftEndOfStreamException eose) {
return EventType.MSG_INVALID.getName().getBytes();
}
}
我还制作了一些新的频道来容纳我的路由器,以便流程如下:
成功流程tcpIn(@Router) - > serviceChannel(包含业务逻辑的@serviceActivator) - > outputChannel(向客户端发送响应的@serviceActivator)
异常流tcpIn(@Router) - > errorChannel(准备错误响应消息的@serviceActivator) - > outputChannel(向客户端发送响应的@serviceActivator)
我的@Router和'errorHandling'@serviceActivator -
@Router(inputChannel = "tcpIn", defaultOutputChannel = "errorChannel")
public String messageRouter(byte[] byteMessage) {
String unfilteredMessage = new String(byteMessage, StandardCharsets.US_ASCII);
System.out.println("------------------> "+unfilteredMessage);
if (Arrays.equals(EventType.MSG_INVALID.getName().getBytes(), byteMessage)) {
return "errorChannel";
}
return "serviceChannel";
}
@ServiceActivator(inputChannel = "errorChannel", outputChannel = "outputChannel")
public String errorHandler(byte[] byteMessage) {
return Message.ACK_RETRY;
}
错误通道用于处理处理消息时发生的异常。在创建消息之前发生反序列化错误(反序列化器解码消息的有效负载)。
反序列化异常是致命的,正如您所观察到的,套接字已关闭。
一种选择是在反序列化器中捕获异常并返回一个“特殊”值,指示发生反序列化异常,然后在主流中检查该值。
以上是关于Spring-Integration:在异常时不发送Tcp服务器响应的主要内容,如果未能解决你的问题,请参考以下文章
Spring-integration / ActiveMQ 在单个线程中订阅多个目的地
了解spring-integration service-activator
在 spring-integration 中使用有效负载类型路由器通过列表通用有效负载路由消息
Spring-Integration:将 DirectChannel 更改为 ExecutorChannel 结果为 ClassCastException