Skip to main content

Making Lombok, AspectJ and Maven to co-exist

Posted by fabriziogiudici on July 19, 2011 at 9:56 AM PDT

Lombok and AspectJ are two very powerful Java tools, working in a similar way for some respects. In fact, they operate on the bytecode (Lombok is an annotation processor, generating boilerplate code when some annotations are found and aspects are a way to add behaviour to classes). This similarity unfortunately is also the source for a problem. If you configure them with the defaults in a Maven build (for Lombok, just putting the .jar into the classpath, for AspectJ adding the specific aspectj-maven-plugin in the build workflow), you'll experience the loss of all Lombok generated code.

The problem is that iajc, the AspectJ compiler, by default launches javac without enabling Lombok for some reason. So, what's happening is:

  1. javac compiles the code enabling Lombok;
  2. iajc kicks in and recompiles everything, throwing the previously generated bytecode and not enabling Lombok when creating the new bytecode.

Trying to bind the aspectj-maven-plugin to the process-classes phase of Maven (which happens just after compile) doens't change anything, as iajc still overwrites the previously generated bytecode. We have to prevent this in order to solve the problem.

The fun thing is that iajc can work in a different way, by ignoring source files and post-processing the bytecode already generated by javac. Unfortunately, the aspectj-maven-plugin has got a bug for which this approach doesn't work; basically, you can't properly pass the required option (-inpath) to the iajc compiler.

 

 

The workaround is to call iajc "manually", that is not passing through aspectj-maven-plugin. A possible solution is to use the maven-antrun-plugin that makes it possible to embed an Ant run attached to a specific Maven aphase. 

Before getting into the details, let me stress the fact that while this is clearly a workaround, using Ant inside Maven is not necessarily a bad thing. Some people screams about that because this would be a "mixed way to do things". It is not true. I'm not jeopardizing the Maven way of working - it's just business as usual. Ant does only a specific and limited task in a specific phase of the Maven workflow. I'm just using Ant as a scripting language to write a sort of own Maven plugin of mine. I could do this by coding a real Maven plugin in Java code, or by embedding a Groovy script. It's just that I find myself more proficient with Ant scripting rather than with Groovy - and of course, developing and releasing a Java-based plugin would be a waste of time.

Now, the gory details:

<profile>
    <id>aspectj</id>
    <activation>
        <file>
            <exists>src/config/activate-aspectj-profile</exists>
        </file>
    </activation>
    <build>
        <plugins>
        <!-- Will no more needed when http://jira.codehaus.org/browse/MASPECTJ-9 is available -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjrt</artifactId>
                        <version>${tft.aspectjrt.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjtools</artifactId>
                        <version>${tft.aspectjrt.version}</version>
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <id>weave-classes</id>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <target>
                                <taskdef resource="org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties"
                            classpathref="maven.plugin.classpath" />
                                <iajc
                            destDir="${project.build.directory}/classes"
                            inpath="${project.build.directory}/classes"
                            source="${tft.javac.source}"
                            target="${tft.javac.target}"
                            aspectPath="${org.springframework:spring-aspects:jar}"
                            classpathRef="maven.compile.classpath"
                            Xlint="ignore" />
                            </target>
                        </configuration>
                    </execution>
                    <execution>
                        <id>weave-test-classes</id>
                        <phase>process-test-classes</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <target>
                                <taskdef resource="org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties"
                            classpathref="maven.plugin.classpath" />
                                <iajc
                            destDir="${project.build.directory}/test-classes"
                            inpath="${project.build.directory}/test-classes"
                            source="${tft.javac.source}"
                            target="${tft.javac.target}"
                            aspectPath="${org.springframework:spring-aspects:jar}"
                            classpathRef="maven.compile.classpath"
                            Xlint="ignore" />
                            </target>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

From the user's perspective, we're just binding a plugin to a phase (actually two phases: process-classes and process-test-classes, that are invoked just after the compiler has compiled the main code and the test code) and a developer using this configuration, once he has understood what's happening, can fire and forget it.

As you can see, the configuration is wrapped into a profile, which is activated by a file. By creating the file src/config/activate-aspectj-profile in my modules needing AspectJ I'm enabling all the needed stuff. This allows me to put the above POM fragment in a single place, my superPOM, as I described in my previous blog post., avoding to copy-paste the workaround in multiple places. When the aspectj-maven-plugin is fixed I'll just release a new superPOM containing the default AspectJ configuration and my projects won't need any change.

 

I have to thank people in the Lombok mailing list for suggesting how to solve the Lombok+AspectJ incompatibility problem with Ant, from which I just added the Maven integration part.

 

 

Related Topics >>