Springboot with both aspectj and Spring AOP



我正在尝试获得一个springboot(2.6.2)项目与AspectJ和Spring AOP一起工作。

我有以下样例类:

@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;
}

我的pom.xml文件定义了以下插件:

<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>

我还定义了一个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>

似乎可以编译,我看到连接点被建议的信息消息。然而,在OtherAspect中,自动连接的MyUtility没有被设置。

从我能找到我希望Spring识别OtherAspect作为一个组件和自动在我的实用程序,但我得到一个NullPointerException。

任何想法吗?谢谢!

好的,我有一点时间准备了MCVE,这实际上是你的工作。我做了以下假设:

  1. 你需要本地的AspectJ,因为你想编织一个不是Spring bean的目标类。
  2. 你想使用编译时的编织,而不是加载时的编织。因此,你应该使用AspectJ Maven Plugin。
  3. 你想使用Spring依赖注入将Spring bean连接到本机AspectJ方面,如Spring手册中所述,即在Spring中使用aspectOf工厂方法。
  4. 您绝对坚持将Lombok和本机AspectJ结合起来,即使它们开箱即用不兼容。也就是说,您需要在Maven中找到一个解决方案,要么是二进制编织(例如,如果Lombok仅用于非方面类),要么是"delombok";构建步骤(例如,如果您的方面也使用Lombok,不幸的是,他们使用MyAspect中的@Slf4jLombok注释)。

我在你的设置中改变了什么:

  • 我删除了对Spring Data JPA的依赖,使事情变得更容易,因为我懒得设置一个虚拟的内存数据库。它与这里的解决方案无关。也就是说,我还在Item类中注释了@Entity@Id注释。
  • 您已经配置了一个"构建步骤,我想坚持,因为它似乎是你的偏好。因此,当使用${project.build.directory}/generated-sources/delombok作为源目录时,示例代码只能使用AspectJ Maven进行编译。使用<weaveDirectory>的想法不起作用,因为带有Lombok注释的方面不会以这种方式编译,因为它引用了Lombok生成的静态log字段。
  • 我从本机AspectJ方面删除了@Service注释,因为这会在连接应用程序时导致问题。相反,我将@Bean工厂方法添加到OtherAspect,因此我们可以在那里使用@Autowired MyUtility myUtility。在同一方面,我也从@annotation(SystemCall)(由于在你的例子中缺少代码)切换到@annotation(Verify.Access),以便有一些东西来测试。我删除了多余的aop.xml文件。
  • 我添加了一个小的Spring引导驱动程序。
  • 我从不再维护的com.nickwongdevAspectJ Maven插件切换到当前的dev.aspectj插件,它有更多的功能,也支持Java 17+。

整个应用程序是这样的:

<?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());
}
}

如果您运行Spring Boot应用程序,您将在控制台中看到以下内容(删除时间戳):

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

可以看到,两个方面都起作用了,第一个记录了一个ERROR,另一个改变了返回值,从"my name"到"我的信息">

"delombook "的优势不同之处在于,在同一个Maven模块中,您可以将方面编织到lombok生成的源代码中。缺点是,由于非常不寻常的自定义配置,您可能无法在IDE中编译从Maven导入的项目。在IntelliJ IDEA中,我不得不将构建委托给Maven,但源代码编辑器仍然显示出弯曲的线条。

作为一种替代方法,您可以使用Lombok编译创建一个模块(没有"delombok"),使用二进制编织创建第二个模块,以便将方面编织到Lombok增强的类文件中,如下所述。不过,如果没有龙目岛,一切都会容易得多。第三种选择是使用为Spring Boot配置的Lombok和本机AspectJ加载时编织进行编译,而不是在构建时使用编译时或二进制编织。我无法在这里详细解释和展示每一个变体,这已经是一个很长的回答了。

最新更新