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

1

5

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

Tennessee answered 11/7, 2019 at 19:32 Comment(0)
M
11

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.

Mix answered 12/7, 2019 at 1:57 Comment(15)
Please note that I have updated my answer with extra information and resources.Mix
What i did to make IDEA run my project is to make sure Delegate to javac is set in Java compiler settings and the aspectj facet for main-app module has post-compile weave mode option enabled (it's not reset on maven project reimport). Thanks for letting me know about delegation to maven option. Never knew about it.Tennessee
I looked at the official approach that you linked but there is no issues with ajc when compiling so i didn't change the directory. And binding to process-classes seems unnecessary.Tennessee
Thanks for the afford for playing around with this sample project. Unfortunately i can't use your multi-phase-compilation approach in real application. I want to add Lombok to an old project that has a lot of stuff going on, but adding Lombok will change resulting bytecode (changing ajc to javac+binary ajc) . Is there any risks from moving to post-compile time weaving?Tennessee
The question is very general. What kind of risks and in comparison to what? None that I can think of which would stop me from using it. Also, what you just said about the legacy application to me does not sound like my two phase approach would not work. BTW, if you want to compile Java code with Ajc directly, consider using Delombok via Maven plugin first and then compile normally.Mix
I assumed that modifying bytecode from javac can introduce bugs that are not present when doing compilation and weaving at the same time with ajc. I looked at "Delombok" approach but official usage requires moving lombok classes to "src/main/lombok" which is unacceptable. In general i had issues making IDEA+delombok+aspects work in unison.Tennessee
There are people who find a solution for every problem and others who find a problem in every solution. The plugin FAQ tell you how to override the source folder. Anyway, however inflexible and unwilling to refactor your build process you might be, I made many suggestions now, maybe we just close the discussion. Good luck to you.Mix
Thanks for you time @kriegaex. I made both post-compile and delombok solutions working but went with the first one. Good luck to you too.Tennessee
I just returned from a 1 week trip and was curious to try "Delegate to javac" plus "post compile weave mode" in my IDEA settings. It does not work in my project with the two application modules (unwoven, then woven). The test aspectsApplied fails. I had to switch back to "delegate to Maven", as I suggested in my answer.Mix
Welcome back! With your setup the issue is that IDEA doesn't support <weaveDependencies> optionTennessee
Yeah, IDEA has many AspectJ-related shortcomings and open tickets. Many years ago there was an initial burst of AJ support but since then it never has been improved or finished, which is why my development activities are kinda schizophrenic: I use IDEA 90% except for anything related to AspectJ. There is still prefer Eclipse due to its more mature AJ support, though also not perfect. I just used IDEA in this case because you did.Mix
I tried to configure IDEA for my case just out of curiosity. I was surprised it worked :)Tennessee
Apparently this still won't work for Java 11 because the plugin can't handle it. See github.com/mojohaus/aspectj-maven-plugin/pull/… for possible replacementOldtimer
Jakub, your comment is not really related to this question. Anyway, for Java >8 you can indeed use plugin fork com.nickwongdev:aspectj-maven-plugin as a replacement. Even the upcoming IntelliJ IDEA 2020.x will recognise it. So even though there is a not so well maintained Mojohaus plugin (maintainer is too busy with job and private things, he told me), there is a solution for the problem. Thanks for mentioning it. We are also working on getting the necessary changes merged into the upstream version.Mix
Please note my recent update, explaining an important dependency management issue and linking to new commits showing and testing how to deal with it.Mix

© 2022 - 2024 — McMap. All rights reserved.