Search code examples
javamavenaspectjlombokaspectj-maven-plugin

How to use AspectJ Maven for binary weaving after Javac + Lombok phase


I have a project that uses compiled aspects and weaves them at compile time. I want to add Lombok, but unfortunately AJC is not supported by Lombok. Since this project doesn't have any sources of aspects on its own i configured AspectJ Maven plugin to do post-compile weaving instead, after compiling with Javac+Lombok.

Here is config for AspectJ Maven plugin:

<forceAjcCompile>true</forceAjcCompile>
<sources/>
<weaveDirectory>${project.build.outputDirectory}</weaveDirectory>

It's attached to compile phase right after Maven Compiler plugin compile. That way Lombok + Javac will be invoked first and later AJC will perform weaving on Javac's generated class files.

Is there any limitations/disadvantages when performing bytecode weaving on javac generated classes?

Maybe there is a better approach of making Maven+Lombok+Aspects+Idea work together without issues.

Here is a minimal example project: https://github.com/Psimage/aspectj-and-lombok


Solution

  • When in the other question you asked me in a comment I actually thought that you had problems with your approach, but it is working. The only thing I had to do in order to run the test directly from IDE (IntelliJ IDEA) is to actually delegate application and test runners to Maven because otherwise IDEA does not get Lombok + AspectJ applied at the same time.

    Delegate IDE build/run actions to Maven

    If your approach works, use it. But actually AspectJ Maven suggests another approach: compiling with Maven compiler first to another output directory, then use that directory as weave directory for the AspectJ compiler. The sample POM there does not work 100%, though, because when specifying an output directory for Javac on the command line that directory needs to exist, it will not be created by the compiler. So you need some ugly Antrun action, too:

    <plugins>
    
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <executions>
          <execution>
            <id>unwovenClassesFolder</id>
            <phase>generate-resources</phase>
            <configuration>
              <tasks>
                <delete dir="${project.build.directory}/unwoven-classes"/>
                <mkdir dir="${project.build.directory}/unwoven-classes"/>
              </tasks>
            </configuration>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <executions>
          <execution>
            <!-- Modifying output directory of default compile because non-weaved classes must be stored
                 in separate folder to not confuse ajc by reweaving already woven classes (which leads to
                 to ajc error message like "bad weaverState.Kind: -115") -->
            <id>default-compile</id>
            <configuration>
              <compilerArgs>
                <arg>-d</arg>
                <arg>${project.build.directory}/unwoven-classes</arg>
              </compilerArgs>
            </configuration>
          </execution>
        </executions>
      </plugin>
    
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <configuration>
          <aspectLibraries>
            <aspectLibrary>
              <groupId>me.yarosbug</groupId>
              <artifactId>aspects</artifactId>
            </aspectLibrary>
          </aspectLibraries>
    
          <forceAjcCompile>true</forceAjcCompile>
          <sources/>
          <weaveDirectories>
            <weaveDirectory>${project.build.directory}/unwoven-classes</weaveDirectory>
          </weaveDirectories>
        </configuration>
        <executions>
          <execution>
            <phase>process-classes</phase>
            <goals>
              <goal>compile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.22.2</version>
      </plugin>
    
    </plugins>
    

    I would suggest another approach as an alternative:

    1. Create an unwoven Java module, doing Java + Lombok stuff there.
    2. Create a separate module for AspectJ binary weaving, using the Java module as a weave dependency. Because your unit test depends on both Lombok and AspectJ, put the test in this module.

    The advantage is that you don't need to fiddle around with multiple compilers, execution phases, output directories, Antrun etc.


    Update:

    I cloned your GitHub MCVE and this commit on branch master reflects what I have explained in my sample XML above.

    I also created a branch multi-phase-compilation with another commit which effectively refactors the project according to my alternative idea. I am just quoting the commit message:

    Multi-phase compilation: 1. Java + Lombok, 2. AspectJ binary weaving
    
    There are many changes (sorry, I should have split them into multiple
    commits):
      - Marker annotation renamed to @marker and moved to separate module
        because the main application should not depend on the aspect module.
        Rather both application and aspect now depend on a common module.
      - New module "main-app-aspectj" does only AspectJ binary weaving on
        the already lomboked Java application.
      - Both application modules have slightly different unit tests now: One
        checks that Lombok has been applied and AspectJ has not, the other
        checks that both have been applied.
      - Aspect pointcut limits matching to "execution(* *(..))" in order to
        avoid also matching "call()" joinpoints.
    
    The end result is that now we have a clear separation of concerns, clear
    dependencies, no more scripted Ant build components and the new option
    to use the lomboked code optionally with or without aspects applied
    because both types or JARs are created during the build.
    

    Feel free to add my fork as another remote to your Git repository and pull in my changes from there. If you prefer me to send you pull requests in order to make it easier, just let me know.

    Update 2, 2023-12-16:

    I revisited this topic for another reason and realised, that last time I did not mention one important thing: In a multi-module approach with an intermediary, unwoven and a final, woven module, the former should be declared with provided scope in the latter, because we want to avoid the unwoven module ending up on the classpath of other modules depending on the woven one. This is important, because otherwise two sets of (duplicate, but different) classes would end up on the classpath, and depending on the exact classl-loading situation, either one could be found first.

    Therefore, I added a few commits to the multi-module branch, cleaning up the situation and adding a dependent module with a test on classpath content to make sure everything works as expected.