1

I am trying to get a springboot (2.6.2) project to work with both AspectJ and Spring AOP.

I have the following sample classes:

@Entity
public class Item {
  @Id @Getter private String uuid = UUID.randomUUID().toString();

  private String name;

  @Verify.Access
  public String getName() {
    return name;
  }
}
public @interface Verify {
 @Target({ElementType.METHOD})
  @Retention(RetentionPolicy.RUNTIME)
  @interface Access {}
}
@Aspect
@Slf4j
public class MyAspect {

  @Before("@annotation(Verify.Access)")
  public void beforeAnnotation(JoinPoint joinPoint) {
    log.error("BEFORE ANNOTATION");
  }
}
@Aspect
@Service
public class OtherAspect {
  @Autowired private MyUtility myUtility;

  @Around("@annotation(SystemCall)")
  public Object run(@NonNull final ProceedingJoinPoint join) throws Throwable {
    return myUtility.getInfo();
  }
}
@Service
@Data
public class MyUtility {
  Object info;
}

My pom.xml file has the following plugins defined:

<plugin>
   <groupId>com.nickwongdev</groupId>
   <artifactId>aspectj-maven-plugin</artifactId>
   <version>1.12.6</version>
   <configuration>
      <source>${java.version}</source>
      <target>${java.version}</target>
      <proc>none</proc>
      <complianceLevel>${java.version}</complianceLevel>
      <showWeaveInfo>true</showWeaveInfo>
      <forceAjcCompile>true</forceAjcCompile>
      <sources/>
         <weaveDirectories>
            <weaveDirectory>${project.build.directory}/classes</weaveDirectory>
         </weaveDirectories>
   </configuration>
   <executions>
      <execution>
         <goals>
            <goal>compile</goal>
         </goals>
      </execution>
   </executions>
   <dependencies>
      <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjtools</artifactId>
         <version>${aspectj.version}</version>
      </dependency>
   </dependencies>
</plugin>

<plugin>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-maven-plugin</artifactId>
   <version>1.18.20.0</version>
   <executions>
      <execution>
         <phase>generate-sources</phase>
         <goals>
            <goal>delombok</goal>
         </goals>
      </execution>
   </executions>
   <configuration>
      <addOutputDirectory>false</addOutputDirectory>
      <sourceDirectory>src/main/java</sourceDirectory>
      <encoding>UTF-8</encoding>
   </configuration>
</plugin>

I have also defined a src/main/resources/org/aspectj/aop.xml:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
   <weaver>
      <include within="mypackage..*" />
      <include within="org.springframework.boot..*" />
   </weaver>

   <aspects>
      <aspect name="mypackage.MyAspect" />
   </aspects>
</aspectj>

It seems to compile okay and I see the info messages that the join points are being advised. However, in the OtherAspect the autowired MyUtility is not getting set.

From what I could find I would expect Spring to recognize OtherAspect as a Component and Autowire in MyUtility but instead I get a NullPointerException.

Any thoughts? Thanks!

kriegaex
  • 63,017
  • 15
  • 111
  • 202
bkqdad
  • 15
  • 3
  • Welcome to SO. I am a bit confused, because you configured your POM for native AspectJ compile-time weaving while also using _aop.xml_ for load-time weaving. Then you also add a Spring `@Service` annotation to one of your aspects, which does not make much sense to me. It seems as if you want to use AspectJ and Spring AOP for the same aspects. Why? Do you think that "much helps much", trying to get the same aspect picked up 3 different ways? Please explain which type of AOP ansd which type of weaving you actually want and why. Ideally, provide an full, minimal project on GitHub. – kriegaex Dec 03 '22 at 11:26
  • I need to understand first, before I can help. For example, why do you think you need native AspectJ in the first place? Are you trying to advise classes which are not Spring beans? From your code, I cannot infer your intent or understand your problem. The classes and the POM are incomplete, there is no Spring Boot driver application and the annotations used in the code are not the same as the ones in the aspect. I cannot debug pseudo code. – kriegaex Dec 03 '22 at 11:29

1 Answers1

1

