Here is my code i had a gateway defined in my spring-config.xml like below
<int:gateway id="myGateway"
service-interface="com.sample.si.MyService"
error-channel="myError-channel"
default-request-channel="sendServiceChannel"
>
<int:method name="sendService" request-channel="sendServiceChannel" /
When i debug my test it is directly going to RequesthandlerCircuitBreakerAdvice.class and never going to my error channel, below is my error channel
<int:channel id="myError-channel">
</int:channel>
<int:chain input-channel="myError-channel">
<int:service-activator ref="myErrorHandler" method="resolveError" />
</int:chain>
I want to redirect to error channel when i had an exception from gateway and want to redirect every message to error channel with the circuitbreaker halfopen timelimit. Any help is appreciated
Edit Showing Outbound gateway
<int-ws:outbound-gateway id="sendMyService"
uri="${my.gateway.send.uri}"
mapped-request-headers="*"
marshaller="SOAPMarshaller"
unmarshaller="SOAPMarshaller"
message-sender="soapMessageSender"
request-channel="sendServiceChannel"
fault-message-resolver="faultResolver"
reply-channel="sendServiceResponseChannel">
<int:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.RequestHandlerCircuitBreakerAdvice">
<property name="threshold" value="1" />
<property name="halfOpenAfter" value="10000" />
</bean>
</int:request-handler-advice-chain>
I want the circuit breaker between the and so that i dont want to hit the outbound gateway when the provided uri in the outbound gateway is not available, thanks for looking in to it Gary Russell
Here is my testcase
public class EmailGatewayProducerConsumerTest {
#Inject
#Qualifier("myGateway")
private Myservice myService;
#Test
public void senderTest(){
MyRequest myRequest = new MyRequest(12,"rajendra","rajednra.ch#hotmail.com");
try {
myService.sendService(myRequest);
}catch (Exception e){
//System.out.println("Caught Exception " +e);
}
}
}
The error handler is a sending the exception message to queue and reading it back and sending again to gateway here is my error handler code
public class ErrorHandler{#Inject private JmsTemplate jmsTemplate;
public void resolveError( Message<MessagingException> message) {
try{
MyRequest myRequest=(MessageRequest)message.getPayload().getFailedMessage().getPayload()
jmsTemplate.convertAndSend(DATA_QUEUE, myRequest);
} catch(Exception e){
//log error
}
}
}
Here is my consumer which consumes it and sends back to the gateway
#JmsListener(destination = DATA_QUEUE)public void consume(MyRequest myRequest) throws InterruptedException {
log.info("Receiving event: {}", myRequest);
try {
myService.sendService(myRequest)
}catch (Exception e){
log.error(e.toString());
} }
Exception:
Caused by: org.springframework.ws.client.WebServiceIOException: I/O error: Connect to localhost:10002 timed out; nested exception is org.apache.http.conn.ConnectTimeoutException: Connect to localhost:10002 timed out
at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:561)
at org.springframework.integration.ws.MarshallingWebServiceOutboundGateway.doHandle(MarshallingWebServiceOutboundGateway.java:81)
at org.springframework.integration.ws.AbstractWebServiceOutboundGateway.handleRequestMessage(AbstractWebServiceOutboundGateway.java:188)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler$AdvisedRequestHandler.handleRequestMessage(AbstractReplyProducingMessageHandler.java:144)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice$1.execute(AbstractRequestHandlerAdvice.java:74)
at org.springframework.integration.handler.advice.RequestHandlerCircuitBreakerAdvice.doInvoke(RequestHandlerCircuitBreakerAdvice.java:61)
at org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice.invoke(AbstractRequestHandlerAdvice.java:69)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy102.handleRequestMessage(Unknown Source)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.doInvokeAdvisedRequestHandler(AbstractReplyProducingMessageHandler.java:117)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:102)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
... 90 more
The gateway (and hence error channel) is downstream of your circuit breaker - you need to show what's upstream of (before) the service-activator - that's where you need to configure the error-channel to handle the circuit-breaker result.
That said, you have a circular dependency here - the gateway is sending to the service-activator, which is invoking the gateway again.
You need to explain further what you are trying to do - show more config - upstream and downstream.
EDIT
Bear in mind that will cause an infinite loop because the failed message will be retried indefinitely.
Sending to JMS should not make any different compared to this test case, which works exactly as I would expect (including the infinite loop)...
public interface GW {
void send(String foo);
}
<int:gateway id="gw" service-interface="com.example.GW" default-request-channel="foo"
error-channel="errorChannel"/>
<int:service-activator input-channel="foo" expression = "1 / 0"> <!-- always fail -->
<int:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.RequestHandlerCircuitBreakerAdvice">
<property name="threshold" value="1" />
<property name="halfOpenAfter" value="10000" />
</bean>
</int:request-handler-advice-chain>
</int:service-activator>
<int:bridge input-channel="errorChannel" output-channel="retryChannel" />
<int:channel id="retryChannel">
<int:queue />
</int:channel>
<int:chain input-channel="retryChannel">
<int:transformer expression="payload.failedMessage.payload" />
<int:service-activator ref="gw" />
<int:poller fixed-delay="1000" />
</int:chain>
Result:
org.springframework.messaging.MessageHandlingException: Expression evaluation failed: 1 / 0;
...
Caused by: java.lang.ArithmeticException: / by zero
CircuitBreakerOpenException: Circuit Breaker is Open for ServiceActivator for [ExpressionEvaluatingMessageProcessor for: [1 / 0]]
CircuitBreakerOpenException: Circuit Breaker is Open for ServiceActivator for [ExpressionEvaluatingMessageProcessor for: [1 / 0]]
CircuitBreakerOpenException: Circuit Breaker is Open for ServiceActivator for [ExpressionEvaluatingMessageProcessor for: [1 / 0]]
Above is my edited configuration that worked for me to implement the circuit breaker, after all the changes I still had this issue with halfOpenAfter time, which I increased to 60000 and it worked the above solution in the question should work for circuit breaker, follow the comment of me and Gary Russell.
Related
I am trying this use case:
Poll for a message in a queue->transform the message->Call a method with the transformed message.
Here is my code
<jms:message-driven-channel-adapter id="jmsIn"
destination-name="test"
channel="jmsInChannel"/>
<channel id="jmsInChannel"/>
<channel id="consoleOut"/>
<int:transformer input-channel="jmsInChannel" ref="xmlMsgToVORPojoTransformer" output-channel="consoleOut">
</int:transformer>
<beans:bean id="xmlMsgToVORPojoTransformer" class="com.order.jmspublisher.ValidateOrderMessageTransformer">
<beans:property name="unmarshaller" ref="marshaller" />
</beans:bean>
<beans:bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<beans:property name="classesToBeBound">
<beans:list>
<beans:value>com.order.jmspublisher.ValidateOrderResponseType</beans:value>
</beans:list>
</beans:property>
</beans:bean>
<logging-channel-adapter id="consoleOutloggerChannel" channel="consoleOut" log-full-message="true" level="DEBUG"/>
<int:service-activator id="sa" input-channel="consoleOut" output-channel="someChannel" method="handleVOR">
<beans:bean id="vorActivator" class="com.order.jmspublisher.VORServiceActivator"/>
</int:service-activator>
My Transformer code is as follows:
#SuppressWarnings("rawtypes")
public Message transform(String message)
{
try {
XMLInputFactory xif = XMLInputFactory.newFactory();
XMLStreamReader xsr = xif.createXMLStreamReader(new StreamSource(new StringReader(message)));
xsr.nextTag(); // Advance to Envelope tag
while(xsr.hasNext() ) {
int next = xsr.next();
if(next == XMLStreamReader.START_ELEMENT)
if(xsr.getLocalName().equals("ValidateOrderResponse"))
{
break;
}
}
ValidateOrderResponseType vor = (ValidateOrderResponseType)marshaller.unmarshal(new StAXSource(xsr));
return MessageBuilder.withPayload(vor).build();
} catch (XmlMappingException e) {
return MessageBuilder.withPayload(e).build();
} catch(Exception e){
return MessageBuilder.withPayload(e).build();
}
}
========================================================================
My service activator method code is as follows:
public class VORServiceActivator {
private static final Logger logger = LoggerFactory.getLogger(VORServiceActivator.class);
#ServiceActivator
public String handleVOR(ValidateOrderResponseType vor)
{
logger.info("vor information received and invoke some services here....\r\n"+vor);
return vor.toString();
}
}
=========================================================================
My service activator method is not getting called. But my transform is getting called and I can see the same in logs.
Please help me to know where I am going wrong.
Thanks in advance.
<channel id="consoleOut"/> is a DirectChannel; you have 2 consumers subscribed to the channel (logging adapter and service activator).
The default dispatcher will distribute messages to these competing consumers in round-robin fashion; so each will get alternate messages.
You need to change consoleOut to a <publish-subscribe-channel /> if you want both consumers to get all messages.
You can read about channel types here.
My poller fetches data from DB and pass it to a service activator.
If any exception happens in the service activator method,i should roll back the data fetched to its previous state and should again send the same data to the service activater only for a specific retry-count(say 3). **Is it possible to do this in a xml configuration. ? For details i will share the poller configurations and the service activator.
poller.xml
<int-jdbc:inbound-channel-adapter id="datachannel"
query="select loyalty_id,process_id,mobile_uid from TBL_RECEIPT where r_cre_time
=(select min(r_cre_time) from TBL_RECEIPT where receipt_status=0)"
data-source="dataSource" max-rows-per-poll="1"
update="update TBL_RECEIPT set receipt_status=11 where process_id in (:process_id)">
<int:poller fixed-rate="5000">
<int:transactional/>
</int:poller>
</int-jdbc:inbound-channel-adapter>
<int:channel id="errors">
<int:queue/>
</int:channel>
<bean id="poller" class="main.java.com.as.poller.PollerService" />
<int:channel id="executerchannel">
<int:dispatcher task-executor="taskExecutor" />
</int:channel>
<task:executor id="taskExecutor" pool-size="2" />
<int:service-activator input-channel="datachannel"
output-channel="executerchannel" ref="poller" method="getRecordFromPoller">
<int:request-handler-advice-chain>
<int:retry-advice recovery-channel="errors" />
</int:request-handler-advice-chain>
</int:service-activator>
<int:service-activator input-channel="executerchannel"
ref="poller" method="getDataFromExecuterChannel">
</int:service-activator>
service activator method
#SuppressWarnings({ "unchecked", "rawtypes" })
#ServiceActivator
public void processMessage(Message message) throws IOException {
int capLimit = Integer.parseInt(env.getProperty("capping_limit"));
List<Map<String, Object>> rows = (List<Map<String, Object>>) message
.getPayload();
for (Map<String, Object> row : rows) {
String loyaltyId = (String) row.get("loyalty_id");
String processId = (String) row.get("process_id");
String xid=(String)row.get("mobile_uid");
i have heard about int:transactional being used in poller configuration.But when i added it,its taking the same record even after successful transaction.(means its getting rolled back everytime).
Can anyone please help me on this ?
You can add a retry request-handler-advice to the service activator.
To make retry stateful (throw the exception so the transaction will rollback) you need to provide a RetryStateGenerator. Otherwise the thread will simply be suspended during the retries.
Regardless of using stateful or stateless retry, you really should use a transactional poller so that the update is only applied after success.
means its getting rolled back everytime
The transactional poller will only rollback if an exception is thrown. So if you're seeing the same row, something must be failing.
Turn on DEBUG logging for all of org.springframework to follow the message flow and transaction activity.
I am trying to unit test an xpath router, but having problems with it. here is my context file:
<int:channel id="toTransactionTypeRouterChannel" />
<int-xml:xpath-router id="transactionsTypeRouter"
input-channel="toTransactionTypeRouterChannel" resolution-required="false"
evaluate-as-string="true" default-output-channel="errorChannel">
<!-- Select node name of the first child -->
<int-xml:xpath-expression
expression="name(/soapNs:Envelope/soapNs:Body/schNs:processArchiveRequest/schNs:fulfillmentRequest/schNs:requestDetail/*[1])"
namespace-map="archiveNamespaceMap" />
<int-xml:mapping value="sch:bulkRequestDetail"
channel="bulkChannel" />
<int-xml:mapping value="sch:transactionalRequestDetail"
channel="transactionChannel" />
</int-xml:xpath-router>
<int:channel id="bulkChannel" />
<int:channel id="transactionChannel" />
<int:transformer input-channel="bulkChannel"
output-channel="consoleOut" expression="'Bulk channel has received the payload' " />
<int:transformer input-channel="transactionChannel"
output-channel="consoleOut" expression="'Transaction channel has received payload' " />
<int:transformer input-channel="errorChannel"
output-channel="consoleOut" expression="'Error channel has received payload' " />
As you can see here, there are 2 different routes(bulk,trans.) + error channel.Here is my unit test case for trans channel route:
#Test
public void testTransactionFlow() throws Exception {
try {
Resource bulkRequest = new FileSystemResource("src/main/resources/mock-message-examples/SampleProcessArchiveTransRequest.xml");
String transRequestStr= extractResouceAsString(bulkRequest);
toTransactionTypeRouterChannel.send(MessageBuilder.withPayload(transRequestStr).build());
Message<?> outMessage = testChannel.receive(0);
assertNotNull(outMessage);
context file for junit
<int:bridge input-channel="transactionChannel"
output-channel="testChannel"/>
<int:channel id="testChannel">
<int:queue/>
</int:channel>
As you can see, in the junit context file, I am connecting transactional channel to the test channel.in the junit test case, I am sending a payload to the router in the junit method, and trying to receive it from the input channel and use it for assertion. however the assertion fails, as the message from transaction channel directly goes to consoleOut before getting routed to inputChannel as given in the junit coontext file. How do I intercept the message before it goes to consoleOut? I also tried adding wireTap interceptors but they didnt work:
WireTap wireTap = new WireTap(someChannel);
boolean w = wireTap.isRunning();
transactionChannel.addInterceptor(wireTap);
Basically, I need a separate flow for unit testing.
With that configuration, you are just adding a second consumer to transactionChannel - messages will be round-robin distributed to the transformer and bridge.
You can unsubscribe the transformer for your test case by autowiring it by id as an EventDrivenConsumer and stop() it before sending your message.
I am using Spring Integration to send notifications and as an error test case, I am sending in malformed JSON (a Map) and am getting MessagingException which seems to just go on and on.. not stopping.. I have to kill the Application.
So want to know how to capture this, may be via errorChannel. Code examples would be helpful.
My Spring Integration config:
<!-- channel to connect to disruption exchange -->
<int-amqp:publish-subscribe-channel id="inputChannel"
connection-factory="connectionFactory"
exchange="notification.exchange"/>
<int:json-to-object-transformer input-channel="inputChannel"
output-channel="notificationChannel"
type="java.util.Map"/>
<int:channel id="notificationChannel">
<int:interceptors>
<int:wire-tap channel="loggingChannel"/>
</int:interceptors>
</int:channel>
<int:logging-channel-adapter id="loggingChannel" log-full-message="true" logger-name="tapInbound" level="INFO"/>
<!-- depending on the deviceType route to either apnsChannel or gcmChannel -->
<int:router ref="notificationTypeRouter" input-channel="notificationChannel"/>
<!-- apple push notification channel-->
<int:channel id="apnsChannel"/>
<!-- service activator to process disruptionNotificationChannel -->
<int:service-activator input-channel="apnsChannel" ref="apnsPushNotificationService" method="pushNotification"/>
<!-- google cloud messaging notification channel-->
<int:channel id="gcmChannel"/>
<!-- service activator to process disruptionNotificationChannel -->
<int:service-activator input-channel="gcmChannel" ref="gcmPushNotificationService" method="pushNotification"/>
<!-- error channel to may be log to file or email or store to db in the future -->
<int:channel id="errorChannel"/>
<int:service-activator input-channel="errorChannel" ref="notificationErrorHandler" method="handleFailedNotification"/>
<!-- Infrastructure -->
<rabbit:connection-factory id="connectionFactory"
host="${spring.rabbitmq.host}"
port="${spring.rabbitmq.port}"
username="${spring.rabbitmq.username}"
password="${spring.rabbitmq.password}"/>
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:fanout-exchange name="notification.exchange"/>
I also have an error handler:
public class NotificationErrorHandler {
private final Logger LOG = LoggerFactory.getLogger(NotificationErrorHandler.class);
public void handleFailedNotification(Message<MessageHandlingException> message) {
Map<String, Object> map = (Map) message.getPayload();
Notification notification = Notification.fromMap(map);
saveToBD(notification);
}
private void saveToBD(Notification notification) {
LOG.error("[Notification-Error-Handler] Couldn't Send Push notification: device='{}', type='{}', pushId='{}', message='{}', uid='{}'",
new Object[]{notification.getDevice(),
notification.getDeviceType(),
notification.getDeviceToken(),
notification.getBody(),
notification.getUid()});
}
}
This is the exception:
Caused by: org.springframework.messaging.MessagingException: Failure occured in AMQP listener while attempting to convert and dispatch Message.; nested exception is org.springframework.integration.transformer.MessageTransformationException: failed to transform message; nested exception is com.fasterxml.jackson.core.JsonParseException: Unexpected character ('}' (code 125)): was expecting double-quote to start field name
at [Source: [B#7a707c2c; line: 7, column: 2]
at org.springframework.integration.amqp.channel.AbstractSubscribableAmqpChannel$DispatchingMessageListener.onMessage(AbstractSubscribableAmqpChannel.java:202)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:799)
... 10 common frames omitted
Caused by: org.springframework.integration.transformer.MessageTransformationException: failed to transform message; nested exception is com.fasterxml.jackson.core.JsonParseException: Unexpected character ('}' (code 125)): was expecting double-quote to start field name
at [Source: [B#7a707c2c; line: 7, column: 2]
at org.springframework.integration.transformer.AbstractTransformer.transform(AbstractTransformer.java:44)
at org.springframework.integration.transformer.MessageTransformingHandler.handleRequestMessage(MessageTransformingHandler.java:68)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:99)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
at org.springframework.integration.dispatcher.BroadcastingDispatcher.invokeHandler(BroadcastingDispatcher.java:160)
at org.springframework.integration.dispatcher.BroadcastingDispatcher.dispatch(BroadcastingDispatcher.java:142)
at org.springframework.integration.amqp.channel.AbstractSubscribableAmqpChannel$DispatchingMessageListener.onMessage(AbstractSubscribableAmqpChannel.java:181)
... 11 common frames omitted
Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('}' (code 125)): was expecting double-quote to start field name
at [Source: [B#7a707c2c; line: 7, column: 2]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1419)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:508)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:437)
Hope someone can help.
Thanks in advance
GM
Changes made as per #Gary's answer and its working now:
<!-- Infrastructure -->
<rabbit:connection-factory id="connectionFactory"
host="${spring.rabbitmq.host}"
port="${spring.rabbitmq.port}"
username="${spring.rabbitmq.username}"
password="${spring.rabbitmq.password}"/>
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
<rabbit:direct-exchange name="notification.direct">
<rabbit:bindings>
<rabbit:binding queue="notification.queue" key="notification.queue"/>
</rabbit:bindings>
</rabbit:direct-exchange>
<rabbit:queue id="notification.queue" name="notification.queue"/>
<int-amqp:inbound-channel-adapter channel="inputChannel"
queue-names="notification.queue"
connection-factory="connectionFactory"
error-channel="errorChannel"/>
<int:json-to-object-transformer input-channel="inputChannel"
output-channel="notificationChannel"
type="java.util.Map"/>
<int:channel id="notificationChannel">
<int:interceptors>
<int:wire-tap channel="loggingChannel"/>
</int:interceptors>
</int:channel>
<int:logging-channel-adapter id="loggingChannel" log-full-message="true" logger-name="tapInbound" level="INFO"/>
<!-- depending on the deviceType route to either apnsChannel or gcmChannel -->
<int:router ref="notificationTypeRouter" input-channel="notificationChannel"/>
<!-- apple push notification channel-->
<int:channel id="apnsChannel"/>
<!-- service activator to process disruptionNotificationChannel -->
<int:service-activator input-channel="apnsChannel" ref="apnsPushNotificationService" method="pushNotification"/>
<!-- google cloud messaging notification channel-->
<int:channel id="gcmChannel"/>
<!-- service activator to process disruptionNotificationChannel -->
<int:service-activator input-channel="gcmChannel" ref="gcmPushNotificationService" method="pushNotification"/>
<!-- no op channel where message is logged for unknown devices -->
<int:channel id="noOpChannel"/>
<!-- service activator to process disruptionNotificationChannel -->
<int:service-activator input-channel="noOpChannel" ref="noOpPushNotificationService" method="pushNotification"/>
<!-- error channel to may be log to file or email or store to db in the future -->
<int:channel id="errorChannel"/>
<int:service-activator input-channel="errorChannel" ref="notificationErrorHandler"/>
Why are you starting the flow with a pub-sub channel? It's not normal to use a pub/sub channel for message distribution.
If you can use a message-driven channel adapter instead, you can add an error-channel.
You can't add an error channel to a pub-sub channel. You can, however inject an error-handler (implements org.springframework.util.ErrorHandler) and throw an AmqpRejectAndDontRequeueException when you detect a fatal error.
You can also use a Json MessageConverter in the channel instead of using a Json transformer downstream in the flow; in that case, the default error handler will detect a message conversion exception and reject the message rather than requeueing it.
Version : spring-integration-core - 2.2.3
Here is the simplified version of my splitter/aggregator setup.
<task:executor id="taskExecutor" pool-size="${pool.size}"
queue-capacity="${queue.capacity}"
rejection-policy="CALLER_RUNS" keep-alive="120"/>
<int:channel id="service-requests"/>
<int:channel id="service-request"/>
<int:channel id="channel-1">
<int:dispatcher task-executor="taskExecutor" failover="false"/>
</int:channel>
<int:channel id="channel-2">
<int:dispatcher task-executor="taskExecutor" failover="false"/>
</int:channel>
<int:gateway id="myServiceRequestor" default-reply-timeout="${reply.timeout}"
default-reply-channel="service-aggregated-reply"
default-request-channel="service-request"
service-interface="com.blah.blah.MyServiceRequestor"/>
<int:splitter input-channel="service-request"
ref="serviceSplitter" output-channel="service-requests"/>
<!-- To split the request and return a java.util.Collection of Type1 and Type2 -->
<bean id="serviceSplitter" class="com.blah.blah.ServiceSplitter"/>
<int:payload-type-router input-channel="service-requests" resolution-required="true">
<int:mapping
type="com.blah.blah.Type1"
channel="channel-1"/>
<int:mapping
type="com.blah.blah.Type2"
channel="channel-2"/>
</int:payload-type-router>
<!-- myService is a bean where processType1 & processType2 method is there to process the payload -->
<int:service-activator input-channel="channel-1"
method="processType1" output-channel="service-reply" requires-reply="true"
ref="myService"/>
<int:service-activator input-channel="channel-2"
method="processType2" output-channel="service-reply" requires-reply="true"
ref="myService"/>
<int:publish-subscribe-channel id="service-reply" task-executor="taskExecutor"/>
<!-- myServiceAggregator has a aggregate method which takes a Collection as argument(aggregated response from myService) -->
<int:aggregator input-channel="service-reply"
method="aggregate" ref="myServiceAggregator"
output-channel="service-aggregated-reply"
send-partial-result-on-expiry="false"
message-store="myResultMessageStore"
expire-groups-upon-completion="true"/>
<bean id="myResultMessageStore" class="org.springframework.integration.store.SimpleMessageStore" />
<bean id="myResultMessageStoreReaper" class="org.springframework.integration.store.MessageGroupStoreReaper">
<property name="messageGroupStore" ref="myResultMessageStore" />
<property name="timeout" value="2000" />
</bean>
<task:scheduled-tasks>
<task:scheduled ref="myResultMessageStoreReaper" method="run" fixed-rate="10000" />
</task:scheduled-tasks>
If the processType1/processType2 method in mySevice throws a RuntimeException, then it tries to send the message to an error channel(i believe spring does it by default) and the message payload in error channel stays on in heap and not getting garbage collected.
Updated More Info:
For my comment on error channel. I debugged the code and found that ErrorHandlingTaskExecutor is trying to use a MessagePublishingErrorHandler which inturn sending the message to the channel returned by MessagePublishingErrorHandler.resolveErrorChannel method.
Code snippet from ErrorHandlingTaskExecutor.java
public void execute(final Runnable task) {
this.executor.execute(new Runnable() {
public void run() {
try {
task.run();
}
catch (Throwable t) {
errorHandler.handleError(t); /// This is the part which sends the message in to error channel.
}
}
});
}
Code snipper from MessagePublishingErrorHandler.java
public final void handleError(Throwable t) {
MessageChannel errorChannel = this.resolveErrorChannel(t);
boolean sent = false;
if (errorChannel != null) {
try {
if (this.sendTimeout >= 0) {
sent = errorChannel.send(new ErrorMessage(t), this.sendTimeout);
.....
When i take a heap dump, I always see the reference to the payload message(which i believe is maintained in the above channel) and not getting GC'ed.
Would like to know what is the correct way to handle this case or if i'm missing any in my config?
Also is it possible to tell spring to discard the payload(instead of sending it to error channel) in case of any exception thrown by the service activator method?
Looking forward for your inputs.
Thanks.
You don't have an error-channel defined on your gateway so we won't send it there, we'll just throw an exception to the caller.
However, the partial group is sitting in the aggregator and will never complete. You need to configure a MessageGroupStoreReaper as shown in the reference manual (or set a group-timeout in Spring Integration 4.0.x) to discard the partial group.