Les Les - 2 months ago 24
Groovy Question

In Spring Integration app, Is it possible to test a Spring Retry mechanism outside of chain?

I've inherited a Spring Integration project that incorporates Spring Retry. I'm not sure it has ever been tested out, and there is no separate tests for it. So I'm trying to exercise it with a simple scenario.

By mocking the RestTemplate

exchange
method, I'd like to be able to test the retry logic. I can get the exception I want thrown to occur, but it only happens once - no retry is taking place.

The XML for the retry advice is here(retry-advice-context.xml):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

<bean id="retryAdvice" class="org.springframework.integration.handler.advice.RequestHandlerRetryAdvice" >
<property name="retryTemplate">
<bean class="org.springframework.retry.support.RetryTemplate">
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="${retry.initialInterval}"/>
<property name="maxInterval" value="${retry.maxInterval}"/>
<property name="multiplier" value="${retry.multiplier}"/>
</bean>
</property>

<property name="retryPolicy">
<bean class="com.reachlocal.mediapublishing.shim.integration.retry.CustomRetryPolicy">
<constructor-arg name="maxAttempts" value="${retry.maxAttempts}" />
<constructor-arg name="retryableExceptions" ref="retryableExceptions" />
</bean>
</property>
</bean>
</property>

<property name="recoveryCallback">
<bean class="org.springframework.integration.handler.advice.ErrorMessageSendingRecoverer">
<constructor-arg ref="errorChannel" />
</bean>
</property>
</bean>

<util:map id="retryableExceptions" map-class="java.util.HashMap" >
<entry key="java.net.SocketException" value="true" />
<entry key="com.examplel.ConnectionException" value="true" />
<entry key="com.example.CustomException" value="true" />
</util:map>

</beans>


Here is a chunk of an SI processing file:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">

<import resource="retry-advice-context.xml"/>

<int:channel id="channel1">
</int:channel>

<int:header-value-router id="commandTypeRouter" input-channel="commandChannel" <---DEFINED IN MASTER FILE
header-name="commandType" resolution-required="true">
<int:mapping value="COMMAND_1" channel="channel1"/>
</int:header-value-router>

<int:chain id="command1Chain" input-channel="channel1" output-channel="commandProcessed">
<int:header-enricher>
<int:error-channel ref="errorChannel" />
</int:header-enricher>
<int:service-activator ref="eventDataWriter" method = "addEventStart"/>

<int:service-activator ref="accountProcessor" method="processAccount">
<int:request-handler-advice-chain><ref bean="retryAdvice" /></int:request-handler-advice-chain>
</int:service-activator>
</int:chain>
</beans>


So the retry bean,
retryAdvice
, is a part of the different chains. There is a lot more to the chains, so I only want to be able to check the retry logic from the service layer. There are no Retry annotations anywhere in the code (don't know if they are needed).

A couple of questions:


  1. Can I test the retry feature from the service layer or do I need to execute the entire chain?

  2. Is there anything missing (annotations, other XML) that the retry mechanism requires?



BTW, this is using SI 4.1.3.

Thanks.

UPDATE 1:

Managed to get a Gary's project running in my environment. After that I added in the
retry-advice-context.xml
file into the main SI xml. I changed the Map to only have
RuntimeException
in it. The log statements showed
ExponentialBackoffPolicy
statements. I was also getting the
RetryTemplate
debug log statements.

With some more understanding I translated what was there to the real code I'm working with and had more success. I'm getting the log statement that my exception occurred and will be retried up to 3 times.

Unfortunately what I get is:

17:29:26.154 DEBUG [task-scheduler-2][org.springframework.retry.support.RetryTemplate] Checking for rethrow: count=1
17:29:26.155 DEBUG [task-scheduler-2][org.springframework.retry.support.RetryTemplate] Retry failed last attempt: count=1


So it initially knows that it's supposed to retry up to 3 times. But then it states that it's made its last attempt after 1 retry.

In Gary's working code the debug statements will show
Retry: count=2
etc... for the next successive try.

There is a
sleep
statement in the Spock test code during the time the retries should be taking place. I both lengthened and shortened the time without any change.

Going to continue to try and debug through the retry code to see why it stops on the 1st retry.

Answer

You don't need any additional annotations or XML.

If you give the chain and service id attributes you can test the handler independently...

<int:chain id="myChain" input-channel="foo">
    <int:transformer expression="payload" />
    <int:service-activator id="myService" ref="bar">
        <int:request-handler-advice-chain>
            <int:ref bean="retry" />
        </int:request-handler-advice-chain>
    </int:service-activator>
</int:chain>

<bean id="retry" class="org.springframework.integration.handler.advice.RequestHandlerRetryAdvice" />

<bean id="bar" class="com.example.Bar" />

With Bar being...

public class Bar {

    private int count;

    public void bar(String in) {
        System.out.println(in);
        if (count++ < 2) {
            throw new RuntimeException("foo");
        }
    }

}

Test:

public class So39604931ApplicationTests {

    @Autowired
    @Qualifier("myChain$child.myService.handler")
    public MessageHandler handler;

    @Test
    public void test() {
        handler.handleMessage(new GenericMessage<>("foo"));
    }

}

Result:

foo
foo
foo

You should also turn on debug logging for org.springframework.retry to see the retry behavior.

08:51:16.897 [main] DEBUG o.s.retry.support.RetryTemplate - Retry: count=0
foo
08:51:16.902 [main] DEBUG o.s.retry.support.RetryTemplate - Checking for rethrow: count=1
08:51:16.902 [main] DEBUG o.s.retry.support.RetryTemplate - Retry: count=1
foo
08:51:16.903 [main] DEBUG o.s.retry.support.RetryTemplate - Checking for rethrow: count=2
08:51:16.903 [main] DEBUG o.s.retry.support.RetryTemplate - Retry: count=2
foo
Comments