OK, I had a little bit of time and prepared the MCVE which actually would have been your job to provide. I made the following assumptions:

  1. You need native AspectJ, because you want to weave a target class which is not a Spring bean.
  2. You want to use compile-time, not load-time weaaving. Therefore, you would use AspectJ Maven Plugin.
  3. You want to use Spring dependency injection for wiring Spring beans into native AspectJ aspects, as described in the Spring manual, i.e. using an aspectOf factory method for the native aspect in Spring.
  4. You absolutely insist on combining Lombok and native AspectJ, even though they are incompatible out of the box. I.e., you need a workaround in Maven, either binary weaving (e.g. if Lombok is only used for your non-aspect classes) or a "delombok" build step (e.g. if your aspects also use Lombok, which unfortunately they do, using the @Slf4j Lombok annotation in MyAspect.

What I changed in your setup:

  • I removed the dependency on Spring Data JPA to make things easier, because I was too lazy to set up a dummy in-memory database. It is not relevant for the solution here. I.e., I also commented out the @Entity and @Id annotations in class Item.
  • You already configured a "delombok" build step, which I wanted to stick with, because it seems to be your preference. Hence, your sample code only compiles with AspectJ Maven when using ${project.build.directory}/generated-sources/delombok as the source directory. Your idea to use a <weaveDirectory> does not work, because the aspect with the Lombok annotation does not compile that way, as it refers to the Lombok-generated static log field.
  • I removed the @Service annotation from the native AspectJ aspect, because that would lead to problems when wiring the application. Instead, I added a @Bean factory method to OtherAspect, so we can use @Autowired MyUtility myUtility there. In the same aspect, I also switched from @annotation(SystemCall) (due to missing code in your example) to @annotation(Verify.Access) in order to have something to test against.
  • I removed the superfluous aop.xml file.
  • I added a little Spring Boot driver application.
  • I switched from the no longer maintained com.nickwongdev AspectJ Maven plugin to the current dev.aspectj plugin which has more features and supports Java 17+, too.

The whole application looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>SO_AJ_SpringAutowireBeanNativeAspect_74661663</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <aspectj.version>1.9.9.1</aspectj.version>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>dev.aspectj</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.13.1</version>
        <configuration>
          <complianceLevel>${maven.compiler.target}</complianceLevel>
          <proc>none</proc>
          <showWeaveInfo>true</showWeaveInfo>
          <forceAjcCompile>true</forceAjcCompile>
          <sources>
            <source>
              <basedir>${project.build.directory}/generated-sources/delombok</basedir>
            </source>
          </sources>
          <!--
          <weaveDirectories>
            <weaveDirectory>${project.build.directory}/classes</weaveDirectory>
          </weaveDirectories>
          -->
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
            </goals>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>${aspectj.version}</version>
          </dependency>
        </dependencies>
      </plugin>
      <plugin>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok-maven-plugin</artifactId>
        <version>1.18.20.0</version>
        <executions>
          <execution>
            <phase>generate-sources</phase>
            <goals>
              <goal>delombok</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <addOutputDirectory>false</addOutputDirectory>
          <sourceDirectory>src/main/java</sourceDirectory>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
      <version>2.6.2</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>2.6.2</version>
      <scope>compile</scope>
    </dependency>
    <!--
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
      <version>2.6.2</version>
      <scope>compile</scope>
    </dependency>
    -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
    </dependency>
  </dependencies>

</project>
package org.example;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public @interface Verify {
  @Target({ ElementType.METHOD })
  @Retention(RetentionPolicy.RUNTIME)
  @interface Access {}
}
package org.example;

import lombok.Data;
import org.springframework.stereotype.Service;

@Service
@Data
public class MyUtility {
  Object info;
}
package org.example;

import lombok.Getter;

//import javax.persistence.Entity;
//import javax.persistence.Id;
import java.util.UUID;

//@Entity
public class Item {
//  @Id
  @Getter
  private String uuid = UUID.randomUUID().toString();

  private String name;

  public Item(String name) {
    this.name = name;
  }

  @Verify.Access
  public String getName() {
    return name;
  }
}
package org.example;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
@Slf4j
public class MyAspect {
  @Before("@annotation(Verify.Access)")
  public void beforeAnnotation(JoinPoint joinPoint) {
    log.error("BEFORE ANNOTATION");
  }
}
package org.example;

import lombok.NonNull;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;

@Aspect
public class OtherAspect {
  @Autowired
  private MyUtility myUtility;

//  @Around("@annotation(SystemCall)")
  @Around("@annotation(Verify.Access)")
  public Object run(@NonNull final ProceedingJoinPoint join) throws Throwable {
    return myUtility.getInfo();
//    return join.proceed();
  }
}
package org.example;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.Aspects;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@SpringBootApplication
@Configuration
@Slf4j
public class Main {
  @Bean
  public OtherAspect otherAspect() {
    return Aspects.aspectOf(OtherAspect.class);
  }

  public static void main(String[] args) {
    try (ConfigurableApplicationContext appContext = SpringApplication.run(Main.class, args)) {
      doStuff(appContext);
    }
  }

  private static void doStuff(ConfigurableApplicationContext appContext) {
    MyUtility myUtility = appContext.getBean(MyUtility.class);
    myUtility.setInfo("my info");
    Item item = new Item("my name");
    log.info(item.getName());
  }
}

If you run the Spring Boot application, you will see the following on the console (timestamps removed):

ERROR 20680 --- [           main] org.example.MyAspect                     : BEFORE ANNOTATION
 INFO 20680 --- [           main] org.example.Main                         : my info

As you can see, both aspects kick in, the first one logging an ERROR and the other one changing the return value from "my name" to "my info".

The advantage of the "delombok" variant is that within the same Maven module, you can weave aspects into the Lombok-generated source code. The disadvantage is, that in your IDE you might not be able to compile the project imported from Maven because of the very unusual custom configuration. In IntelliJ IDEA, I had to delegate the build to Maven, but still the source code editor shows squiggly lines.

As an alternative, you could create one module with Lombok compilation (no "delombok") and a second module using binary weaving in order to weave aspects into the Lombok-enhanced class files, as described here. It would all be much easier without Lombok, though. The third alternative is compilation with Lombok and native AspectJ load-time weaving configured for Spring Boot instead of compile-time or binary weaving during build time. I cannot explain and show every variant in detail here, it is a long answer already.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thank you for taking the time to provide such a detailed answer. I am new to spring aop and aspectj and was just trying to get something to work. I would be very interested in the third alternative you mentioned (load time weaving). Would you be able to point me to a good tutorial/example? Thanks! – bkqdad Dec 05 '22 at 21:06
  • I getting errors like "ajc: The method getInfo() is undefined for the type MyUtility" and arc: log cannot be resolved" trying to run this example. It seems like arc is not seeing the delomboked sources. I tried uncommenting the weaveDirectories section bu no luck. – rhinmass Feb 07 '23 at 05:24
  • Why are you hijacking this answer? And what is "arc" supposed to be? Please ask a new question with a full source code reproducer. – kriegaex Feb 07 '23 at 16:57