Fun with AOP - reducing redundant code - the ‘for’ loop
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:
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.



















































Um - I’m new to this, so, hey, forgive me if I’m way off base, but it looks to me like you started with a file 38 lines long, and then you wrote 85 lines of code along with 9 lines of additional configuration so that you could end up with a file that is 38 lines long.
Dude.
This is interesting… Should’ve factored out a timing aspect too just for a total before and after
GM>>
You are right for the above *single* example - HelloWorldClient - it may not make sense to refactor the for loop into an aspect.
But the idea of refactoring the for loop (identified by an annotation) into an aspect was so that this aspect (run a method multiple times) could be used by classes other than just HelloWorldClient. So in the future if you need to code for loops which call methods, all you got to do is:
- tag the method with the RunMultiple annotation
- run the code with the javaagent jvm option.
Hope this makes sense.
Thanks,
RAS
The main problem with your code is that the number of loops is set in static time, better loop abstraction could be achieved by using something like Commons Collection Utils (they use anonymous methods and abstract the iteration away), still its a nice intro to AOP.