摘要
本文介绍如何在spring boot3.0.0中使用Load-time Weaving (LTW),spring 普通的切面只能拦截到容器中的对象,而不在容器中管理的类该如何实现切面功能呢,这种情况可以通过LTW切面技术实现这些特殊场景需求,如:覆盖spring data底层方法,在jpa中实现动态设置存储过程名称。通常情况下如果需要修改引用jar中的class文件,我们会在项目中新建一个相同路径,创建相同类名,然后将整个class的方法复制到新类中,然后在新类中重新写入自己需要的逻辑,利用tomcat加载项目相同限定路径下同名文件时候先加载class文件夹下的编译文件。然而,这种方法在spring boot 打包成可执行jar的情况下行不通,这里就利用到了Load-time Weaving技术去实现,换而言之,通过LTW我们可以实现更多骚操作,amazing!
简介
Load-time weaving (LTW)是利用JVMTI+AspectJ实现class二进制文件加载到JVM时,对类中的方法中进行切面操作,对方法进行增强操作,所以使用LTW会议用到我们熟悉的-javaagent
去执行程序,相比于编写javaagent
,LTW只需要简单的注解和xml
配置即可实现
demo工程:喵喵变汪汪
引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
</dependency>
这里需要注意jdk与spring-aspects版本依赖关系,由于示例工程中做了版本统一管理,引入模块时不用再次声明版本信息
知识伸延
spring-boot-starter-aop
Aspect-Oriented Programming (AOP)面向切面编程,Spring AOP 的 AOP 方法不同于大多数其他 AOP 框架。目的不是提供最完整的AOP实现,而是提供 AOP 实现和 Spring IoC 之间的紧密集成,以帮助解决企业应用程序中的常见问题。Spring AOP只是 AOP的一种解决方案,Spring 2.0开始支持@AspectJ
注解
spring-aspects
引入aspectjweaver.jar,AspectJ是一个易用的功能强大的AOP框架。Spring AOP 比使用完整的 AspectJ 更简单,因为不需要将 AspectJ 引入到工程中。如果只需要通知对 Spring beans 的操作执行,那么 Spring AOP 已经能满足使用需求。如果切点是不受 Spring 容器管理的对象(例如通常的域对象),那么需要使用 AspectJ。如果希望切点不是简单的方法执行(例如,字段获取等),需要使用 AspectJ。
spring-instrument
提供Instrumentation代理,用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义,依赖于 JVMTI。
代码编写
启用LoadTimeWeaving
package com.laijava.aspect;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableLoadTimeWeaving;
import org.springframework.scheduling.annotation.EnableAsync;
import static org.springframework.context.annotation.EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT;
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving = AUTODETECT)
@EnableAsync(mode = AdviceMode.ASPECTJ)
public class LoadTimeWeavingConfig {
}
@EnableLoadTimeWeaving
- ENABLED 启用
- DISABLED 禁用
- AUTODETECT 如果存在
META-INF/aop.xml
则启用
@EnableAsync
mode用来控制如何应用切面,如果mode是AdviceMode.PROXY(默认),其他的几个属性会一起来控制如何进行代理,请注意,代理只可以拦截在代理对象上的方法调用,在目标对象内部的方法调用是无法被拦截的。如果mode是AdviceMode.ASPECTJ,proxyTargetClass这个属性会被忽略,同时要注意此时classpath中必须要包含spring-aspects相关模块的jar包,此时就不存在代理了,方法内部调用也会被拦截。即切面使用ASPECTJ不是Spring AOP
编写普通类
package com.laijava.example;
public class Cat {
public String noise(){
return "mew ~~";
}
}
普通的java类,将通过LTW修改
noise()
方法
编写切面
package com.laijava.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @ClassName LoadTimeWeavingAspect * @Description LTW切面
* @Date 2022/12/6 16:37
* @Version 1.0 * @Author 3045566537@qq.com **/
@Aspect
@Component
public class LoadTimeWeavingAspect {
// 解决找不到aspectOf()方法异常
public static LoadTimeWeavingAspect aspectOf() {
return new LoadTimeWeavingAspect();
}
//通过切面实现noise重写
@Around("execution(public * com.laijava..*.*(..))")
public Object invoked(ProceedingJoinPoint pjp) throws Throwable {
return "dog dog dog";
}
}
编写aspectj配置
在src/main/resources
下创建META-INF/aop.xml
文件
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="com.laijava.example.*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="com.laijava.aspect.LoadTimeWeavingAspect"/>
</aspects>
</aspectj>
测试
package com.laijava;
import com.laijava.example.Cat;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class LoadTimeWeavingAppTest {
@Test
void contextLoads() {
Cat c = new Cat();
System.out.println(c.noise());
}
}
配置vm:-ea -javaagent:your_path\spring-instrument-6.0.2.jar -javaagent:your_path\aspectjweaver-1.9.9.1.jar --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
执行单元测试控制台输出dog dog dog
到此一个简单的LTW
模块完成开发
常见问题
Failed to instantiate [org.springframework.instrument.classloading.LoadTimeWeaver]: Factory method 'loadTimeWeaver' threw exception with message: ClassLoader [jdk.internal.loader.ClassLoaders$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
需要在vm配置-javaagent:spring-instrument-{version}.jar
java.lang.NoSuchMethodError: 'com.laijava.aspect.LoadTimeWeavingAspect com.laijava.aspect.LoadTimeWeavingAspect.aspectOf()'
切面类中需要实现aspectOf()
方法
更多阅读
AOP&Aspects
Load-Time Weaving
5.10.4. Load-time Weaving with AspectJ in the Spring Framework
思考
启动时候加入-javaagent
不美观,不知是否还有更优雅的方法
评论区