如何将tcp出站网关的异常传播到上游SI组件

时间:2018-06-12 10:09:07

标签: spring exception-handling spring-integration

我一直在网上搜索,并一直在尝试不同的方法让它发挥作用,但我无法让它发挥作用。

实际上我的系统需要连接到多个tcp服务器端点(我们称之为付款人端点)作为tcp客户端应用程序。客户端组件包括限制和排队组件,它们根据存储在DB中的付款人配置动态初始化为单独的应用程序上下文。我为此目的创建了一个tcp出站网关,并根据

中给出的想法在动态上下文中进行初始化。

https://github.com/spring-projects/spring-integration-samples/tree/master/advanced

我想要的是在发生任何错误的情况下,在调用payersGateway组件时捕获/获取调用代码的异常。但我无法收到异常。

我的tcp客户端连接的上下文文件如下:

dynamic-tcp-gateway-context file

<context:property-placeholder/>

<int-ip:tcp-connection-factory id="client"
                               type="client"
                               host="${host}"
                               port="${port}"
                               single-use="true"
                               so-timeout="${soTimeout}"
                               serializer="byteArrayLfSerializer"
                               deserializer="ediTcpSerializer"
/>

<bean id="ediTcpSerializer" class="com.abc.throttling.EdiTcpSerializer">
    <property name="maxMessageSize" value="20480000"/>
</bean>
<bean id="byteArrayLfSerializer" class="org.springframework.integration.ip.tcp.serializer.ByteArrayLfSerializer">
    <property name="maxMessageSize" value="20480000"/>
</bean>


<int:channel id="toPayerChannel">
    <int:dispatcher task-executor="producerThreadExecutor"/>
</int:channel>
<task:executor id="producerThreadExecutor" pool-size="10" queue-capacity="50" rejection-policy="ABORT"/>


<int:channel id="throttlerChannel">
    <int:priority-queue capacity="${queueSize:1000}"/> <!-- for example queue size, you can increase this capacity
    based on your
    requirement -->
</int:channel>

<int:bridge input-channel="toPayerChannel" output-channel="throttlerChannel" />


 <int-ip:tcp-outbound-gateway id="outGateway"
                             request-channel="throttlerChannel"
                             connection-factory="client"
                             request-timeout="10000"
                             reply-timeout="${replyTimeout}">

    <int:poller id="tcpPoller" error-channel="errorChannel" fixed-rate="1000" max-messages-per-poll="${messageRate:20}"/>

</int-ip:tcp-outbound-gateway>


<int:transformer input-channel="errorChannel"
                 ref="exceptionTransformer" method="createErrorResponse"/>


<bean id="exceptionTransformer" class="com.stella.healthenet.throttling.TcpGatewayExceptionTransformer"/>


<int:object-to-string-transformer input-channel="errorChannel"/>

主要付款人连接的应用程序上下文如下:

main-payers-gateway-context文件

<context:component-scan base-package="com.abc.throttling" />

class="com.abc.throttling.DynamicTcpChannelResolverTest" id="dynamicTcpChannelResolverTest"/>-->

<int:gateway id="payersGateway"
             service-interface="com.abc.gateway.PayersGateway"
             default-request-channel="toDynRouter"
default-request-timeout="1000000"
default-reply-timeout="${payersGateway.defaultReplyTimout}"
/>


<int:converter ref="converter"/>
<bean class="com.abc.manager.edi.core.core4.ByteArrayToStringConverter" id="converter"/>

<int:converter ref="converter2"/>
<bean class="com.abc.manager.edi.core.core2.Core2RealTimeResponseToString" id="converter2"/>

<int:converter ref="converter3"/>
<bean class="com.abc.manager.edi.core.core4.Core4RealTimeResponseToString" id="converter3"/>

<int:channel id="toDynRouter" />

<int:router  resolution-required="true" input-channel="toDynRouter"
            expression="@dynamicPayerChannelResolver.resolve(headers['payer'])"  >
