目录
1. 关于 LoadTimeWeaving
1.1 LTW 与不同的切面织入时机
1.2 JDK 实现 LTW 的原理
1.3 如何在 Spring 中实现 LTW
2. Springboot 中使用 LTW 实现 AOP 的例子
3. 参考资料
1. 关于 LoadTimeWeaving
1.1 LTW 与不同的切面织入时机
AOP-- 面向切面编程, 通过为目标类织入切面的方式, 实现对目标类功能的增强. 按切面被织如到目标类中的时间划分, 主要有以下几种:
1. 运行期织入
这是最常见的, 比如在运行期通过为目标类生成动态代理的方式实现 AOP 就属于运行期织入, 这也是 Spring AOP 中的默认实现, 并且提供了两种创建动态代理的方式: JDK 自带的针对接口的动态代理和使用 CGLib 动态创建子类的方式创建动态代理.
2. 编译期织入
使用特殊的编译器在编译期将切面织入目标类, 这种比较少见, 因为需要特殊的编译器的支持.
3. 类加载期织入
通过字节码编辑技术在类加载期将切面织入目标类中, 这是本篇介绍的重点. 它的核心思想是: 在目标类的 class 文件被 JVM 加载前, 通过自定义类加载器或者类文件转换器将横切逻辑织入到目标类的 class 文件中, 然后将修改后 class 文件交给 JVM 加载. 这种织入方式可以简称为 LTW(LoadTimeWeaving).
1.2 JDK 实现 LTW 的原理
可以使用 JKD 的代理功能让代理器访问到 JVM 的底层组件, 借此向 JVM 注册类文件转换器, 在类加载时对类文件的字节码进行转换. 具体而言, java.lang.instrument 包下定义了 ClassFileTransformer 接口, 该接口的作用如下面的注释所描述
- * An agent provides an implementation of this interface in order
- * to transform class files.
- * The transformation occurs before the class is defined by the JVM.
可以通过实现该接口, 并重写如下抽象方法自定义类文件转换规则
- byte[]
- transform( ClassLoader loader,
- String className,
- Class<?> classBeingRedefined,
- ProtectionDomain protectionDomain,
- byte[] classfileBuffer)
- throws IllegalClassFormatException;
classfileBuffer 是原始类文件对应的字节码数组, 返回的 byte[] 为转化后的字节码数组, 如果返回 null, 则表示不进行字节码处理.
而 java.lang.instrument 包下的 Instrumentation 接口则可以将我们自定义的 ClassTransFormer 向 JVM 内部的组件进行注册
- void
- addTransformer(ClassFileTransformer transformer);
在实际使用中, 可以通过 JVM 的 - javaagent 代理参数在启动时获取 JVM 内部组件的引用, 将 ClassFileTransformer 实例注册到 JVM 中, JVM 在加载 Class 文件时, 会先调用这个 ClassTransformer 的 transform() 方法对 Class 文件的字节码进行转换, 比如织入切面中定义的横切逻辑, 实现 AOP 功能. 整个过程可以入下所示
1.3 如何在 Spring 中实现 LTW
Spring 中默认通过运行期生成动态代理的方式实现切面的织入, 实现 AOP 功能, 但是 Spring 也可以使用 LTW 技术来实现 AOP, 并且提供了细粒度的控制, 支持在单个 ClassLoader 范围内实施类文件转换.
Spring 中的 org.springframework.instrument.classloading.LoadTimeWeaver 接口定义了为类加载器添加 ClassFileTransfomer 的抽象
- * Defines the contract for adding one or more
- * {
- @link ClassFileTransformer ClassFileTransformers
- } to a {
- @link ClassLoader
- }.
- *
- public interface LoadTimeWeaver {
Spring 的 LTW 支持 AspectJ 定义的切面, 既可以是直接使用 AspectJ 语法定义的切面, 也可以是使用 @AspectJ 注解, 通过 java 类定义的切面. Spring LTW 通过读取 classpath 下 META-INF/aop.xml 文件, 获取切面类和要被切面织入的目标类的相关信息, 通过 LoadTimeWeaver 在 ClassLoader 加载类文件时将切面织入目标类中, 其工作原理如下所示
Spring 中可以通过 LoadTimeWeaver 将 Spring 提供的 ClassFileTransformer 注册到 ClassLoader 中. 在类加载期, 注册的 ClassFileTransformer 读取类路径下 META-INF/aop.xml 文件中定义的切面类和目标类信息, 在目标类的 class 文件真正被 VM 加载前织入切面信息, 生成新的 Class 文件字节码, 然后交给 VM 加载. 因而之后创建的目标类的实例, 就已经实现了 AOP 功能.
2. Springboot 中使用 LTW 实现 AOP 的例子
实现一个简单的 AOP 需求, 在方法调用前后打印出开始和结束的日志信息.
相关的 maven 依赖和插件
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <argLine>
- -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
- -javaagent:"${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar"
- <!-- -Dspring.profiles.active=test-->
- </argLine>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <configuration>
- <agent>
- ${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar
- </agent>
- <agent>
- ${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar
- </agent>
- </configuration>
- </plugin>
- </plugins>
- </build>
这里通过 maven 插件的方式为 JVM 设置代理, 通过 - javaagent 参数指定织入器类包的路径, 这样就可以在类加载期将切面织入, 更多关于 javaagent 的知识可以参考 javaagent
织入目标类
- /**
- * @author: takumiCX
- * @create: 2018-12-19
- **/
- @Component
- public class LtwBean {
- public void test(){
- System.out.println("process.......");
- }
- }
只有一个 test() 方法, 通过 @Componet 注解向容器注册.
切面类
- /**
- * @author: takumiCX
- * @create: 2018-12-19
- **/
- @Aspect
- public class LogMethodInvokeAspect {
- @Pointcut("execution(public * com.takumiCX.ltw.*.*(..))")
- public void pointCut(){
- }
- @Around("pointCut()")
- public void advise(ProceedingJoinPoint pjp) throws Throwable {
- Signature signature = pjp.getSignature();
- System.out.println(signature+"start.....");
- pjp.proceed();
- System.out.println(signature+"end......");
- }
- }
@Aspect 注解表示这是一个切面类
配置类
- /**
- * @author: takumiCX
- * @create: 2018-12-19
- **/
- @Configuration
- @ComponentScan("com.takumiCX.ltw")
- @EnableLoadTimeWeaving(aspectjWeaving=AUTODETECT)
- public class CustomLtwConfig{
- }
通过 @@EnableLoadTimeWeaving 开启 LTW 功能, 可以通过属性 aspectjWeaving 指定 LTW 的开启策略
ENABLED
开启 LTW
DISABLED
不开启 LTW
AUTODETECT
如果类路径下能读取到 META-INF/aop.xml 文件, 则开启 LTW, 否则关闭
在 META-INF 文件夹下编写 aop.xml 文件
aop.xml 文件内容
<?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
- <aspectj>
- <!-- 要织入切面的目标类 -->
- <weaver>
- <include within="com.takumiCX.ltw..*" />
- </weaver>
- <!-- 切面类 -->
- <aspects>
- <aspect name="com.takumiCX.ltw.aspect.LogMethodInvokeAspect" />
- </aspects>
- </aspectj>
这样我们的 Spring 容器就能加载该文件读取到描述目标类和切面类的相关信息, 容器在加载目标类的 class 文件到 jvm 之前, 会将切面类中定义的增强逻辑织入到 class 文件中, 真正加载到 jvm 中的是织入切面后的 class 文件, 因而通过该 class 文件创建出的目标类的实例, 不需要经过动态代理就能实现 AOP 相关功能.
测试类
- /**
- * @author: takumiCX
- * @create: 2018-12-20
- **/
- @RunWith(SpringRunner.class)
- @SpringBootTest(classes ={CustomLtwConfig.class})
- public class LTWTest {
- @Autowired
- private LtwBean ltwBean;
- @Test
- public void testLTW() throws InterruptedException {
- ltwBean.test();
- }
- }
最后的结果如下
方法调用前后分别记录的开始和结束的日志信息, 说明我们的切面成功的织入到了目标类. 但是这里可能有一个疑问, 这真的是 LTW(Load TimeWeaving) 通过在类加载期织入切面起到的作用吗? 有没有可能是 LTW 没起作用, 是 Spring AOP 默认通过运行期生成动态代理的方式实现的 AOP.
我们的 LogMethodInvokeAspect 切面类上并没有加 @Component 注解向容器注册, 并且配置类 CustomLtwConfig 上也没有加 @EnableAspectJAutoProxy 注解开启 Aspectj 的运行时动态代理, 所以这里基于动态代理的 AOP 并不会生效.
为了验证我们的想法, 将 aop.xml 文件删除
重新运行测试代码
AOP 没起到作用, 说明刚才的 AOP 功能确实是通过 LTW 技术实现的.
当我们给切面类加上 @Component 注解, 给配置类加上 @EnableAspectJAutoProxy
- /**
- * @author: takumiCX
- * @create: 2018-12-19
- **/
- @Aspect
- @Component
- public class LogMethodInvokeAspect {
- /**
- * @author: takumiCX
- * @create: 2018-12-19
- **/
- @Configuration
- @ComponentScan("com.takumiCX.ltw")
- @EnableAspectJAutoProxy
- @EnableLoadTimeWeaving(aspectjWeaving=AUTODETECT)
- public class CustomLtwConfig{
- }
再次运行测试类时, 发现 AOP 又生效了, 这时候类路径下并没有 aop.xml, 所以这时候 AOP 是 Spring 在运行期通过动态代理的方式实现的.
3. 参考资料
《精通 Spring4.x 企业应用开发实战》
《spring 揭秘》
https://sexycoding.iteye.com/blog/1062372
来源: https://www.cnblogs.com/takumicx/p/10150344.html