The gathering of application statistics is a common requirement. It may be desirable to determine how many times a method is executed or how much time is spent in a method. This recipe illustrates collecting both of these types of statistics using a chain of interceptors.
The process for developing and using interceptors for an application's statistics is similar to previous techniques and include:
In this recipe we will create two interceptors. The first will keep track of the number of times a method is executed and the second will keep track of the amount of time spent in a method. We will apply the interceptors against the RegistrationManager's register
method. If we need to keep track of statistics for more than one method, then a more sophisticated version of the statistic class would need to be created.
In both of the interceptors we will be using an instance of the ApplicationStatistics
class to record information about the application. Since only one instance of this class is needed at a time, we will develop it as a singleton. Add the class to the packt
package along with three member variables:
To implement the singleton, add a private default constructor to the class. This means there are no constructors available to create an instance of the class. Add a static getInstance
method that creates a single instance of the class and returns the instance.
public class ApplicationStatistics { private static ApplicationStatistics instance; private static int count; private long totalTime; public static ApplicationStatistics getInstance() { if(instance == null) { instance = new ApplicationStatistics(); } return instance; } ... }
Add four methods to the class:
getCount
Returns the value of countincrement
Increments the countincreaseTotalTime
Adds a time to totalTime
getTotalTime
Returns totalTime
public int getCount() { return count; } public void increment() { this.count++; } public void increaseTotalTime(long time) { totalTime += time; } public long getTotalTime() { return this.totalTime; } }
Next, create two interceptors: HitCounterInterceptor
and TimeInMethodInterceptor
. Both of these interceptors will use the ApplicationStatistics
class.
In the HitCounterInterceptor
class, add an instance variable for ApplicationStatistics
and a method called incrementCounter
. In this method, obtain an instance of the ApplicationStatistics
using its getInstance
method and then invoke its increment
method. Next, call the proceed
method and then return the result of the method.
public class HitCounterInterceptor { ApplicationStatistics applicationStatistics; @AroundInvoke public Object incrementCounter(InvocationContext context) throws Exception { System.out.println("HitCounterInterceptor - Starting"); applicationStatistics = ApplicationStatistics.getInstance(); applicationStatistics.increment();Object result = context.proceed(); System.out.println("HitCounterInterceptor - Terminating"); return result; } }
In the TimeInMethodInterceptor
class, add an instance variable for ApplicationStatistics
and a method called recordTime
. In this method, obtain an instance of the ApplicationStatistics
using its getInstance
method. We will use the System
class' currentTimeMillis
method to get a start and an ending time. The difference between these two times will be used as an argument to the ApplicationStatistics's increaseTotalTime
method. As we did in the HitCounterInterceptor
interceptor, call the proceed
method and then return the result of the method.
public class TimeInMethodInterceptor { ApplicationStatistics applicationStatistics; @AroundInvoke public Object recordTime(InvocationContext context) throws Exception { System.out.println("TimeInMethodInterceptor - Starting"); applicationStatistics = ApplicationStatistics.getInstance(); long startTime = System.currentTimeMillis(); Object result = context.proceed(); long endTime = System.currentTimeMillis();applicationStatistics.increaseTotalTime(endTime-startTime); System.out.println("TimeInMethodInterceptor - Terminating"); return result; } }
In the RegistrationManager
class, add an interceptor annotation to the register
method for the two interceptors.
@Interceptors({HitCounterInterceptor.class, TimeInMethodInterceptor.class})
public Attendee register(String name, String title, String company) {
Modify the RegistrationServlet
to get an instance of the ApplicationStatistics
class and then create an attendee. Display the number of attendees and time spent in the register
method.
...
ApplicationStatistics applicationStatus = ApplicationStatistics.getInstance();
Attendee attendee = registrationManager.register("Bill Schroder", "Manager", "Acme Software");
out.println("<h3>" + attendee.getName() + " has been registered</h3>");
out.println("<h3>Number of attendees: " + applicationStatus.getCount() + "</h3>");
out.println("<h3>Total Time: " + applicationStatus.getTotalTime() + "</h3>");
Execute the servlet. Its output will appear similar to the following screenshot:
The server console should illustrate the execution order of the interceptors:
INFO: HitCounterInterceptor - Starting
INFO: TimeInMethodInterceptor - Starting
INFO: register
INFO: TimeInMethodInterceptor - Terminating
INFO: HitCounterInterceptor - Terminating
Notice the @Interceptors annotation for the register
method included both interceptors. This is an example of interceptor chaining which will be discussed in the next section. Also note, the use of the currentTimeMillis
method may not be accurate enough for some applications.
There are three other topics regarding interceptor chaining we need to address:
The InvocationContext
interface has a getContextData
method that can be used to pass information between interceptors. This can be illustrated through simple modification of HitCounterInterceptor
and TimeInMethodInterceptor
.
A java.util.Map
object is returned from the getContextData
method. We can add data to the map in one interceptor and retrieve it in a later interceptor. In this example, we will pass the count value generated in the HitCounterInterceptor
. While this can be retrieved easily from the ApplicationStatistics
class, using this is a simple way of demonstrating the use of the map.
In the HitCounterInterceptor
add these two lines of code before the invocation of the proceed
method. This retrieves the map and then assigns the count value to the key "count".
Map<String,Object> data = context.getContextData(); data.put("count", applicationStatistics.getCount());
In the TimeInMethodInterceptor,
add this code at the beginning of the recordTime
method. The map is retrieved and the "count" element is returned.
Map<String,Object> data = context.getContextData(); System.out.println("ContextData count: " + data.get("count"));
When the application executes, you will see the value display in the console window.
INFO: HitCounterInterceptor
INFO: TimeInMethodInterceptor
INFO: ContextData count: 2
When multiple interceptors are used, understanding the order of execution of the interceptors can be important. The rules for determining the order of interceptor execution are:
ejb.jar
file, will be executed firstWe can see this in the execution sequence for the interceptors of this recipe. Consider the use of the following interceptors for the RegistrationManager
class:
// DefaultInterceptor declared in the ejb-jar.xml file @Interceptors(SimpleInterceptor.class) public class RegistrationManager { ... @Interceptors({HitCounterInterceptor.class, TimeInMethodInterceptor.class}) public Attendee register(String name, String title, String company) { ... @AroundInvoke public Object internalMethod(InvocationContext context) throws Exception{ ... }
The output sequence illustrates the execution order for the register
method. Comments have been added to clarify the sequence.
INFO: Default Interceptor: Invoking method: register // Default interceptor
INFO: SimpleInterceptor entered: register // Defined at class level
INFO: HitCounterInterceptor // Method level first in list
INFO: TimeInMethodInterceptor // Method level second in list
INFO: internalMethod: Invoking method: register // Class interceptor
INFO: register
...
The use of super classes is not illustrated here; however any interceptors of the EJB super class or an interceptor super class are executed before the derived class interceptors are executed. A super class interceptor for a class level interceptor will be executed before the super class of a method level interceptor.
Sometimes it may be desirable to ignore certain interceptors. There are two annotations whose use will exclude the annotated method from execution:
The @ExcludeDefaultInterceptors annotation is used at the class level and will exclude default interceptors. The @ExcludeClassInterceptors annotation is used at the method level and will exclude annotations declared at the class level.
Use these interceptors with the RegistrationManager
class as outlined below to exclude the use of the DefaultInterceptor
and SimpleInterceptor
.
@Interceptors(SimpleInterceptor.class) @ExcludeDefaultInterceptors public class RegistrationManager { ... @Interceptors({HitCounterInterceptor.class, TimeInMethodInterceptor.class}) @ExcludeClassInterceptors public Attendee register(String name, String title, String company) { ... @AroundInvoke public Object internalMethod(InvocationContext context) throws Exception{ ... }
The output reflects the use of only the HitCounterInterceptor, TimeInMethodInterceptor
and internal interceptors.
INFO: HitCounterInterceptor
INFO: TimeInMethodInterceptor
INFO: internalMethod: Invoking method: register
INFO: register
...
3.144.37.12