В приложении Spring Integration можно ли протестировать механизм Spring Retry вне цепочки?

Я унаследовал проект Spring Integration, который включает Spring Retry. Я не уверен, что он когда-либо тестировался, и для него нет отдельных тестов. Итак, я пытаюсь применить простой сценарий.

Насмехаясь над методом RestTemplate exchange, я хотел бы иметь возможность проверить логику повтора. Я могу получить исключение, которое я хочу вызвать, но это происходит только один раз - повторных попыток не происходит.

XML для рекомендации по повторной попытке находится здесь (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>

Вот фрагмент файла обработки SI:

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

Таким образом, bean-компонент повтора, retryAdvice, является частью различных цепочек. Цепочки - это намного больше, поэтому я хочу иметь возможность проверять логику повтора только на уровне сервиса. В коде нет аннотаций Retry (не знаю, нужны ли они).

Пара вопросов:

  1. Могу ли я протестировать функцию повтора на уровне сервиса или мне нужно выполнить всю цепочку?
  2. Что-то отсутствует (аннотации, другой XML), что требуется для механизма повтора?

Кстати, это использует SI 4.1.3.

Спасибо.

ОБНОВЛЕНИЕ 1:

Удалось запустить проект Гэри в моей среде. После этого я добавил retry-advice-context.xml файл в основной SI xml. Я изменил карту, чтобы на ней было только RuntimeException. Операторы журнала показали ExponentialBackoffPolicy операторов. Я также получал RetryTemplate отчеты журнала отладки.

Получив немного больше понимания, я перевел то, что там было, на настоящий код, с которым я работаю, и добился большего успеха. Я получаю сообщение журнала о том, что произошло мое исключение и что будет повторяться до 3 раз.

К сожалению, я получаю следующее:

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

Таким образом, он изначально знает, что он должен повторить попытку до 3 раз. Но затем он сообщает, что сделал последнюю попытку после 1 попытки.

В рабочем коде Гэри операторы отладки будут отображать Retry: count=2 и т. Д. Для следующей попытки.

В тестовом коде Spock есть выражение sleep в то время, когда должны выполняться повторные попытки. Я и увеличил, и сократил время без каких-либо изменений.

Собираюсь продолжить отладку с помощью кода повтора, чтобы понять, почему он останавливается при 1-й попытке.


person Les    schedule 20.09.2016    source источник
comment
Не могли бы вы пояснить в вопросе 1, что вы имеете в виду под уровнем обслуживания?   -  person code4kix    schedule 21.09.2016
comment
Я имею в виду класс обслуживания, который вызывает REST-вызов внешней системы. Запрос, который, возможно, придется повторить. Он вызывает код с использованием Spring RestTemplate.   -  person Les    schedule 21.09.2016


Ответы (1)


Вам не нужны никакие дополнительные аннотации или XML.

Если вы укажете атрибуты chain и service id, вы можете протестировать обработчик независимо ...

<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" />

Когда Bar был ...

public class Bar {

    private int count;

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

}

Тестовое задание:

public class So39604931ApplicationTests {

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

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

}

Результат:

foo
foo
foo

Вам также следует включить ведение журнала отладки для org.springframework.retry, чтобы увидеть поведение повторной попытки.

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
person Gary Russell    schedule 21.09.2016
comment
Пара вопросов: значит, вам все равно нужно тестировать через цепочку, поскольку вы имеете дело с обработчиком сообщений, верно? Это причина GenericMessage. В моем XML для активатора службы используется только ref, так что же происходит с @Qualifier? Я не очень хочу добавлять id атрибутов ко всем своим сервис-активаторам. В принципе, я не знаю, как вы пришли к child.myService. Спасибо. - person Les; 21.09.2016
comment
Цепочка - это цепочка обработчиков сообщений; квалификатор приведет вас к экземпляру hander, который обертывает ваш accountProcessor bean-компонент; так что вы просто тренируете этого индивидуального обработчика. Общая стратегия именования - <chainId>$child.<memberId>.handler. Без id в элементе цепочки мы не регистрируем обработчик (который обертывает ваш bean-компонент) в контексте приложения, поэтому нет возможности подключить его к тесту. Если вы не хотите использовать идентификатор, альтернативой является внедрение bean-компонента theMessageHandlerChain и использование getHandlers(). Но это немного хрупко, потому что это по положению. Он был добавлен в 4.3. - person Gary Russell; 21.09.2016
comment
Могут ли id и ref быть одинаковыми или они должны быть разными? - person Les; 21.09.2016
comment
Это может быть то же самое, потому что в элементе цепочки это не полное имя bean-компонента, это просто часть имени myChain$child.<id>.handler bean-компонента. - person Gary Russell; 21.09.2016
comment
Спасибо. Кстати, означает ли это, что в SI ref имеет другое значение, чем ссылка на экземпляр bean-компонента? - person Les; 21.09.2016
comment
Нет; смысл такой же; ref - это ссылка на компонент с таким именем; как и везде, но есть много тайных вещей. Для bean-компонента, реализующего MessageHandler, ссылка используется напрямую (а затем handler) для других типов, bean-компонент заключен в структуру MessageHandler, которая выполняет любое необходимое преобразование сообщений. Затем обработчик используется внутри цепочки или, когда она находится вне цепочки, оборачивается в потребителя (при этом тип потребителя зависит от входного канала). - person Gary Russell; 22.09.2016
comment
Итак, я использую Groovy / Spock для этого тестирования и настраиваю объекты Mock. В XML-файле, который определяет совет по повторной попытке, есть карта с повторяющимися исключениями (это использует ExponentialBackoffPolicy), но ничего не повторяется. Похоже, что исключение генерируется один раз, но в консоли ничего нет и нет операторов журнала повторной отладки. Карта не используется? - person Les; 24.09.2016
comment
Я не могу предположить, в чем ваша проблема, без того, чтобы вы предоставили простой пример, подобный этому, в моем ответе, который, как и ожидалось, работает нормально. Непонятно, что вы имеете в виду, настраивая объекты Mock в своем тесте - совет применяется во время инициализации контекста, если вам нужна другая конфигурация для тестов, вам нужно переопределить bean-компонент retryAdvice в своем тестовом примере, чтобы он применялся вместо стандартный боб. Хотя я не очень знаком с groovy или spock, я буду рад помочь, если вы предоставите образец проекта. - person Gary Russell; 24.09.2016
comment
У нас есть настраиваемая политика повтора (расширяет SimpleRetryPolicy), которая имеет переопределенное значение registerThrowable, но оно никогда не вызывается. Что могло бы заставить этого не случиться? У Spock есть встроенная функциональность для создания «фиктивного» объекта, используемого тестируемым классом. Итак, я издевался над RestTemplate и вызовом exchange, чтобы выбросить собственное исключение - то, которое предполагается повторить. В моем коде exchange вызывается двумя службами (beans) ниже метода, указанного в цепочке SI, в отличие от вашего примера, который является немедленным. И никаких операторов журнала отладки. - person Les; 26.09.2016
comment
Опять же: I can't speculate what your issue is without you providing a simple example like that in my answer, which works just fine, as expected. Я даже не могу предугадать, в чем ваша проблема, с помощью простого текстового описания. Вам необходимо предоставить пример приложения, которое ясно показывает вашу проблему. В противном случае я перестану отвечать на эти комментарии. - person Gary Russell; 26.09.2016