Adding StatisticsListener (using xml configuration)

In this post under Spring Retry, I will show with example how to add Spring Retry’s StatisticsListener class into your application.

StatisticsListener class provided by Spring Retry framework collects statistics of a particular retry operations.

The statistics information involves
1) Abort Count –> How many times the retry was aborted
2) Complete Count –> How many times the retry completed successful
3) Error Count –> How many times exception was thrown
4) Recovery Count –> How many times the application recovered from the exception
5) Started Count –> How many retry attempts were involved including first attempt

For this example we will use two services “Service1” and “Service2” as shown below

Service1


public class Service1 {
    private int i = 0;
    private int j = 0;

    public void executeWithException() {
        i = i + 1;
        System.out.println("Executing method 'executeWithException' : " + i);
        throw new NullPointerException();
    }

    public void executeWithoutException() {
        j = j + 1;
        System.out.println("Executing method 'executeWithoutException' : " + j);
    }

    public String recovery() {
        return "Hello";
    }
}

Service2



public class Service2 {
    private int i = 0;
    private int j = 0;
    private boolean throwNullPointerException = true;
    private boolean throwIllegalArgumentException = true;

    public void executeWithNullPointerException() {
        i = i + 1;
        System.out.println("Executing 'executeWithNullPointerException' : " + i);
        if(throwNullPointerException) {
            throwNullPointerException = false;
            throw new NullPointerException();
        }
    }

    public void executeWithIllegalArgumentException() {
        j = j + 1;
        System.out.println("Executing 'executeWithIllegalArgumentException' : " + j);
        if(throwIllegalArgumentException) {
            throwIllegalArgumentException = false;
            throw new IllegalArgumentException();
        }
    }
}

Next I will show the xml configuration

XML Configuration


1  <?xml version="1.0" encoding="UTF-8"?>
2  <beans xmlns="http://www.springframework.org/schema/beans"
3  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
5      <bean id="statisticsRepository" class="org.springframework.retry.stats.DefaultStatisticsRepository"/>
6      
7      <bean id="statisticsListener" class="org.springframework.retry.stats.StatisticsListener">
8          <constructor-arg index="0" ref="statisticsRepository"/>
9      </bean>
10     
11     <bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
12         <property name="listeners">
13             <list>
14                 <ref bean="statisticsListener"/>
15          </list>
16         </property>
17     </bean>
18     
19     <bean id="service1" class="Service1"/>
20     
21     <bean id="service2" class="Service2"/>
22 </beans>

In the above xml configuration, at line 5 I have created a bean definition for DefaultStatisticsRepository class. This repository holds statistics information in memory.

At line 7 I have created a bean definition for StatisticsListener class and passing a reference to DefaultStatisticsRepository bean as constructor argument.

From line 11 to 17, I have created a bean definition for RetryTemplate class.

At line 14, I am refering the StatisticsListener bean (created at line 7) in the “listeners” list.

During the retry attempts StatisticsListener will listen for abort, complete etc events and populates the DefaultStatisticsRepository instance with appropriate statistics.

Below is the main class

Main Class


1  import java.util.Iterator;
2  
3  import org.springframework.context.ApplicationContext;
4  import org.springframework.context.support.ClassPathXmlApplicationContext;
5  import org.springframework.retry.RetryContext;
6  import org.springframework.retry.RetryStatistics;
7  import org.springframework.retry.stats.StatisticsRepository;
8  import org.springframework.retry.support.RetryTemplate;
9  
10 public class Example3 {
11     public static void main(String[] args) {
12         ApplicationContext context = new ClassPathXmlApplicationContext("Example3.xml");
13         Service1 service1 = (Service1)context.getBean("service1");
14         Service2 service2 = (Service2)context.getBean("service2");
15         RetryTemplate retryTemplate =  (RetryTemplate)context.getBean("retryTemplate");
16         
17         try {
18             retryTemplate.execute(retryContext -> { retryContext.setAttribute(RetryContext.NAME, "Service1"); 
19             service1.executeWithException(); return null; });
20         } catch(NullPointerException excep) {
21             excep.printStackTrace();
22         }
23         
24         try {
25             retryTemplate.execute(retryContext -> { retryContext.setAttribute(RetryContext.NAME, "Service2"); 
26             service2.executeWithNullPointerException(); return null; });
27         } catch(NullPointerException excep) {
28             excep.printStackTrace();
29         }
30         
31         StatisticsRepository statisticsRepository = (StatisticsRepository) context.getBean("statisticsRepository");
32         Iterator<RetryStatistics> retryStatistics = statisticsRepository.findAll().iterator();
33         
34         while(retryStatistics.hasNext()) {
35             RetryStatistics entry = retryStatistics.next();
36             System.out.println(entry);
37         }
38     }
39 }

In the above Example3 main code, at line 18, we passed a lambda statement to RetryTemplate’s execute method.

The lambda function receives an instance of RetryContext as an argument. In this instance we will store the name for this retry attempt. This name will be used to retrieve statistics of this retry attempt. Then we call the service method. For this retry attempt we name it “Service1” since it operates on “Service1” instance.

Same thing we do for second lambda function at line 25 and 26. For this retry attempt we store the name in RetryContext instance as “Service2”, since it will operate on “Service2” instance.

At line 31, we get an instance of StatisticsRepository from Spring application context.

At line 34, we get an iterator instance.

From line 34 to 36, we loop through the list of RetryStatistics and print one by one.

Below is the output

Output

[INFO] ClassPathXmlApplicationContext – Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6576fe71: startup date [Fri Dec 10 19:06:57 IST 2021]; root of context hierarchy
[INFO] XmlBeanDefinitionReader – Loading XML bean definitions from class path resource [Example3.xml]
Executing method ‘executeWithException’ : 1
Executing method ‘executeWithException’ : 2
Executing method ‘executeWithException’ : 3
java.lang.NullPointerException
at Service1.executeWithException(Service1.java:8)
at Example3.lambda$0(Example3.java:19)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:329)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:209)
at Example3.main(Example3.java:18)
Executing ‘executeWithNullPointerException’ : 1
Executing ‘executeWithNullPointerException’ : 2
DefaultRetryStatistics [name=Service2, startedCount=2, completeCount=1, recoveryCount=0, errorCount=1, abortCount=0]
DefaultRetryStatistics [name=Service1, startedCount=3, completeCount=0, recoveryCount=0, errorCount=3, abortCount=1]

Leave a Reply