我正在尝试让一个 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 识别为 MyUtility 中的组件和自动装配,但我得到的是 NullPointerException。
有什么想法吗? 谢谢!
好的,我有一点时间准备了 MCVE,这实际上是您的工作。我做了以下假设:
aspectOf
工厂方法。 @Slf4j
MyAspect
.我在你的设置中改变了什么:
@Entity
.中注释掉了
@Id
和
Item
${project.build.directory}/generated-sources/delombok
作为源目录时使用 AspectJ Maven 进行编译。您使用 <weaveDirectory>
的想法行不通,因为带有 Lombok 注释的方面不会以这种方式编译,因为它指的是 Lombok 生成的静态 log
字段。@Service
注释,因为这会导致在连接应用程序时出现问题。相反,我在 @Bean
中添加了一个 OtherAspect
工厂方法,这样我们就可以在那里使用 @Autowired MyUtility myUtility
了。在同一方面,我也从@annotation(SystemCall)
(由于您的示例中缺少代码)切换到@annotation(Verify.Access)
以便进行测试。com.nickwongdev
AspectJ 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
如您所见,两个方面都起作用,第一个记录错误,另一个将返回值从“我的名字”更改为“我的信息”。
“delombok”变体的优点是在同一个 Maven 模块中,您可以将方面编织到 Lombok 生成的源代码中。缺点是,由于非常不寻常的自定义配置,在您的 IDE 中您可能无法编译从 Maven 导入的项目。在 IntelliJ IDEA 中,我不得不将构建委托给 Maven,但源代码编辑器仍然显示波浪线。
作为替代方案,您可以使用 Lombok 编译(不是“delombok”)创建一个模块,并使用二进制编织创建第二个模块,以便将方面编织到 Lombok 增强的类文件中,如 here 所述。不过,如果没有 Lombok,一切都会容易得多。第三种选择是使用 Lombok 和为 Spring Boot 配置的本机 AspectJ 加载时织入进行编译,而不是在构建时使用编译时或二进制织入。我无法在这里详细解释和展示每个变体,这已经是一个很长的答案了。