A few days ago I was coding a simple load-testing program using ‘for’ loops. Something of the form:

package com.plexibus.samples;

public class HelloWorldClient {

  public static void main(String[] args) {
    HelloWorld hw = new HelloWorld();

		long startTime = System.currentTimeMillis();
    for(int i=0; i<10000; i++) {
      hw.greet();
    }
		long endTime = System.currentTimeMillis();
		System.out.println("Time taken to run greet "
				+ (double) (endTime - startTime) / 1000 + " seconds");

    startTime = System.currentTimeMillis();
    for(int i=0; i<100; i++) {
      hw.greetByName("Big Dog");
    }
		endTime = System.currentTimeMillis();
		System.out.println("Time taken to run greetByName "
				+ (double) (endTime - startTime) / 1000 + " seconds");

  }

}

class HelloWorld {

  public void greet() {
    System.out.println("Hi");
  }

  public void greetByName(final String name) {
    System.out.println("Hello " + name);
  }

}

Taking a step back I realized I could reduce some of these redundant ‘for’ loops by using AOP.
While working on this I decided to throw in some java annotations as well.

Requirement:
Create an aspect that will run certain (marked) methods multiple times.

For this exercise, I am using aspectjweaver-1.6.0.jar

RunMultiple annotation

Let’s start by defining an annotation, RunMultiple. The purpose of this annotation is to mark methods as candidates to be executed multiple times by an aspect (coming soon).
The first meta-annotation, @Retention(RetentionPolicy.RUNTIME), indicates that annotations with this type are to be retained by the VM so they can be read reflectively at run-time.
The second meta-annotation, @Target(ElementType.METHOD), indicates that this annotation type can be used to annotate only method declarations.
The ‘counter’ element allows us to specify the number of times we want a method, marked with this annotation, executed.

package com.plexibus.util.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Marks a method to run multiple times.
 *
 *

Typically used with the RunMultipleAspect aspect.
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RunMultiple {

	/**
	 * Number of times the method should be invoked
	 */
	int counter() default 1;

}

Next, let’s look at the aspect.

RunMultipleAspect

package com.plexibus.aop.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;

import com.plexibus.util.annotation.RunMultiple;

/**
 * The repeat advice reinvokes every method which is tagged with
 * @RunMultiple annotation (at most counter times), so all you need is to use
 * this mechanism to tag the methods that should be reinvoked more than once.
 *
 */
@Aspect
public class RunMultipleAspect {

	private final Log logger = LogFactory.getLog(getClass());

	/**
	 * This advice will run when join points are picked out by the pointcut (methods marked by
	 * RunMultiple annotation)
	 *
	 */
	@Around("call(@com.plexibus.util.annotation.RunMultiple * *.*(..))")
	public Object repeat(ProceedingJoinPoint jp) throws Throwable {
		Method method = ((MethodSignature) jp.getSignature()).getMethod();

		RunMultiple rmAnnotation = method.getAnnotation(RunMultiple.class);

		for (int i = 0; i < rmAnnotation.counter()-1; i++) {

			if (logger.isTraceEnabled()) {
				logger.trace("i: " + i);
			}
			try {
				method.invoke(jp.getTarget(), jp.getArgs());
			} catch (Throwable t) {
				if (logger.isTraceEnabled()) {
				  logger.trace("RunMultipleAspect - t.getMessage(): "
							+ t.getMessage());
			  }
				throw t;
		  }
		}

		return jp.proceed();
	}

}

In the RunMultipleAspect, shown above, I’ve defined an advice (in a method, repeat) and declared that it be run “around” (before and after) any method tagged with the RunMultiple annotation at the time the method is called by a calling program (pointcut).

In the repeat method, we grab the method that was intercepted since this is the method that we need to call multiple times.

		Method method = ((MethodSignature) jp.getSignature()).getMethod();

Next, we get the annotation of type RunMultiple defined on the above method

		RunMultiple rmAnnotation = method.getAnnotation(RunMultiple.class);

And then we call the target method ‘n’ times (where, ‘n’ equals RunMultiple.counter)

		for (int i = 0; i < rmAnnotation.counter()-1; i++) {
		  ...
			method.invoke(jp.getTarget(), jp.getArgs());
			...
		}

Now that we have the aspect ready, let’s refactor the HelloWorldClient.

package com.plexibus.samples;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.plexibus.util.annotation.RunMultiple;

public class HelloWorldClient {

  public static void main(String[] args) {
    HelloWorldClient hwc = new HelloWorldClient();
    HelloWorld hw = new HelloWorld();

		long startTime = System.currentTimeMillis();
    hwc.greet(hw);
		long endTime = System.currentTimeMillis();
		System.out.println("Time taken to run greet "
				+ (double) (endTime - startTime) / 1000 + " seconds");

    startTime = System.currentTimeMillis();
    hwc.greetByName(hw, "Big Dog");
		endTime = System.currentTimeMillis();
		System.out.println("Time taken to run greetByName "
				+ (double) (endTime - startTime) / 1000 + " seconds");

  }

  @RunMultiple(counter=10000)
  public void greet(HelloWorld helloWorld) {
    helloWorld.greet();
  }

  @RunMultiple(counter=100)
  public void greetByName(HelloWorld helloWorld, String name) {
    helloWorld.greetByName(name);
  }

}

In the above code, I’m marking the HelloWorldClient.greet method with the RunMultiple annotation, to be run 10,000 times and the greetByName method to be run 100 times.

aop.xml
We will weave the aspect, RunMultipleAspect, into the HelloWorldClient using Load-Time Weaving i.e. when HelloWorldClient is loaded by the JVM. Load-Time Weaving can be enabled by specifying the -javaagent: /aspectjweaver-.jar option to the jvm.

We will configure load-time weaving with a META-INF/aop.xml which should be placed on the classpath of the executing code. In the aop.xml, we will declare the RunMultiple aspect and declare that all types under the com.plexibus package and it’s sub-packages be woven.

Next, assuming you have compiled the above code samples, run the HelloWorldClient as follows:

java -javaagent:aspectjweaver-1.6.0.jar HelloWorldClient

Summary
Thus, using an aspect (and annotations) I’ve effectively reduced code duplication – in this case, the ‘for’ loop. You could also move the ‘performance capture’ (System.currentTimeMillis()) code into a separate aspect as well.