AOP and Compile-Time Weaving with Maven, Eclipse, and Spring 2.5

11/8/2010

It's easy to become accustomed to Spring's aspect-oriented magic, particularly dependency injection and transaction management. Should this method execute inside a transaction? Just add the @Transactional annotation and you're done! You need that service there in the code? Just tell Spring to autowire it in with the @Autowire annotation!

The release of Java EE 6 has given the subject extra momentum with its @Inject and @TransactionAttribute annotations, effectively standardizing what was common practice anyway. (Note: These annotations are supported from Spring 3.0, which I'm not using yet because the latest Hibernate release is not fully compatible with it yet.)

Great, isn't it? Well, unless you need those capabilities in objects that are not instantiated through Spring, like entity classes (instantiated by your JPA provider) or other objects instantiated somewhere by a framework you're using. There, you need code weaving to make things work.

Code weaving comes in two flavors:

While at first load-time weaving sounds like a reasonable approach, in reality you will have to replace the class loader by including a special JAR file on the command line or by adding it to your servlet container and changing its class loader configuration. This makes execution environments hard to set up.

There's very good tool support for compile-time weaving available for Maven and Eclipse, and it's not so hard to set up. Once you have your build set up, you no longer need to worry about what is weaved when, things will just work. (With one caveat that I'll discuss later.)

Assuming you already have Maven support installed in Eclipse, this is what you need:

In Eclipse

Install the AspectJ Developer Tools Plugin (AJDT).

In your pom.xml:

Add the Spring-Aspects and AspectJ dependencies:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>2.5.6</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.6.7</version>
</dependency>

Configure the AspectJ plugin:

 <plugin>
     <groupId>org.codehaus.mojo</groupId>
     <artifactId>aspectj-maven-plugin</artifactId>
     <version>1.3</version>
     <configuration>
        <complianceLevel>1.6</complianceLevel>
        <encoding>UTF-8</encoding>
        <aspectLibraries>
           <aspectLibrary>
              <groupId>org.springframework</groupId>
              <artifactId>spring-aspects</artifactId>
           </aspectLibrary>
        </aspectLibraries>
     </configuration>
     <executions>
        <execution>
           <goals>
              <goal>compile</goal>
              <goal>test-compile</goal>
           </goals>
        </execution>
     </executions>
  </plugin>

Also configure the Eclipse plugin to configure the project to use the AJDT compiler:

<plugin>
    <artifactId>maven-eclipse-plugin</artifactId>
    <version>2.8</version>
    <configuration>
       <ajdtVersion>1.5</ajdtVersion>
    </configuration>
</plugin>

In your Spring context configuration:

<context:spring-configured />
<context:annotation-config/>
<tx:annotation-driven transaction-manager="transactionManager"
       mode="aspectj" />

These tags enable the use of, amongst others, the @Autowire, @Configurable, @Transactional and @PersistenceContext annotations in objects that may or not be managed by Spring.

For example, if you need access to a service and the entity manager inside an entity, that could look like this:

@Entity
@Table(name = "formatsettings_append_packet")
@Configurable(autowire = Autowire.BY_TYPE)
public class PacketAppendFormatSettings extends AbstractFormatSettings {

    @Transient
    private transient MyService myService;

    @PersistenceContext
    private transient EntityManager entityManager;

    public void myAction() {
       this.entityManager.find(...);
       this.myService.someMethod(...);
    }

    /*
     * Setters for depenencies
     */
    public void setMyService(MyService myService) {
       this.myService = myService;
    }

    public void setEntityManager(EntityManager entityManager) {
       this.entityManager = entityManager;
    }
}

Generally, AJDT and Maven work together quite well. There are a number of caveats, though:

Because you use Maven to create builds, you will not have any of there problems when you create a JAR or WAR file for deployment.

Comments