</int:router>

动态频道解析器的代码如下:

@Component("dynamicPayerChannelResolver")
public class DynamicPayerChannelResolver {

@Value("${throttling.payersChannelQueueSize}")
private String payersChannelQueueSize;

@Value("${throttling.tcpSoTimeout}")
private String tcpSoTimeout;

@Value("${tcpOutboundGateway.replyTimeout}")
private String tcpReplyTimeout;

@Autowired
private ApplicationContext applicationContext;



public static final int MAX_CACHE_SIZE = 30;

private static final org.apache.logging.log4j.Logger LOG = LogManager.getLogger();

@Autowired
PayerRepositoryService payerRepositoryService;

private final Map<UUID, MessageChannel> channels = Collections.synchronizedMap(new LinkedHashMap<UUID,
        MessageChannel>(){

    private static final long serialVersionUID = 1L;

            @Override
            protected boolean removeEldestEntry(
                    Entry<UUID, MessageChannel> eldest) {
                //This returning true means the least recently used
                //channel and its application context will be closed and removed
                boolean remove = size() > MAX_CACHE_SIZE;
                if(remove) {
                    MessageChannel channel = eldest.getValue();
                    ConfigurableApplicationContext ctx = contexts.get(channel);
                    if(ctx != null) { //shouldn't be null ideally
                        ctx.close();
                        contexts.remove(channel);
                    }
                }
                return remove;
            }

        });


private final Map<MessageChannel, ConfigurableApplicationContext> contexts =
        new HashMap<MessageChannel, ConfigurableApplicationContext>();



/**
 * Resolve a payer to a channel, where each payer gets a private
 * application context and the channel is the inbound channel to that
 * application context.
 *
 * @param payer
 * @return a channel
 */
public MessageChannel resolve(UUID payer) {
    MessageChannel channel = this.channels.get(payer);
    if (channel == null) {
        channel = createNewPayerChannel(payerRepositoryService.findPayerByPayerId(payer));
    }
    return channel;
}



private synchronized MessageChannel createNewPayerChannel(PayerEntity payer) {
    MessageChannel channel = this.channels.get(payer.getPayerId());
    if (channel == null) {
        ConfigurableApplicationContext ctx;

        if(TransportType.SOAP_WSDL.getValue().equals(payer.getConnTransportType())){
            ctx = new ClassPathXmlApplicationContext(
                    new String[] { "dynamic-soap-gateway-context.xml" },
                    false,applicationContext);
        }
        else{
            ctx = new ClassPathXmlApplicationContext(
                    new String[] { "dynamic-tcp-gateway-context.xml" },
                    false,applicationContext);
        }


        this.setEnvironmentForPayer(ctx, payer);
        ctx.refresh();
        channel = ctx.getBean("toPayerChannel", MessageChannel.class);
        this.channels.put(payer.getPayerId(), channel);
        //Will works as the same reference is presented always
        this.contexts.put(channel, ctx);

    }
    return channel;
}


public void updatePollerConfigs() {

    Iterator it = this.channels.entrySet().iterator();
    while(it.hasNext()){
        Map.Entry pair = (Map.Entry)it.next();
        PayerEntity payer = this.payerRepositoryService.findPayerByPayerId((UUID)pair.getKey());
        final String transportType = payer.getConnTransportType();


        if(TransportType.SOAP_WSDL.getValue().equals(transportType)){

            MessageChannel channel= this.channels.get(payer);
            ConfigurableApplicationContext ctx;
            ctx=this.contexts.get(channel);
            PollingConsumer pc = ctx.getBean("soapPoller",PollingConsumer.class);
            //TODO: this value need to be set from payer configs in db.
            pc.setMaxMessagesPerPoll(10);

        }
        else if (TransportType.X12_SOCKET.getValue().equals(transportType)) {

            MessageChannel channel= this.channels.get(payer);
            ConfigurableApplicationContext ctx;
            ctx=this.contexts.get(channel);
            PollingConsumer pc = ctx.getBean("outGateway",PollingConsumer.class);
            //TODO: this value need to be set from payer configs in db.
            pc.setMaxMessagesPerPoll(10);
        }
    }
}


private void setEnvironmentForPayer(ConfigurableApplicationContext ctx,
                                    PayerEntity payer) {

    final String transportType = payer.getConnTransportType();
    final String endPoint = payer.getConnEndPoint();

    if(payer.getMessageRate()==null){

        throw new NullPointerException("MessageRate value is NULL in DB. Please specify a MessageRate value in " +
                "Payers DB for PayerID: " + payer.getPayerId());
    }


    final String messageRate = payer.getMessageRate();


    StandardEnvironment env = new StandardEnvironment();

    Properties props = new Properties();


    // populate properties for payer
    ;
    if(TransportType.SOAP_WSDL.getValue().equals(transportType)){
        props.setProperty("endpoint", endPoint);
        props.setProperty("messageRate",messageRate);
        props.setProperty("queueSize",payersChannelQueueSize);

    }
    else if (TransportType.X12_SOCKET.getValue().equals(transportType)) {
        if (!endPoint.contains(":")) {

            throw new Exception(ErrorType.UNEXPECTED, "Payer X12 Socket endpoint must include a port.");
        }

        final String ip;
        if (endPoint.contains("/")) {
            ip = endPoint.substring(endPoint.lastIndexOf('/') + 1, endPoint.lastIndexOf(":"));
        } else {
            ip = endPoint.substring(0, endPoint.lastIndexOf(":"));

        }

        final String port = endPoint.substring(endPoint.lastIndexOf(':') + 1);

        props.setProperty("host", ip);
        props.setProperty("port", port);
        props.setProperty("messageRate",messageRate);
        props.setProperty("queueSize",payersChannelQueueSize);
        props.setProperty("soTimeout", tcpSoTimeout);
        props.setProperty("replyTimeout",tcpReplyTimeout);
    }


    PropertiesPropertySource pps = new PropertiesPropertySource("payerChannelProps", props);
    env.getPropertySources().addLast(pps);
    ctx.setEnvironment(env);
}

我想让下游组件生成的异常出现在payersGateway接口发送方法调用中,如下所示:

String response = payersGateway.send(
            MessageBuilder.withPayload(requestMessage)
                    .setHeader("endpointUrl", endPoint)
                    .setHeader("senderId", coreSenderId)
                    .setHeader("receiverId", payerIdCode)
                    .setHeader("username", username)
                    .setHeader("password", password)
                    .setHeader("payloadType", payloadType)
                    .setHeader("nginxUrl", nginxUrl)
                    .setHeader("payer", payerId)
                    .setHeader("transType", transType)
                    .setHeader("priority", 1)
                    .build());

其中payersGateway是指向主上下文文件中的Gateway组件的简单接口。

public interface PayersGateway {
public String send(Message<String> message);

}

请查看生成的异常的堆栈跟踪如下:

