编译时:比如使用 AspectJ 编译器
加载时:本文介绍的 AspectJ 的 LoadTimeWeaving (LTW)
运行时:Spring AOP 默认方式,通过动态代理或 cglib
在类加载期通过字节码编辑技术将切面织入目标类,这种方式叫做 LTW(Load Time Weaving)。
使用 JDK5 新增的 java.lang.instrument 包,在类加载时对字节码进行转换,从而实现 AOP 功能。
依赖类库
JDK 8 及以上, spring-AOP 和 aspectJ
(最低为 JDK 5)
org.springframework.boot:spring-boot-starter-aop:2.3.1.RELEASE
org.springframework:spring-aspects:5.2.7.RELEASE
完整代码
com\example\
@SpringBootApplication
@EnableLoadTimeWeaving
@EnableSpringConfigured
@EnableAsync(mode = AdviceMode.ASPECTJ)
public class AppApplication {
public static void main(String[] args) {
// 初始化 spring context
ApplicationContext context = SpringApplication.run(AppApplication.class, args);
// 创建 POJO,此时 TestService 会被注入到 POJO 中
POJO pojo = new POJO();
System.out.println("inject bean " + pojo.testService);
TestService testService = context.getBean(TestService.class);
// 正常调用切面
testService.asyncPrint();
// 切面的内部调用
testService.print();
// 非 spring 管理的类切面调用,spring 定义的切面
pojo.print();
// 非 spring 管理的类切面调用,自定义的切面
pojo.print1();
}
}
@Configurable
public class POJO {
// 依赖注入
@Autowired
public TestService testService;
// spring 的切面
@Async
public void print() {
System.out.println("POJO print thread " + Thread.currentThread().toString());
}
// 自定义的切面
@Profile
public void print1() {
System.out.println("POJO print1");
}
}
public @interface Profile {
}
// 切面定义
@Aspect
public class ProfilingAspect {
// 环绕通知,打印方法执行时间
@Around("methodsToBeProfiled()")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object obj = pjp.proceed();
long cost = System.currentTimeMillis() - start;
System.out.println("profile " + pjp.getSignature().toShortString() + " " + cost);
return obj;
}
// 切点为 Profile 注解,且织入到方法执行中
@Pointcut("execution(* *(..)) && @annotation(com.example.Profile)")
public void methodsToBeProfiled() {
}
}
@Component
public class TestService {
@Async
@Profile
public void asyncPrint() {
System.out.println("TestService print thread " + Thread.currentThread().toString());
}
public void print() {
asyncPrint();
}
}
org\aspectj\aop.xml (注意路径)
<aspectj>
<weaver options="-showWeaveInfo -verbose">
<include within="com.example.*" />
weaver>
<aspects>
<aspect name="com.example.ProfilingAspect" />
aspects>
aspectj>
说明
@EnableLoadTimeWeaving 为开启 LTW,或使用
@Configurable 必须和 @EnableSpringConfigured (或
@Configurable 可指明在构造函数前或后注入
@EnableAsync 或 @EnableCaching 必须使用 ASPECTJ 模式
启动 VM 参数
-javaagent:path\spring-instrument-5.1.6.RELEASE.jar
-javaagent:path\aspectjweaver-1.9.2.jar
spring-instrument 用于类加载时修改字节码
aspectjweaver 用于 @Async、@Cacheable 等 spring 内置切面
(path替换为本地路径;若使用 Intellij Idea,配在 VM options 中)
输出
inject bean com.example.TestService@63cd604c
POJO print thread Thread[task-3,5,main]
TestService print thread Thread[task-1,5,main]
POJO print1
TestService print thread Thread[task-2,5,main]
profile TestService.asyncPrint() 0ms
profile POJO.print1() 0ms
profile TestService.asyncPrint() 0ms
成功向 new 创建的对象注入 bean,且类内调用切面生效、 new 创建的对象切面生效
<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.0modelVersion>
<groupId>com.examplegroupId>
<artifactId>test-ltwartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.1.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
<version>2.3.1.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.2.7.RELEASEversion>
dependency>
dependencies>
project>
引入maven依赖 spring-boot-starter-web 即可,LoadTimeWeaving 用法不变。
对于直接使用 Tomcat 或 Jboss 的项目不需要 spring-instrument,Tomcat 等容器自带实现。
java.lang.IllegalStateException: ClassLoader [sun.misc.Launcher$AppClassLoader] does NOT provide an ‘addTransformer(ClassFileTransformer)’ method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring’s agent: -javaagent:spring-instrument-{version}.jar
缺少 javaagent spring-instrument
Caused by: java.lang.NoSuchMethodException: XXX.aspectOf()。
缺少 javaagent aspectjweaver
切面通知执行两次的问题
若切点为 annotation 类型,对 method-call 和 method-execution 都会生效,导致通知代码被注入了两次,一次在外部调用方法的地方,一次在方法体执行的地方。应加上 execution(* *(…)), 详见:
https://stackoverflow.com/questions/34218012/spring-aop-and-aspectj-load-time-weaving-around-advice-will-be-invoked-twice-fo
AspectJ:(第5节为 Load-Time Weaving)
https://www.eclipse.org/aspectj/doc/released/devguide/index.html
Spring AOP using AspectJ: (5.10.4)
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-using-aspectj
https://www.cnblogs.com/yjmyzz/p/why-spring-aop-does-not-work.html
思路是将自己注入到自己中
https://stackoverflow.com/questions/310271/injecting-beans-into-a-class-outside-the-spring-managed-context
You can do this:
ApplicationContext ctx = ...
YourClass someBeanNotCreatedBySpring = ...
ctx.getAutowireCapableBeanFactory().autowireBeanProperties(
someBeanNotCreatedBySpring,
AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT, true);
You can use @Autowired and so on within YourClass to specify fields to be injected etc.