Using interceptors to handle application statistics

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.

Getting ready

The process for developing and using interceptors for an application's statistics is similar to previous techniques and include:

  1. Creating a class to maintain the application's statistics
  2. Creating interceptors to support the gathering of the statistics
  3. Using the @Interceptors annotation to designate a target method

    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.

How to do it...

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:

  • instance Used to implement the singleton pattern
  • count Keeps track of how many times a method is executed
  • totalTime Keeps track of the total amount of time spent in a method

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 count
  • increment Increments the count
  • increaseTotalTime 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:

How to do it...

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

How it works...

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's more...

There are three other topics regarding interceptor chaining we need to address:

  • Using the getContextData method
  • Understanding interceptor chaining
  • Excluding interceptors

Using the getContextData method

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

Understanding interceptor chaining

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:

  • The default interceptors, specified in the ejb.jar file, will be executed first
  • Interceptors are executed in the order in which they are declared
  • EJB level interceptors are executed before method level interceptors
  • Interceptors defined within the target class are executed last
  • The interceptors of the super class of an EJB or interceptor classes are executed before the derived classes are.

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

Excluding interceptors

Sometimes it may be desirable to ignore certain interceptors. There are two annotations whose use will exclude the annotated method from execution:

  • @ExcludeDefaultInterceptors
  • @ExcludeClassInterceptors

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

...

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.37.12