 [task-scheduler-1] ERROR org.springframework.integration.ip.tcp.TcpOutboundGateway - Tcp Gateway exception
java.net.ConnectException: Connection refused (Connection refused)
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:589)
    at java.net.Socket.connect(Socket.java:538)
    at java.net.Socket.<init>(Socket.java:434)
    at java.net.Socket.<init>(Socket.java:211)
    at javax.net.DefaultSocketFactory.createSocket(SocketFactory.java:271)
    at org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory.createSocket(TcpNetClientConnectionFactory.java:76)
    at org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory.buildNewConnection(TcpNetClientConnectionFactory.java:49)
    at org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory.obtainNewConnection(AbstractClientConnectionFactory.java:116)
    at org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory.obtainConnection(AbstractClientConnectionFactory.java:79)
    at org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory.getConnection(AbstractClientConnectionFactory.java:69)
    at org.springframework.integration.ip.tcp.TcpOutboundGateway.handleRequestMessage(TcpOutboundGateway.java:130)
    at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:109)
    at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:127)
    at org.springframework.integration.endpoint.PollingConsumer.handleMessage(PollingConsumer.java:129)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint.doPoll(AbstractPollingEndpoint.java:272)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint.access$000(AbstractPollingEndpoint.java:58)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint$1.call(AbstractPollingEndpoint.java:190)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint$1.call(AbstractPollingEndpoint.java:186)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller$1.run(AbstractPollingEndpoint.java:353)
    at org.springframework.integration.util.ErrorHandlingTaskExecutor$1.run(ErrorHandlingTaskExecutor.java:55)
    at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50)
    at org.springframework.integration.util.ErrorHandlingTaskExecutor.execute(ErrorHandlingTaskExecutor.java:51)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller.run(AbstractPollingEndpoint.java:344)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
