Creating Annotation-based Concurrency with AspectJ & Maven

For a project at the University we had the task to create Annotations that would remove the concurrency boilerplate Code from Java. The main Idea of the Annotation is to mark some kind of Collection-processing method with @Parallel, so that the method-body will be executed by several Threads, which after completion will be joined and passed back as return value. I thought that this would be the right time to finally take a look into AspectJ.

So at first we create a standard maven project and a basic Annotation:

package de.fabianmaass.abc.annotation;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Parallel {
  int numThreads();
}

Then we create the Aspect (i read that the default maven directory for aspects is /src/main/aspect but somehow i feel that the location doesn’t really matter).

package de.fabianmaass.abc.aspect;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import junit.framework.Assert;
import de.fabianmaass.abc.annotation.Parallel;

public aspect ParallelAnnotationAspect {

  // The Pointcut to select all Methods that are annotated with @Parallel,
  // need a Collection as Parameter and return a Collection
  public pointcut parallelExecution(Object arg, Parallel parallel) : 
    execution(@Parallel Collection *(Collection)) 
                         && args(arg) 
                         && @annotation(parallel);

  // The advice to replace the original behaviour and run the Method with
  // parts of the Collection in several Threads instead
  Object around(Object arg, final Parallel parallel) : 
                                     parallelExecution(arg, parallel) {
    if (arg == null || parallel.numThreads() <= 1)
      return proceed(arg, parallel);
    
    final List<?> originalCollection = (List) arg;
    
    // if there are more Threads than Elements in the Collection specified
    // use the Collection-Size
    final int realNumOfThreads = parallel.numThreads() > originalCollection.size() ?
                                 originalCollection.size() :
                                 parallel.numThreads();
       
    final List<?> processedCollection = new ArrayList();
    
    ExecutorService executor = Executors.newFixedThreadPool(realNumOfThreads);
    
    final int stepSize = (int) Math.ceil(Double.valueOf(originalCollection.size()) / 
                                         Double.valueOf(realNumOfThreads));
    
    for (int i = 0; i<originalCollection.size(); i += stepSize) {
      final int counter = i;
      executor.execute(new Thread() {
        @Override
        public void run() {
          processedCollection.addAll(
              (List) proceed(originalCollection
                         .subList(counter, 
                           (
                              (counter+stepSize) < originalCollection.size()) ?
                               counter + stepSize :
                               originalCollection.size()
                           )
                         , parallel)
          );
        }
      });
    }
       
    executor.shutdown();
    
    while(!executor.isTerminated()) {
        try {
            executor.awaitTermination(60, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    Assert.assertEquals(originalCollection.size(), processedCollection.size());
    
    return processedCollection;
  }
}

Now, if you want to stop right here, you would need to add the AspectJ dependency and the compile-time weaving of the Aspects into your normal Java-Files to your maven pom file.

<build>
  <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>test-compile</goal>
            </goals>

            <configuration>
              <source>1.5</source>
              <target>1.5</target>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>${aspectj.version}</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

But the (imho) more elegant solution is to keep the aspects in a separate maven project and include it in your normal project. So the last thing left to do is to put the just created aspectj-library to your projects dependencies and tell the aspectj-maven plugin to use it as such:

<build>
  <plugins>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>aspectj-maven-plugin</artifactId>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>test-compile</goal>
          </goals>
          <configuration>
            <source>1.5</source>
            <target>1.5</target>
          </configuration>
        </execution>
      </executions>
      <configuration>
        <aspectLibraries>
          <aspectLibrary>
            <groupId>de.fabianmaass</groupId>
            <artifactId>abc</artifactId>
          </aspectLibrary>
        </aspectLibraries>
      </configuration>
    </plugin>
  </plugins>
</build>

<dependencies>
  <dependency>
    <groupId>de.unikonstanz</groupId>
    <artifactId>abc</artifactId>
    <version>1.0-SNAPSHOT</version>
  </dependency>
</dependencies>

Now you’ve got an AspectJ maven library and can annotate your collection-processing methods with @Parallel (woot!) 😉

Personally I think AspectJ is worth the trouble to take a look into – I guess you can do some really powerful stuff with it. Next steps for me are creating a convenience project with common tools, like annotation-driven profiling and logging, just to eliminate the ramp-up obstacle for the next real usage…

Source Code:
Annotation & AspectJ Project
Sample usage


You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.

Comments are closed.