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!
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:
aspectOf
factory method for the native aspect in Spring.@Slf4j
Lombok annotation in MyAspect
.What I changed in your setup:
@Entity
and @Id
annotations in class Item
.${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.@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.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.