How to Make Lombok and Aspectj Work Together

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

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.

How to use AspectJ + Lombok + Maven + IntelliJ?

You also need to turn off the autobuild in Intellij Idea. And execute the following command:

mvn clean compile install -Pdev

the statement above is not correct from AspectJ perspective. There are three (if do not pay attention to Spring API) modes to use AspectJ:

  • compile time - when we need to compile native aspectJ (.aj) syntax files or use inter-type declarations
  • post-compile time - we compile sources using javac and after that ajc processes resulting classes - that is your case cause you have specified <sources/>
  • load time weaving - that requires to either specify -javaagent in JVM arguments or have specialised classloader

technically, from maven lifecycle perspective everything should work fine in case of post-compile time weaving, however IDEs typically do not trigger maven lifecycle when you are pressing green arrow near #main or @Test method, instead of that IDEs try to analyse project object model (pom) and make some assumptions about target classpath and dependencies. Unfortunately, IDEs do not support all maven plugins and that is the main reason why sometimes something does not work as expected (for example, IntelliJ do not support code generators like OpenAPI or Axis2, that is why we place that stuff into separate maven modules/projects and run mvn install). It seems that purpose of your mvn ... install is to place correct jar into .m2/repository and give IDE a chance to pick up that jar.


UPD. Some explanations....

Let consider a case when we need to run individual unit test, from maven perspective that would be something like:

mvn -am clean test -Dtest=TestClass#testMethod*

and maven will launch it without facing any difficulties, because before test step it will start compile and test-compile steps, where javac and ajc do the required job. Unfortunately, from developer perspective the maven command mentioned above "takes ages", and that is the reason why IDE tries to optimise that process by compiling only changed classes, unfortunately, if IDE does not support AspectJ (for example, IntelliJ CE does not) or does not recognise maven configuration it has no chance to produce expected output.

Your case is even more complicated because you are using lombok, which is not compatible with ajc (or vice versa), so to produce correct classes you need first compile sources by javac and then postprocess output by ajc.

If your goal is to somehow simplify IDE setup for particular project I would recommend the following:

  • provide reproducible example, describe your goals and difficulties you have faced with
  • try to install AspectJ plugin (if you are not on IntelliJ CE), it seems it has some useful options:

Sample Image

Intellij + Ajc + Lombok/Mapstruct

Enabling Post-compile weave mode in IntelliJ solved it for me (https://www.jetbrains.com/help/idea/using-the-aspectj-ajc-compiler.html).



Related Topics



Leave a reply



Submit