[task-scheduler-1] ERROR org.springframework.integration.handler.LoggingHandler - org.springframework.messaging.MessagingException: Failed to send or receive; nested exception is java.net.ConnectException: Connection refused (Connection refused)
    at org.springframework.integration.ip.tcp.TcpOutboundGateway.handleRequestMessage(TcpOutboundGateway.java:158)
    at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:109)
    at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:127)
    at org.springframework.integration.endpoint.PollingConsumer.handleMessage(PollingConsumer.java:129)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint.doPoll(AbstractPollingEndpoint.java:272)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint.access$000(AbstractPollingEndpoint.java:58)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint$1.call(AbstractPollingEndpoint.java:190)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint$1.call(AbstractPollingEndpoint.java:186)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller$1.run(AbstractPollingEndpoint.java:353)
    at org.springframework.integration.util.ErrorHandlingTaskExecutor$1.run(ErrorHandlingTaskExecutor.java:55)
    at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50)
    at org.springframework.integration.util.ErrorHandlingTaskExecutor.execute(ErrorHandlingTaskExecutor.java:51)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller.run(AbstractPollingEndpoint.java:344)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.net.ConnectException: Connection refused (Connection refused)
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:589)
    at java.net.Socket.connect(Socket.java:538)
    at java.net.Socket.<init>(Socket.java:434)
    at java.net.Socket.<init>(Socket.java:211)
    at javax.net.DefaultSocketFactory.createSocket(SocketFactory.java:271)
    at org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory.createSocket(TcpNetClientConnectionFactory.java:76)
    at org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory.buildNewConnection(TcpNetClientConnectionFactory.java:49)
    at org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory.obtainNewConnection(AbstractClientConnectionFactory.java:116)
    at org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory.obtainConnection(AbstractClientConnectionFactory.java:79)
    at org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory.getConnection(AbstractClientConnectionFactory.java:69)
    at org.springframework.integration.ip.tcp.TcpOutboundGateway.handleRequestMessage(TcpOutboundGateway.java:130)
    ... 21 more

1 个答案:

答案 0 :(得分:0)

它应该按你的需要工作; throttlerChannel之前的异常将直接抛给调用者;该通道之后的异常将由轮询器的错误处理程序发送到网关。

也许你的网关超时太短了?

启用DEBUG日志记录并跟踪消息流应该可以帮助您进行调试。

修改

您可以通过向Tcp出站网关failedMessage添加此类型的bean来修复缺少的<int-ip:request-handler-advice-chain /> ...

public class FailedMessagePopulator extends AbstractRequestHandlerAdvice {

    @Override
    protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
        try {
            return callback.execute();
        }
        catch (MessagingException e) {
            if (e.getFailedMessage() == null) {
                throw new MessageHandlingException(message, e.getMessage(), e);
            }
            else {
                throw e;
            }
        }
        catch (Exception e) {
            throw new MessageHandlingException(message, e.getMessage(), e);
        }
    }

}

请注意,您需要将错误通道添加到<gateway/>,因为轮询器现在会看到失败的消息并将异常发送回网关以进行错误处理。