侧边栏壁纸
博主头像
lai博主等级

  • 累计撰写 51 篇文章
  • 累计创建 19 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Spring Boot集成 Load-time Weaving (LTW)

lai
lai
2022-12-27 / 0 评论 / 0 点赞 / 1,083 阅读 / 1,516 字
温馨提示:
本文最后更新于 2022-12-27,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

摘要

本文介绍如何在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

  1. ENABLED 启用
  2. DISABLED 禁用
  3. AUTODETECT 如果存在META-INF/aop.xml则启用

@EnableAsyncmode用来控制如何应用切面,如果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不美观,不知是否还有更优雅的方法

0

评论区