简单的skywalking探针加载原理学习

news/2024/12/29 0:55:49/

skywalking_0">简单的skywalking探针加载原理学习

本课程为B站bytebuddy课程 的学习笔记。
记录本人的操作记录方便后续复习学习使用,本人具体代码地址为: https://gitee.com/xiaodali/git-agent-demo

1. 创建目标工程

server工程比较简单,只是简单的一个controller可以进行数据库查询,首先我们新建一个git-agent-demo项目,再在下面创建一个子项目agent-server作为测试server服务,具体的搭建过程如下,git-agent-demo父类工程项目pom文件如下:

<?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>cn.git.agent</groupId><artifactId>git-agent-demo</artifactId><version>1.0-SNAPSHOT</version><packaging>pom</packaging><modules><module>agent-server</module></modules><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.9.RELEASE</version><relativePath/></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version><spring-cloud.version>Hoxton.SR8</spring-cloud.version><mybatis.plus.version>3.3.0</mybatis.plus.version><mysql.version>5.1.47</mysql.version><alibaba.version>2.2.5.RELEASE</alibaba.version><seata.version>1.4.2</seata.version><lombok.version>1.18.6</lombok.version></properties><dependencyManagement><dependencies><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!-- springCloud --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><!-- mysql驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis.plus.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.4</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--单元测试--></dependencies></project>

子项目agent-server引入pom文件内容如下:

<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><parent><groupId>cn.git.agent</groupId><artifactId>git-agent-demo</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>agent-server</artifactId><packaging>jar</packaging><name>agent-server</name><url>http://maven.apache.org</url><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies><build><plugins><!-- compiler --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><annotationProcessorPaths><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></path></annotationProcessorPaths></configuration></plugin></plugins></build>
</project>

配置application.yaml内容如下:

server:port: 8080
spring:application:name: git-agent-demodatasource:url: jdbc:mysql://192.168.138.129:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false  # 数据库连接 URLusername: root  # 数据库用户名password: 101022  # 数据库密码driver-class-name: com.mysql.jdbc.Driver
mybatis-plus:configuration:# sql日志打印log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:insert-strategy: not_nullupdate-strategy: not_nullid-type: auto

初始化sql内容如下:

DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info`  (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`user_name` varchar(50) NOT NULL COMMENT '用户名称',`pwd` varchar(50) NOT NULL COMMENT '密码',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `user_name`(`user_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;select * from user_infoinsert into user_info (user_name, pwd) values('user1', '123456');
insert into user_info (user_name, pwd) values('user2', '123456');
insert into user_info (user_name, pwd) values('user3', '123456');
insert into user_info (user_name, pwd) values('user4', '123456');
insert into user_info (user_name, pwd) values('user5', '123456');
insert into user_info (user_name, pwd) values('user6', '123456');

服务层层面创建一个controller作为入口:

package cn.git.agent.controller;import cn.git.agent.entity.UserInfo;
import cn.git.agent.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** @description: 用户信息controller* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-20*/
@RestController
@RequestMapping("/userInfo")
public class UserInfoController {@Autowiredprivate UserInfoService userInfoService;@GetMapping("/list/{userName}")public List<UserInfo> selectUserInfoList(@PathVariable(value = "userName") String userName) {return userInfoService.selectUserInfoList(userName);}}

具体的service以及对应impl如下:

  • UserInfoService

    package cn.git.agent.service;import cn.git.agent.entity.UserInfo;import java.util.List;/*** @description: 用户信息service* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-20*/
    public interface UserInfoService {List<UserInfo> selectUserInfoList(String userName);}
    
  • UserInfoServiceImpl

    package cn.git.agent.service.impl;import cn.git.agent.entity.UserInfo;
    import cn.git.agent.mapper.UserInfoMapper;
    import cn.git.agent.service.UserInfoService;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;import java.util.List;/*** @description: 用户信息service实现* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-20*/
    @Service
    public class UserInfoServiceImpl implements UserInfoService {@Autowiredprivate UserInfoMapper userInfoMapper;@Overridepublic List<UserInfo> selectUserInfoList(String userName) {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().like(UserInfo::getUserName, userName);List<UserInfo> userInfoList = userInfoMapper.selectList(queryWrapper);return userInfoList;}
    }
    

服务启动类如下:

package cn.git.agent;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;/*** @description: 服务端启动类* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-20*/
@EnableFeignClients
@SpringBootApplication
@MapperScan("cn.git.agent.mapper")
public class AgentServerApplication {public static void main(String[] args) {SpringApplication.run(AgentServerApplication.class, args);}
}

启动服务,访问接口 http://localhost:8080/userInfo/list/user 获得展示信息如下,至此准备服务完毕
在这里插入图片描述

2. Skywalking 概述

SkyWalking主要可以划分为4部分:

  • Probes: agent项目(sniffer)探针
  • Platform backend:oap skywalking server服务端分析平台
  • Storage:oap使用的存储
  • UI: 界面

大致流程即:

  1. agent(sniff)探针在服务端程序中执行SkyWalking的插件的拦截器逻辑,完成监测数据获取
  2. agent(sniff)探针通过gRPC(默认)等形式将获取的监测数据传输到OAP系统
  3. OAP系统将数据进行处理/整合后,存储到Storage中(ES, MySQL, H2等)
  4. 用户可通过UI界面快捷查询数据,了解请求调用链路,调用耗时等信息

在这里插入图片描述

3. SkyWalking Agent

深入理解 Skywalking Agent - 简书 (jianshu.com) <= 推荐先阅读,很详细

  • 基础介绍

    SkyWalking Agent,是SkyWalking中的组件之一(skywalking-agent.jar),在Java中通过Java Agent实现。

  • 使用简述

    1. 通过java -javaagent:skywalking-agent.jar包的绝对路径 -jar 目标服务jar包绝对路径指令,在目标服务main方法执行前,会先执行skywalking-agent.jarorg.apache.skywalking.apm.agent.SkyWalkingAgent#premain方法
    2. premian方法执行时,加载与skywalking-agent.jar同层级文件目录的./activations./plusgins目录内的所有插件jar包,根据jar文件内的skywalking-plugin.def配置文件作相关解析工作
    3. 解析工作完成后,将所有插件实现的拦截器逻辑,通过JVM工具类Instrumentation提供的redefine/retransform能力,修改目标类的.class内容,将拦截器内指定的增强逻辑附加到被拦截的类的原方法实现中
    4. 目标服务的main方法执行,此时被拦截的多个类内部方法逻辑已经被增强,比如某个方法执行前后额外通过gRPC将方法耗时记录并发送到OAP。简单理解的话,类似Spring中常用的AOP切面技术,这里相当于字节码层面完成切面增强
  • 实现思考

    Byte Buddy可以指定一个拦截器对指定拦截的类中指定的方法进行增强。SkyWalking Java Agent使用ByteBuddy实现,从0到1实现时,避不开以下几个问题需要考虑:

    1. 每个插件都需要有各自的拦截器逻辑,如何让Byte Buddy使用我们指定的多个拦截器
    2. 多个拦截器怎么区分各自需要拦截的类和方法
    3. 如何在premain中使用Byte Buddy时加载多个插件的拦截器逻辑

3.1 实现springMVC插件拦截

我们之前的bytebuddy-core核心学习过程中已经实现了springMVC方法的拦截,追要是拦截 @RestController以及@Controller注解的类,然后拦截带有Mapping尾缀注解的方法 ,实现拦截springMVC入口方法。这里新增一个springmvc-standlone-plugin模块,这里就简单回顾一下:

  • 引入pom

    <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><parent><groupId>cn.git.agent</groupId><artifactId>git-agent-demo</artifactId><version>1.0-SNAPSHOT</version><relativePath>../../pom.xml</relativePath></parent><artifactId>springmvc-standlone-plugin</artifactId><packaging>jar</packaging><name>springmvc-standlone-plugin</name><url>http://maven.apache.org</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><!-- Byte Buddy --><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.14.10</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version></dependency></dependencies><build><plugins><plugin><!-- 用于打包插件 --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.0</version><configuration><archive><manifestEntries><!-- MANIFEST.MF 配置项,指定premain方法所在类 --><Premain-Class>cn.git.agent.SpringMVCAgent</Premain-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes><Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix></manifestEntries></archive><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs></configuration><executions><execution><id>make-assembly</id><!-- 什么阶段会触发此插件 --><phase>package</phase><goals><!-- 只运行一次 --><goal>single</goal></goals></execution></executions></plugin></plugins></build>
    </project>
  • SpringMVCAgent探针入口

    package cn.git.agent;import net.bytebuddy.agent.builder.AgentBuilder;
    import net.bytebuddy.matcher.ElementMatchers;import java.lang.instrument.Instrumentation;/*** @description: byteBuddy探针,实现springmvc 拦截器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-19*/
    public class SpringMVCAgent {/*** 控制器注解名称*/public static final String REST_CONTROLLER_NAME = "org.springframework.web.bind.annotation.RestController";public static final String CONTROLLER_NAME = "org.springframework.stereotype.Controller";/*** premain方法,main方法执行之前进行调用,插桩代码入口* @param args 标识外部传递参数* @param instrumentation 插桩对象*/public static void premain(String args, Instrumentation instrumentation) {// 创建AgentBuilder对象AgentBuilder builder = new AgentBuilder.Default()// 忽略拦截的包.ignore(ElementMatchers.nameStartsWith("net.bytebuddy").or(ElementMatchers.nameStartsWith("org.apache")))// 拦截标注以什么注解的类.type(ElementMatchers.isAnnotatedWith(ElementMatchers.named(CONTROLLER_NAME).or(ElementMatchers.named(REST_CONTROLLER_NAME))))// 前面的type()方法匹配到的类,进行拦截.transform(new ByteBuddyTransform()).with(new ByteBuddyListener());// 安装builder.installOn(instrumentation);}
    }
    
  • transformer方法

    package cn.git.agent;import net.bytebuddy.agent.builder.AgentBuilder;
    import net.bytebuddy.description.type.TypeDescription;
    import net.bytebuddy.dynamic.DynamicType;
    import net.bytebuddy.implementation.MethodDelegation;
    import net.bytebuddy.utility.JavaModule;import java.security.ProtectionDomain;import static net.bytebuddy.matcher.ElementMatchers.*;/*** @description: bytebuddy transform,当被拦截的type第一次要被加载的时候,会进入到此方法* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-19*/
    public class ByteBuddyTransform implements AgentBuilder.Transformer {/*** 拦截的注解开头结尾*/private static final String MAPPING_PACKAGE_PREFIX = "org.springframework.web.bind.annotation";private static final String MAPPING_PACKAGE_SUFFIX = "Mapping";/*** Allows for a transformation of a {@link DynamicType.Builder}.** @param builder* @param typeDescription  要被加载的类的信息* @param classLoader      The class loader of the instrumented class. Might be {@code null} to represent the bootstrap class loader.* @param module           The class's module or {@code null} if the current VM does not support modules.* @param protectionDomain The protection domain of the transformed type.* @return A transformed version of the supplied {@code builder}.*/@Overridepublic DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,TypeDescription typeDescription,ClassLoader classLoader,JavaModule module,ProtectionDomain protectionDomain) {// 获取实际的名字String actualName = typeDescription.getActualName();System.out.println("actualName: " + actualName);// 确保匹配的是具体的类,而不是接口if (typeDescription.isInterface()) {System.out.println("接口不拦截");return builder;}// 实例化 SpringMvcInterceptorSpringMvcInterceptor interceptor = new SpringMvcInterceptor();// 拦截所有被注解标记的方法DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition<?> intercept = builder.method(not(isStatic()).and(isAnnotatedWith(nameStartsWith(MAPPING_PACKAGE_PREFIX).and(nameEndsWith(MAPPING_PACKAGE_SUFFIX))))).intercept(MethodDelegation.to(interceptor));// 不能返回builder,因为bytebuddy里面的库里面的类基本都是不可变的,修改之后需要返回一个新的builder,避免修改丢失return intercept;}
    }
    
  • 切面interceptor类,具体逻辑实现

    package cn.git.agent;import net.bytebuddy.implementation.bind.annotation.*;import java.lang.reflect.Method;
    import java.util.Arrays;
    import java.util.concurrent.Callable;public class SpringMvcInterceptor {/*** 被标注 RuntimeType 注解的方法就是拦截方法,此时返回的值与返回参数可以与被拦截的方法不一致* byteBuddy会在运行期间给被拦截的方法参数进行赋值* @return*/@RuntimeTypepublic Object intercept(@This Object targetObject,@Origin Method targetMethod,@AllArguments Object[] targetMethodArgs,@SuperCall Callable<?> superCall) {Long start = System.currentTimeMillis();System.out.println("targetObject : " + targetObject);System.out.println("targetMethodName : " + targetMethod.getName());System.out.println("targetMethodArgs : " + Arrays.toString(targetMethodArgs));Object call;try {call = superCall.call();} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e);} finally {Long end = System.currentTimeMillis();System.out.println(targetMethod.getName() + "  耗时:" + (end - start) + "ms");}return call;}
    }
    
  • 监听器类

    package cn.git.agent;import net.bytebuddy.agent.builder.AgentBuilder;
    import net.bytebuddy.description.type.TypeDescription;
    import net.bytebuddy.dynamic.DynamicType;
    import net.bytebuddy.utility.JavaModule;/*** @description: 监听器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-19*/
    public class ByteBuddyListener implements AgentBuilder.Listener {/*** 当一个类型被发现时调用,就会回调此方法** @param typeName    The binary name of the instrumented type.* @param classLoader The class loader which is loading this type or {@code null} if loaded by the boots loader.* @param module      The instrumented type's module or {@code null} if the current VM does not support modules.* @param loaded      {@code true} if the type is already loaded.*/@Overridepublic void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {if (typeName.contains("TestController")) {System.out.println("onDiscovery: " + typeName);}}/*** 对某一个类型进行转换时调用,就会回调此方法** @param typeDescription The type that is being transformed.* @param classLoader     The class loader which is loading this type or {@code null} if loaded by the boots loader.* @param module          The transformed type's module or {@code null} if the current VM does not support modules.* @param loaded          {@code true} if the type is already loaded.* @param dynamicType     The dynamic type that was created.*/@Overridepublic void onTransformation(TypeDescription typeDescription,ClassLoader classLoader,JavaModule module,boolean loaded,DynamicType dynamicType) {System.out.println("onTransformation: " + typeDescription.getActualName());}/*** 当某一个类被加载并且被忽略时(包括ignore配置或不匹配)调用,就会回调此方法** @param typeDescription The type being ignored for transformation.* @param classLoader     The class loader which is loading this type or {@code null} if loaded by the boots loader.* @param module          The ignored type's module or {@code null} if the current VM does not support modules.* @param loaded          {@code true} if the type is already loaded.*/@Overridepublic void onIgnored(TypeDescription typeDescription,ClassLoader classLoader,JavaModule module,boolean loaded) {
    //        log.info("onIgnored: {}", typeDescription.getActualName());
    //        System.out.println("onIgnored: " + typeDescription.getActualName());}/*** 当transform过程中发生异常时,会回调此方法** @param typeName    The binary name of the instrumented type.* @param classLoader The class loader which is loading this type or {@code null} if loaded by the boots loader.* @param module      The instrumented type's module or {@code null} if the current VM does not support modules.* @param loaded      {@code true} if the type is already loaded.* @param throwable   The occurred error.*/@Overridepublic void onError(String typeName,ClassLoader classLoader,JavaModule module,boolean loaded,Throwable throwable) {System.out.println("onError: " + typeName);throwable.printStackTrace();}/*** 当某一个类被处理完,不管是transform还是忽略时,都会回调此方法** @param typeName    The binary name of the instrumented type.* @param classLoader The class loader which is loading this type or {@code null} if loaded by the boots loader.* @param module      The instrumented type's module or {@code null} if the current VM does not support modules.* @param loaded      {@code true} if the type is already loaded.*/@Overridepublic void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
    //        log.info("onComplete: {}", typeName);
    //        System.out.println("onComplete: " + typeName);}
    }

启动服务,然后访问接口 http://localhost:8080/userInfo/list/user 发现接口已经被增强了

在这里插入图片描述
在这里插入图片描述

3.2 实现mysql监听插件

同样的我们新增一个mysql的插件模块mysql-standlone-plugin,对应的pom如下:

<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><parent><groupId>cn.git.agent</groupId><artifactId>git-agent-demo</artifactId><version>1.0-SNAPSHOT</version><relativePath>../../pom.xml</relativePath></parent><artifactId>mysql-standlone-plugin</artifactId><packaging>jar</packaging><name>mysql-standlone-plugin</name><url>http://maven.apache.org</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- Byte Buddy --><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.14.10</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version></dependency></dependencies><build><plugins><plugin><!-- 用于打包插件 --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.0</version><configuration><archive><manifestEntries><!-- MANIFEST.MF 配置项,指定premain方法所在类 --><Premain-Class>cn.git.agent.MySqlAgent</Premain-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes><Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix></manifestEntries></archive><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs></configuration><executions><execution><id>make-assembly</id><!-- 什么阶段会触发此插件 --><phase>package</phase><goals><!-- 只运行一次 --><goal>single</goal></goals></execution></executions></plugin></plugins></build>
</project>

我对应的mysql的版本信息为5.x.x,所以我增强的方法就暂定了 com.mysql.jdbc.PreparedStatement 此类,并且增强类种的"execute", “executeQuery”, “executeUpdate” 三个方法,探针入口类如下:

package cn.git.agent;import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.matcher.ElementMatchers;import java.lang.instrument.Instrumentation;import static net.bytebuddy.matcher.ElementMatchers.named;/*** @description: mysql的拦截插件* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-23*/
public class MySqlAgent {/*** 拦截方法名称*/public static final String PREPARED_STATEMENT_NAME = "com.mysql.jdbc.PreparedStatement";/*** premain方法,main方法执行之前进行调用,插桩代码入口* @param args 标识外部传递参数* @param instrumentation 插桩对象*/public static void premain(String args, Instrumentation instrumentation) {// 创建AgentBuilder对象AgentBuilder builder = new AgentBuilder.Default()// 忽略拦截的包.ignore(ElementMatchers.nameStartsWith("net.bytebuddy").or(ElementMatchers.nameStartsWith("org.apache")))// 拦截标注以什么注解的类.type(named(PREPARED_STATEMENT_NAME))// 前面的type()方法匹配到的类,进行拦截.transform(new MySqlTransform()).with(new MySqlListener());// 安装builder.installOn(instrumentation);}
}

对应的MySqlTransform实现类如下:

package cn.git.agent;import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;import java.security.ProtectionDomain;import static net.bytebuddy.matcher.ElementMatchers.*;/*** @description: bytebuddy transform,当被拦截的type第一次要被加载的时候,会进入到此方法* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-19*/
public class MySqlTransform implements AgentBuilder.Transformer {/*** 拦截的方法*/private static final String INSPECT_EXECUTE_METHOD = "execute";private static final String INSPECT_EXECUTE_QUERY = "executeQuery";private static final String INSPECT_EXECUTE_UPDATE = "executeUpdate";private static final String INSPECT_EXECUTE_LARGE_UPDATE = "executeLargeUpdate";/*** Allows for a transformation of a {@link DynamicType.Builder}.** @param builder* @param typeDescription  要被加载的类的信息* @param classLoader      The class loader of the instrumented class. Might be {@code null} to represent the bootstrap class loader.* @param module           The class's module or {@code null} if the current VM does not support modules.* @param protectionDomain The protection domain of the transformed type.* @return A transformed version of the supplied {@code builder}.*/@Overridepublic DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,TypeDescription typeDescription,ClassLoader classLoader,JavaModule module,ProtectionDomain protectionDomain) {// 获取实际的名字String actualName = typeDescription.getActualName();System.out.println("actualName: " + actualName);// 确保匹配的是具体的类,而不是接口/***         if (typeDescription.isInterface()) {*             System.out.println("接口不拦截");*             return builder;*         }*/// 实例化 SpringMvcInterceptorMySqlInterceptor interceptor = new MySqlInterceptor();// 拦截所有被注解标记的方法DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition<?> intercept = builder.method(ElementMatchers.namedOneOf(INSPECT_EXECUTE_METHOD,INSPECT_EXECUTE_QUERY,INSPECT_EXECUTE_UPDATE,INSPECT_EXECUTE_LARGE_UPDATE)).intercept(MethodDelegation.to(interceptor));// 不能返回builder,因为bytebuddy里面的库里面的类基本都是不可变的,修改之后需要返回一个新的builder,避免修改丢失return intercept;}
}

我们对应的MySqlInterceptors实现如下:

package cn.git.agent;import net.bytebuddy.implementation.bind.annotation.*;import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;/*** @description: mysql增强拦截器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-23*/
public class MySqlInterceptor {/*** 被标注 RuntimeType 注解的方法就是拦截方法,此时返回的值与返回参数可以与被拦截的方法不一致* byteBuddy会在运行期间给被拦截的方法参数进行赋值* @return*/@RuntimeTypepublic Object intercept(@This Object targetObject,@Origin Method targetMethod,@AllArguments Object[] targetMethodArgs,@SuperCall Callable<?> superCall) {Long start = System.currentTimeMillis();try {String executeSql = getSql(targetObject);System.out.println("执行sql信息为 : " + executeSql);} catch (Exception e) {throw new RuntimeException(e);}Object call;try {call = superCall.call();} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e);} finally {Long end = System.currentTimeMillis();System.out.println(targetMethod.getName() + "  耗时:" + (end - start) + "ms");}return call;}/*** 获取sql语句* @param preparedStatement* @return* @throws Exception*/private static String getSql(Object preparedStatement) throws Exception {Field sqlField = findField(preparedStatement.getClass(), "originalSql");if (sqlField == null) {throw new NoSuchFieldException("originalSql");}sqlField.setAccessible(true);return (String) sqlField.get(preparedStatement);}/*** 获取字段* @param clazz* @param fieldName* @return*/private static Field findField(Class<?> clazz, String fieldName) {while (clazz != null) {try {System.out.println("子类没有对应字段,获取父类信息" + clazz.getName());return clazz.getDeclaredField(fieldName);} catch (NoSuchFieldException e) {clazz = clazz.getSuperclass();}}return null;}
}

此处我们需要本地调试,就不使用复制外部包方式了,直接配置VM-option参数,可以使用断点进行调试探针:

-javaagent:D:\idea_workspace\bytebuddy-demo\git-agent-demo\standlone-plugins\mysql-standlone-plugin\target\mysql-standlone-plugin-1.0-SNAPSHOT-jar-with-dependencies.jar

在这里插入图片描述

启动服务,调用对应的查询接口 http://localhost:8080/userInfo/list/user,我们可以看到方法已经被增强,可以获取到执行的sql信息。

在这里插入图片描述

4. 动态插拔插件实现

4.1 实现要点分析

  1. 如何实现单一java agent入口类

    仅在apm-agent模块提供一个premain方法入口,其他插件未来都在apm-plugins内实现

  2. 如何整合多个插件需要拦截的类范围

    AgentBuilder#type方法中,通过.or(Xxx)链接多个插件需要拦截的类范围

  3. 如何整合多个插件需要增强的方法范围

    DynamicType.Builder#method方法中,通过.or(Xxx)链接多个插件需要增强的方法范围

  4. 如何整个多个插件各自增强的方法对应的拦截器

    DynamicType.Builder.MethodDefinition.ImplementationDefinition#intercept可以通过andThen指定多个拦截器

  5. 如何让被拦截的类中增强的方法走正确的拦截器逻辑?

    拦截器InterceptorA对应类拦截范围ClassA下的增强方法范围MethodA,所以需要有一种机制维护三者之间的关系

    需要记录的映射:

    • 拦截类范围 => 增强的方法范围
    • 拦截类范围 => 拦截器

    (如果没有维护这些映射关系,那么多个拦截器互相干扰,比如意外增强了其他类中的同名方法)

4.2 项目结构

我们主备仿照skywalking进行编写,其agent集合apm-sinffer模块向下包含插件apm-plugins模块,探针入口apm-agent模块,以及核心apm-core模块,其中plugins模块向下还包含多个子插件模块,具体结构如下图所示:

在这里插入图片描述

4.3 插件的抽象

我们插件抽象具体实现主要是围绕动态获取拦截的类,动态获取拦截的方法以及拦截类拦截方法需要有一定关联,我们在新增三个模块中进行具体实现,整体逻辑结构图如下:

在这里插入图片描述

4.3.1 核心模块

插件的抽象工作基本都是核心模块完成,具体实现部分参考如下代码

4.3.1.1 插件顶级父类
package cn.git.agent;import cn.git.agent.interceptor.ConstructMethodInterceptPoint;
import cn.git.agent.interceptor.InstanceMethodInterceptPoint;
import cn.git.agent.interceptor.StaticMethodInterceptPoint;
import cn.git.agent.match.ClassMatch;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;/*** @description: 所有插件的顶级父类* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
public abstract class AbstractClassEnhancePluginDefine {/*** 获取需要拦截的类** @return*/protected abstract ClassMatch enhanceClass();/*** 获取需要拦截的方法,实例拦截方法** @return*/protected abstract InstanceMethodInterceptPoint[] getInstanceMethodsInterceptPoints();/*** 获取构造方法的拦截点** @return*/protected abstract ConstructMethodInterceptPoint[] getConstructMethodInterceptPoint();/*** 获取静态方法拦截点** @return*/protected abstract StaticMethodInterceptPoint[] getStaticMethodInterceptPoint();/*** 增强类的主入口** @param typeDescription* @param builder* @param classLoader* @return*/public DynamicType.Builder<?> define(TypeDescription typeDescription,DynamicType.Builder<?> builder,ClassLoader classLoader) {// 增强插件类的名字 eg: cn.git.agent.Mysql5InstrumentationString pluginsDefineClassName = this.getClass().getName();// 要增强的类名称 eg: com.mysql.jdbc.PreparedStatementString typeName = typeDescription.getTypeName();System.out.println("准备使用" + pluginsDefineClassName + "增强" + typeName + "开始");DynamicType.Builder<?> newBuilder = this.enhance(typeDescription, builder, classLoader);System.out.println("使用" + pluginsDefineClassName + "增强" + typeName + "成功");return newBuilder;}private DynamicType.Builder<?> enhance(TypeDescription typeDescription,DynamicType.Builder<?> builder,ClassLoader classLoader) {// 增强静态方法builder = this.enhanceClass(typeDescription, builder, classLoader);// 增强实例方法builder = this.enhanceInstance(typeDescription, builder, classLoader);return builder;}/*** 增强实例方法* @param typeDescription* @param builder* @param classLoader* @return*/protected abstract DynamicType.Builder<?> enhanceInstance(TypeDescription typeDescription,DynamicType.Builder<?> builder,ClassLoader classLoader);/*** 增强静态方法* @param typeDescription* @param builder* @param classLoader* @return*/protected abstract DynamicType.Builder<?> enhanceClass(TypeDescription typeDescription,DynamicType.Builder<?> builder,ClassLoader classLoader);
}
4.3.1.2 设置方法拦截点

方法拦截点分为构造器 ConstructMethodInterceptPoint,实例InstanceMethodInterceptPoint以及StaticMethodInterceptPoint静态方法拦截点,具体实现如下:

  • InstanceMethodInterceptPoint

    package cn.git.agent.interceptor;import net.bytebuddy.description.method.MethodDescription;
    import net.bytebuddy.matcher.ElementMatcher;/*** @description: 需要拦截的方法点,实例方法拦截点* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
    public interface InstanceMethodInterceptPoint {/*** 获取需要拦截的方法,作为method()方法的参数** @return*/ElementMatcher<MethodDescription> getMethodsMatcher();/*** 获取需要拦截的方法的拦截器** @return*/String getMethodInterceptor();
    }
    
  • ConstructMethodInterceptPoint

    package cn.git.agent.interceptor;import net.bytebuddy.description.method.MethodDescription;
    import net.bytebuddy.matcher.ElementMatcher;/*** @description: 构造方法拦截点* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
    public interface ConstructMethodInterceptPoint {/*** 获取需要拦截的方法,作为method()方法的参数** @return*/ElementMatcher<MethodDescription> getConstructMethodsMatcher();/*** 获取需要拦截的方法的拦截器** @return*/String getMethodInterceptor();
    }
    
  • StaticMethodInterceptPoint

    package cn.git.agent.interceptor;import net.bytebuddy.description.method.MethodDescription;
    import net.bytebuddy.matcher.ElementMatcher;/*** @description: 获取静态方法拦截点* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
    public interface StaticMethodInterceptPoint {/*** 获取需要拦截的方法,作为method()方法的参数** @return*/ElementMatcher<MethodDescription> getStaticMethodsMatcher();/*** 获取需要拦截的方法的拦截器** @return*/String getMethodInterceptor();
    }
    
4.3.1.3 拦截类匹配ClassMatch

拦截类匹配器分为NameMatch以及IndirectMatch,他们分别为名称匹配以及其他匹配,顶级父类接口都是ClassMatch,具体实现如下

  • IndirectMatch

    package cn.git.agent.match;import net.bytebuddy.description.type.TypeDescription;
    import net.bytebuddy.matcher.ElementMatcher;/*** @description: IndirectMatch,所有非nameMatch情况,都需要实现IndirectMatch* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
    public interface IndirectMatch extends ClassMatch {/*** 构建匹配规则* eg: named(CLASS_NAME1).or(named(CLASS_NAME2))** @return*/ElementMatcher.Junction<? super TypeDescription> buildJunction();/*** 用于当前匹配器* 用于判断 typeDescription 是否满足 IndirectMatch 的实现** @param typeDescription* @return*/boolean isMatch(TypeDescription typeDescription);}
  • NameMatch

    package cn.git.agent.match;import net.bytebuddy.description.type.TypeDescription;
    import net.bytebuddy.matcher.ElementMatcher;
    import net.bytebuddy.matcher.ElementMatchers;/*** @description: 类名称匹配实现类* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
    public class NameMatch implements ClassMatch {/*** 全类名称*/private String className;public String getClassName() {return className;}/*** 构造函数* @param className*/public NameMatch(String className) {this.className = className;}/*** 构建匹配器** @return*/public ElementMatcher.Junction<? super TypeDescription> buildJunction() {return ElementMatchers.named(className);}/*** 构建匹配器** @param className* @return*/public static NameMatch byName(String className) {return new NameMatch(className);}}

    暂时还有几个IndirectMatch的具体实现类,参考如下,多个注解拦截

    package cn.git.agent.match;import net.bytebuddy.description.annotation.AnnotationDescription;
    import net.bytebuddy.description.annotation.AnnotationList;
    import net.bytebuddy.description.type.TypeDescription;
    import net.bytebuddy.matcher.ElementMatcher;import java.lang.annotation.Annotation;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
    import static net.bytebuddy.matcher.ElementMatchers.named;/*** @description: 多个注解拦截* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
    public class MultiAnnotationMatch implements IndirectMatch {/*** 注解名称列表*/private List<String> annotationNameList;/*** 构造函数** @param annotationNames 注解名称列表*/private MultiAnnotationMatch(String... annotationNames) {if (annotationNames == null || annotationNames.length == 0) {throw new IllegalArgumentException("classNames can not be null or empty");}this.annotationNameList = Arrays.asList(annotationNames);}/*** 类名构建匹配规则* eg: ElementMatchers.isAnnotatedWith(ElementMatchers.named(CONTROLLER_NAME)*     .and(ElementMatchers.named(REST_CONTROLLER_NAME))...** @return*/@Overridepublic ElementMatcher.Junction<? super TypeDescription> buildJunction() {ElementMatcher.Junction<? super TypeDescription> junction = null;for (String annotationName : annotationNameList) {if (junction == null) {junction = isAnnotatedWith(named(annotationName));} else {junction = junction.and(isAnnotatedWith(named(annotationName)));}}return junction;}/*** 用于当前匹配器匹* 用于判断 typeDescription 是否满足 IndirectMatch 的实现** @param typeDescription* @return*/@Overridepublic boolean isMatch(TypeDescription typeDescription) {/*** 比如annotationNameList = [xxx.xxx.Aannotation, xxx.xxx.Bannotation]* typeDescription上的注解是 @Aannotation(xxx) and @BAnnotation(yyy) true 否则 false*/List<String> annotationList = new ArrayList<>(annotationNameList);// 获取typeDescription上的注解名称AnnotationList declaredAnnotations = typeDescription.getDeclaredAnnotations();for (AnnotationDescription annotationDescription : declaredAnnotations) {// 获取注解的全类名String actualName = annotationDescription.getAnnotationType().getActualName();annotationList.remove(actualName);}return annotationList.isEmpty();}public static IndirectMatch byMultiAnnotationMatch(String... annotationNames) {return new MultiAnnotationMatch(annotationNames);}
    }

    多个类名相等情况匹配

    package cn.git.agent.match;import net.bytebuddy.description.type.TypeDescription;
    import net.bytebuddy.matcher.ElementMatcher;import java.util.Arrays;
    import java.util.List;import static net.bytebuddy.matcher.ElementMatchers.named;/**
    * @description: 多个类名相等情况匹配
    * @program: bank-credit-sy
    * @author: lixuchun
    * @create: 2024-12-24
    */
    public class MultiClassNameMatch implements IndirectMatch {/*** 需要匹配的类名列表*/private List<String> needMatchClassNameList;/*** 构造方法** @param classNames*/private MultiClassNameMatch(String[] classNames) {if (classNames == null || classNames.length == 0) {throw new IllegalArgumentException("classNames can not be null or empty");}this.needMatchClassNameList = Arrays.asList(classNames);}/*** 构建匹配规则* eg: named(CLASS_NAME1).or(named(CLASS_NAME2))** @return*/@Overridepublic ElementMatcher.Junction<? super TypeDescription> buildJunction() {ElementMatcher.Junction<? super TypeDescription> junction = null;for (String className : needMatchClassNameList) {if (junction == null) {junction = named(className);} else {junction = junction.or(named(className));}}return junction;}/*** 用于当前匹配器* 用于判断 typeDescription 是否满足 IndirectMatch 的实现** @param typeDescription* @return*/@Overridepublic boolean isMatch(TypeDescription typeDescription) {/*** 比如 needMatchClassNameList里面是 com.mysql.jdbc.PreparedStatement* 而 typeDescription 是 com.mysql.jdbc.PreparedStatement 返回true,否则返回false*/return needMatchClassNameList.contains(typeDescription.getTypeName());}/*** 静态方法构建匹配规则** @param classNames 匹配类名称* @return*/public static IndirectMatch byMultiClassMatch(String... classNames) {return new MultiClassNameMatch(classNames);}
    }

4.3.2 插件模块

插件模块主要就是继承顶级插件父类,实现对应的类匹配以及方法匹配等,具体实现如下:

4.3.2.1 Mysql5Instrumentation
package cn.git.agent;import cn.git.agent.interceptor.ConstructMethodInterceptPoint;
import cn.git.agent.interceptor.InstanceMethodInterceptPoint;
import cn.git.agent.interceptor.StaticMethodInterceptPoint;
import cn.git.agent.match.ClassMatch;
import cn.git.agent.match.MultiClassNameMatch;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;/*** @description: 定义mysql5的拦截插件* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
public class Mysql5Instrumentation extends AbstractClassEnhancePluginDefine {/*** 拦截的方法*/private static final String INSPECT_EXECUTE_METHOD = "execute";private static final String INSPECT_EXECUTE_QUERY = "executeQuery";private static final String INSPECT_EXECUTE_UPDATE = "executeUpdate";private static final String INSPECT_EXECUTE_LARGE_UPDATE = "executeLargeUpdate";/*** 拦截器类全路径名称*/private static final String INTERCEPTOR_CLASS_NAME = "cn.git.agent.interceptor.Mysql5Interceptor";/*** 拦截类全路径名称*/public static final String PREPARED_STATEMENT_NAME = "com.mysql.jdbc.PreparedStatement";/*** 获取需要拦截的类** @return*/@Overrideprotected ClassMatch enhanceClass() {return MultiClassNameMatch.byMultiClassMatch(PREPARED_STATEMENT_NAME);}/*** 获取需要拦截的方法,实例拦截方法** @return*/@Overrideprotected InstanceMethodInterceptPoint[] getInstanceMethodsInterceptPoints() {return new InstanceMethodInterceptPoint[] {new InstanceMethodInterceptPoint() {/*** 获取需要拦截的方法,作为method()方法的参数** @return*/@Overridepublic ElementMatcher<MethodDescription> getMethodsMatcher() {return ElementMatchers.namedOneOf(INSPECT_EXECUTE_METHOD,INSPECT_EXECUTE_QUERY,INSPECT_EXECUTE_UPDATE,INSPECT_EXECUTE_LARGE_UPDATE);}/*** 获取需要拦截的方法的拦截器** @return*/@Overridepublic String getMethodInterceptor() {return INTERCEPTOR_CLASS_NAME;}}};}/*** 获取构造方法的拦截点** @return*/@Overrideprotected ConstructMethodInterceptPoint[] getConstructMethodInterceptPoint() {return new ConstructMethodInterceptPoint[0];}/*** 获取静态方法拦截点** @return*/@Overrideprotected StaticMethodInterceptPoint[] getStaticMethodInterceptPoint() {return new StaticMethodInterceptPoint[0];}
}
4.3.2.2 springMVC具体实现

mvc比较特殊,需要校验@Controller以及@RestController两个注解标注的类,所以 需要新增两个Instrumentation,并且还有一个公共的SpringMVCCommonInstrumentation,实现公共部分。

package cn.git.agent;import cn.git.agent.interceptor.ConstructMethodInterceptPoint;
import cn.git.agent.interceptor.InstanceMethodInterceptPoint;
import cn.git.agent.interceptor.StaticMethodInterceptPoint;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;import static net.bytebuddy.matcher.ElementMatchers.*;/*** @description: 定义springMVC的拦截插件* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
public abstract class SpringMVCCommonInstrumentation extends AbstractClassEnhancePluginDefine {/*** 拦截的注解开头结尾*/private static final String MAPPING_PACKAGE_PREFIX = "org.springframework.web.bind.annotation";private static final String MAPPING_PACKAGE_SUFFIX = "Mapping";/*** 拦截器类*/public static final String SPRINGMVC_INTERCEPTOR_CLASS = "cn.git.agent.interceptor.SpringMVCInterceptor";/*** 获取需要拦截的方法,实例拦截方法** @return*/@Overrideprotected InstanceMethodInterceptPoint[] getInstanceMethodsInterceptPoints() {return new InstanceMethodInterceptPoint[] {new InstanceMethodInterceptPoint() {@Overridepublic ElementMatcher<MethodDescription> getMethodsMatcher() {return not(isStatic()).and(isAnnotatedWith(nameStartsWith(MAPPING_PACKAGE_PREFIX).and(nameEndsWith(MAPPING_PACKAGE_SUFFIX))));}@Overridepublic String getMethodInterceptor() {return SPRINGMVC_INTERCEPTOR_CLASS;}}};}/*** 获取构造方法的拦截点** @return*/@Overrideprotected ConstructMethodInterceptPoint[] getConstructMethodInterceptPoint() {return new ConstructMethodInterceptPoint[0];}/*** 获取静态方法拦截点** @return*/@Overrideprotected StaticMethodInterceptPoint[] getStaticMethodInterceptPoint() {return new StaticMethodInterceptPoint[0];}
}

@Controller注解的实现

package cn.git.agent;import cn.git.agent.match.ClassMatch;
import cn.git.agent.match.MultiAnnotationMatch;/*** @description: 定义springMVC的Controller拦截插件* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
public class SpringMVCControllerInstrumentation extends SpringMVCCommonInstrumentation {/*** 控制器注解名称*/public static final String CONTROLLER_NAME = "org.springframework.stereotype.Controller";/*** 拦截器类*/public static final String SPRINGMVC_INTERCEPTOR_CLASS = "cn.git.agent.interceptor.SpringMVCInterceptor";/*** 获取需要拦截的类** @return*/@Overrideprotected ClassMatch enhanceClass() {return MultiAnnotationMatch.byMultiAnnotationMatch(CONTROLLER_NAME);}
}

@RestController的实现

package cn.git.agent;import cn.git.agent.match.ClassMatch;
import cn.git.agent.match.MultiAnnotationMatch;/*** @description: 定义springMVC的RestController拦截插件* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
public class SpringMVCRestControllerInstrumentation extends SpringMVCCommonInstrumentation {/*** 控制器注解名称*/public static final String REST_CONTROLLER_NAME = "org.springframework.web.bind.annotation.RestController";/*** 拦截器类*/public static final String SPRINGMVC_INTERCEPTOR_CLASS = "cn.git.agent.interceptor.SpringMVCInterceptor";/*** 获取需要拦截的类** @return*/@Overrideprotected ClassMatch enhanceClass() {return MultiAnnotationMatch.byMultiAnnotationMatch(REST_CONTROLLER_NAME);}
}

4.4 打包插件

4.4.1 插件需求

我们希望项目实现插件打包于skywalking相似,可以生成一个dest目录,放置我们的agent-jar包,并且可以生成一个plugins目录,以便我们之后的插件都可以放置到此目录,将我们的agent也做成一个可以动态插拔的效果。

4.4.2 具体实现

此时我们需要引入一个maven插件maven-antrun-plugin,其配置于apm-agent模块,具体pom配置信息如下所示:

<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><parent><groupId>cn.git.agent</groupId><artifactId>apm-sniffer</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>amp-agent</artifactId><packaging>jar</packaging><name>amp-agent</name><url>http://maven.apache.org</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>cn.git.agent</groupId><artifactId>apm-agent-core</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies><build><plugins><plugin><!-- 用于打包插件 --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.0</version><configuration><archive><manifestEntries><!-- MANIFEST.MF 配置项,指定premain方法所在类 --><Premain-Class>cn.git.agent.CustomSkyWalkingAgent</Premain-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes><Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix></manifestEntries></archive><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs></configuration><executions><execution><id>make-assembly</id><!-- 什么阶段会触发此插件 --><phase>package</phase><goals><!-- 只运行一次 --><goal>single</goal></goals></execution></executions></plugin><!-- 创建目录,拷贝jar包到指定目录 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-antrun-plugin</artifactId><version>1.8</version><executions><execution><!-- 在clean阶段删除dist目录 --><id>clean</id><phase>clean</phase><goals><goal>run</goal></goals><configuration><target><delete dir="${project.basedir}/../../dist"/></target></configuration></execution><execution><!-- 在package阶段创建dist目录,创建/dist/plugins目录,拷贝agent-jar到/dist目录下 --><id>package</id><phase>package</phase><goals><goal>run</goal></goals><configuration><target><mkdir dir="${project.basedir}/../../dist"/><copy file="${project.build.directory}/amp-agent-1.0-SNAPSHOT-jar-with-dependencies.jar" tofile="${project.basedir}/../../dist/apm-agent-1.0.SNAPSHOT-jar-with-dependencies.jar" overwrite="true"></copy><mkdir dir="${project.basedir}/../../dist/plugins"/></target></configuration></execution></executions></plugin></plugins></build>
</project>

4.4.3 打包效果

插件更新完毕后,我们install,观察到dist目录以及plugins目录已经生成,并且jar包也复制到此目录下了
在这里插入图片描述

4.5 获取类型匹配器

我们需要类类型匹配器,其中包含名称以及非名称两种匹配器进行自动联合匹配,在岗进入探针的时候,调用find方法,发现所有的需要加载的类信息,具体的代码实现如下所示:

package cn.git.agent;import cn.git.agent.match.ClassMatch;
import cn.git.agent.match.IndirectMatch;
import cn.git.agent.match.NameMatch;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;import java.util.*;/*** @description: 插件查找器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-25*/
public class PluginFinder {/*** 用于存储ClassMatch为NameMatcher的插件* key: 全类名 ,eg: cn.git.agent.service.impl.UserInfoServiceImpl* value: 处理此类的多个插件列表*/private final Map<String, List<AbstractClassEnhancePluginDefine>> nameMatchDefine = new HashMap<>();/*** 用于存储ClassMatch为IndirectMatch间接匹配器的插件*/private final List<AbstractClassEnhancePluginDefine> signatureMatchDefine = new ArrayList<>();/*** 构造函数** @param pluginDefineList 所有加载的插件*/public PluginFinder(List<AbstractClassEnhancePluginDefine> pluginDefineList) {for (AbstractClassEnhancePluginDefine pluginDefine : pluginDefineList) {// 获取类匹配器ClassMatch classMatch = pluginDefine.enhanceClass();if (classMatch == null) {continue;}// 判断是名称匹配if (classMatch instanceof NameMatch) {NameMatch nameMatch = (NameMatch) classMatch;// computeIfAbsent 方法会检查 nameMatchDefine 中是否存在键为 nameMatch.getClassName() 的条目。// 如果不存在,则使用提供的 lambda 表达式创建一个新的 LinkedList 并将其放入 Map 中,然后返回这个新的 LinkedList// 如果该键已经存在,则直接返回与该键关联的现有列表List<AbstractClassEnhancePluginDefine> enhancePluginDefineList = nameMatchDefine.computeIfAbsent(nameMatch.getClassName(), a -> new LinkedList<>());// 添加插件到名称匹配列表enhancePluginDefineList.add(pluginDefine);} else {// 间接匹配signatureMatchDefine.add(pluginDefine);}}}/*** 构建匹配器,返回已加载最终拼接的类匹配器条件** @return plugin1_junction.or(plugin2_junction.or(plugin3_junction)).....*/public ElementMatcher<? super TypeDescription> buildTypeMatch() {/*** 注释提到“当某个类第一次被加载时,都会回调该方法”,这意味着每当 JVM 加载一个新的类时,ByteBuddy 会调用这个 matches 方法来判断是否对该类进行增强或其他操作。* 例如,如果 nameMatchDefine 中包含 cn.git.TestService,那么当 cn.git.TestService 类被加载时,matches 方法会返回 true,从而触发相应的插件逻辑。*/ElementMatcher.Junction<? super TypeDescription> junction = new ElementMatcher.Junction.AbstractBase<NamedElement>() {@Overridepublic boolean matches(NamedElement target) {// 当某个类第一次被加载时,都会回调该方法 eg: cn.git.TestServicereturn nameMatchDefine.containsKey(target.getActualName());}};// 只增强类,不增强接口junction = junction.and(ElementMatchers.not(ElementMatchers.isInterface()));// 添加间接匹配插件for (AbstractClassEnhancePluginDefine pluginDefine : signatureMatchDefine) {ClassMatch classMatch = pluginDefine.enhanceClass();if (classMatch instanceof IndirectMatch) {IndirectMatch indirectMatch = (IndirectMatch) classMatch;junction = junction.or(indirectMatch.buildJunction());}}return junction;}/*** 通过插件类类型获取插件列表** @param typeDescription* @return*/public List<AbstractClassEnhancePluginDefine> find(TypeDescription typeDescription) {// 返回参数列表List<AbstractClassEnhancePluginDefine> matchedPlugins = new LinkedList<AbstractClassEnhancePluginDefine>();// 获取全类名称String typeName = typeDescription.getTypeName();// 获取名称匹配插件if (nameMatchDefine.containsKey(typeName)) {matchedPlugins.addAll(nameMatchDefine.get(typeName));}// 获取间接匹配插件SignatureMatchDefinefor (AbstractClassEnhancePluginDefine pluginDefine : signatureMatchDefine) {IndirectMatch match = (IndirectMatch) pluginDefine.enhanceClass();if (match.isMatch(typeDescription)) {matchedPlugins.add(pluginDefine);}}return matchedPlugins;}
}

4.6 transform逻辑

transform主要的作用是具体的进行方法的增强处理,我们此处单独定义一个静态类进行实现,仿照skywalking在探针类中实现,使用pluginFinde.find方法获取就提类,然后使用具体类获取类的增强方法,

4.6.1 agent探针新增transform方法

package cn.git.agent;import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;import java.lang.instrument.Instrumentation;
import java.util.List;import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;/*** @description: 仿照skywalking编写的探针入口* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-23*/
public class CustomSkyWalkingAgent {// 插件查找器private static PluginFinder pluginFinder;/*** premain方法,main方法执行之前进行调用,插桩代码入口* @param args 标识外部传递参数* @param instrumentation 插桩对象*/public static void premain(String args, Instrumentation instrumentation) {// 创建AgentBuilder对象, 用于构建动态类// 默认开启类型验证,ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(true));AgentBuilder builder = new AgentBuilder.Default(byteBuddy);try {pluginFinder = new PluginFinder(null);} catch (Exception e) {e.printStackTrace();return;}// 忽略拦截的包builder.ignore(nameStartsWith("net.bytebuddy.").or(nameStartsWith("org.slf4j.")).or(nameStartsWith("org.groovy.")).or(nameContains("javassist")).or(nameContains(".asm.")).or(nameContains(".reflectasm.")).or(nameStartsWith("sun.reflect")).or(ElementMatchers.isSynthetic()));builder.type(pluginFinder.buildTypeMatch()).transform(null).installOn(instrumentation);// 安装builder.installOn(instrumentation);}/*** 方法拦截器,拼接对应类拦截后的方法拦截条件拼接*/private static class Transformer implements AgentBuilder.Transformer {/*** 拦截方法* 正常返回   DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition*          ReceiverTypeDefinition 继承 ReceiverTypeDefinition<U> extends MethodDefinition*          MethodDefinition 继承 MethodDefinition<S> extends Builder*          所以返回的 Builder 类型即可* @param builder* @param typeDescription*          可以获取拦截类名 this.getClass().getName() eg: cn.git.agent.service.impl.UserInfoServiceImpl*          可以获取增强插件 TypeName eg: com.mysql.jdbc.PreparedStatement** @param classLoader* @param javaModule* @return*/@Overridepublic DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,TypeDescription typeDescription,ClassLoader classLoader,JavaModule javaModule) {System.out.println("Transformer方法加载 : typeDescription.getActualName: " + typeDescription.getActualName());System.out.println("Transformer方法加载 : typeDescription.getTypeName: " + typeDescription.getTypeName());// 获取插件列表List<AbstractClassEnhancePluginDefine> pluginDefineList = pluginFinder.find(typeDescription);if (pluginDefineList.size() > 0) {DynamicType.Builder<?> newBuilder = builder;for (AbstractClassEnhancePluginDefine pluginDefine : pluginDefineList) {DynamicType.Builder<?> possiableBuilder = pluginDefine.define(typeDescription, newBuilder, classLoader);if (possiableBuilder != null) {newBuilder = possiableBuilder;}}return newBuilder;}return builder;}}
}

4.6.2 插件顶级父类define实现

我们在顶级父类中,实现了 define 方法, 此方法中获取具体增强类eg: com.mysql.jdbc.PreparedStatement 还有具体的插件类cn.git.agent.Mysql5Instrumentation,然后分别定义抽象的静态方法增强方法以及实例方法增强方法,使用子类进行具体的实现。具体方法实现代码如下:

/*** 增强类的主入口** @param typeDescription* @param builder* @param classLoader* @return*/
public DynamicType.Builder<?> define(TypeDescription typeDescription,DynamicType.Builder<?> builder,ClassLoader classLoader) {// 增强插件类的名字 eg: cn.git.agent.Mysql5InstrumentationString pluginsDefineClassName = this.getClass().getName();// 要增强的类名称 eg: com.mysql.jdbc.PreparedStatementString typeName = typeDescription.getTypeName();System.out.println("准备使用" + pluginsDefineClassName + "增强" + typeName + "开始");DynamicType.Builder<?> newBuilder = this.enhance(typeDescription, builder, classLoader);System.out.println("使用" + pluginsDefineClassName + "增强" + typeName + "成功");return newBuilder;
}private DynamicType.Builder<?> enhance(TypeDescription typeDescription,DynamicType.Builder<?> builder,ClassLoader classLoader) {// 增强静态方法builder = this.enhanceClass(typeDescription, builder, classLoader);// 增强实例方法builder = this.enhanceInstance(typeDescription, builder, classLoader);return builder;
}/*** 增强实例方法* @param typeDescription* @param builder* @param classLoader* @return*/
protected abstract DynamicType.Builder<?> enhanceInstance(TypeDescription typeDescription,DynamicType.Builder<?> builder,ClassLoader classLoader);/*** 增强静态方法* @param typeDescription* @param builder* @param classLoader* @return*/
protected abstract DynamicType.Builder<?> enhanceClass(TypeDescription typeDescription,DynamicType.Builder<?> builder,ClassLoader classLoader);

4.7 静态构造实例方法增强

我们在顶级插件父类新增了两个抽象方法,enhanceInstance,以及 enhanceClass用于实现实际增强方法实现,在此基础上我们新增ClassEnhancePluginDefine类,继承顶级父类,并且实现这两个抽象方法,然后基础插件类便直接实现此类即可,无需实现顶级父类,具体逻辑代码如下所示:

4.7.1 ClassEnhancePluginDefine

package cn.git.agent.enhance;import cn.git.agent.AbstractClassEnhancePluginDefine;
import cn.git.agent.interceptor.ConstructMethodInterceptPoint;
import cn.git.agent.interceptor.InstanceMethodInterceptPoint;
import cn.git.agent.interceptor.StaticMethodInterceptPoint;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;/** * @description: 所有插件都继承AbstractClassEnhancePluginDefine* 此类完成了enhanceInstance,以及 enhanceStaticClass方法,完成增强(transform指定method以及interceptor)* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-25*/
public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePluginDefine {/*** 增强实例与构造方法* builder.method(* 	ElementMatchers.namedOneOf(INSPECT_EXECUTE_METHOD,* 			INSPECT_EXECUTE_QUERY,* 			INSPECT_EXECUTE_UPDATE,* 			INSPECT_EXECUTE_LARGE_UPDATE)* )* .intercept(MethodDelegation.to(interceptor));** @param typeDescription* @param builder* @param classLoader* @return*/@Overrideprotected DynamicType.Builder<?> enhanceInstance(TypeDescription typeDescription,DynamicType.Builder<?> builder,ClassLoader classLoader) {// 获取实例以及构造方法拦截点InstanceMethodInterceptPoint[] instanceMethodsInterceptPoints = this.getInstanceMethodsInterceptPoints();ConstructMethodInterceptPoint[] constructMethodInterceptPoints = this.getConstructMethodInterceptPoint();// 是否存在构造器拦截点boolean existsConstructorInterceptPoint = false;if (constructMethodInterceptPoints != null && constructMethodInterceptPoints.length > 0) {existsConstructorInterceptPoint = true;}// 是否存在实例拦截点boolean existsInstanceInterceptPoint = false;if (instanceMethodsInterceptPoints != null && instanceMethodsInterceptPoints.length > 0) {existsInstanceInterceptPoint = true;}// 如果不存在拦截点,则直接返回if (!existsConstructorInterceptPoint && !existsInstanceInterceptPoint) {return builder;}// 拼装方法拦截点String typeName = typeDescription.getTypeName();// 开始增强实例方法if (existsInstanceInterceptPoint) {for (InstanceMethodInterceptPoint point : instanceMethodsInterceptPoints) {// 静态方法匹配器ElementMatcher<MethodDescription> methodsMatcher = point.getMethodsMatcher();// 拦截器名称String interceptorClassName = point.getMethodInterceptor();if (interceptorClassName == null || interceptorClassName.trim().isEmpty()) {throw new RuntimeException("要增强的实例方法类[{" + typeName + "}]未指定拦截器");}builder = builder.method(ElementMatchers.not(ElementMatchers.isStatic()).and(methodsMatcher)).intercept(MethodDelegation.withDefaultConfiguration().to(new InstanceMethodsInterceptor(interceptorClassName, classLoader)));}}if (existsConstructorInterceptPoint) {for (ConstructMethodInterceptPoint constructPoint : constructMethodInterceptPoints) {// 静态方法匹配器ElementMatcher<MethodDescription> methodsMatcher = constructPoint.getConstructMethodsMatcher();// 拦截器名称String interceptorClassName = constructPoint.getMethodInterceptor();if (interceptorClassName == null || interceptorClassName.trim().isEmpty()) {throw new RuntimeException("要增强的构造方法类[{" + typeName + "}]未指定拦截器");}// 创建builder信息,指定拦截器信息builder = builder.method(methodsMatcher).intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration().to(new ConstructorMethodsInterceptor(interceptorClassName, classLoader))));}}return null;}/*** 增强静态方法* builder.method(* 	ElementMatchers.namedOneOf(INSPECT_EXECUTE_METHOD,* 			INSPECT_EXECUTE_QUERY,* 			INSPECT_EXECUTE_UPDATE,* 			INSPECT_EXECUTE_LARGE_UPDATE)* )* .intercept(MethodDelegation.to(interceptor));* @param typeDescription* @param builder* @param classLoader* @return*/@Overrideprotected DynamicType.Builder<?> enhanceStaticClass(TypeDescription typeDescription,DynamicType.Builder<?> builder,ClassLoader classLoader) {// 获取我们的静态拦截点StaticMethodInterceptPoint[] staticMethodInterceptPoint = this.getStaticMethodInterceptPoint();if (staticMethodInterceptPoint == null || staticMethodInterceptPoint.length == 0) {return builder;}// 拼装方法拦截点String typeName = typeDescription.getTypeName();for (StaticMethodInterceptPoint point : staticMethodInterceptPoint) {// 静态方法匹配器ElementMatcher<MethodDescription> staticMethodsMatcher = point.getStaticMethodsMatcher();// 拦截器名称String interceptorClassName = point.getMethodInterceptor();if (interceptorClassName == null || interceptorClassName.trim().isEmpty()) {throw new RuntimeException("要增强的类[{" + typeName + "}]未指定拦截器");}builder = builder.method(ElementMatchers.isStatic().and(staticMethodsMatcher)).intercept(MethodDelegation.withDefaultConfiguration().to(new StaticMethodsInterceptor(interceptorClassName, classLoader)));}return builder;}
}

4.7.2 MethodsInterceptor

增强静态构造以及实例方法我们主要新增了三个MethodsInterceptor 类作为三个方法byteBuddy拦截器,里面的逻辑就是我们正常的拦截方法,传入class参数,传入params参数等,可以执行父级的call方法,在前后以及异常进行增,三种方法的方法增强类代码如下:

  • 静态方法拦截类

    package cn.git.agent.enhance;import net.bytebuddy.implementation.bind.annotation.*;import java.lang.reflect.Method;
    import java.util.concurrent.Callable;/*** @description: 静态方法byteBuddy拦截器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-25*/
    public class StaticMethodsInterceptor {/*** 静态方法环绕通知器*/private StaticMethodAroundInterceptor aroundInterceptor;/*** 构造方法** @param interceptorClassName* @param classLoader*/public StaticMethodsInterceptor(String interceptorClassName,ClassLoader classLoader) {}/*** 被标注 RuntimeType 注解的方法就是拦截方法,此时返回的值与返回参数可以与被拦截的方法不一致* byteBuddy会在运行期间给被拦截的方法参数进行赋值* @return*/@RuntimeTypepublic Object intercept(@Origin Class<?> clazz,@Origin Method targetMethod,@AllArguments Object[] targetMethodArgs,@SuperCall Callable<?> superCall) throws Throwable {Object call = null;try {aroundInterceptor.beforeMethod(clazz, targetMethod, targetMethodArgs, targetMethod.getParameterTypes());} catch (Exception e) {e.printStackTrace();System.out.println("class [" + clazz.getName() + "] before exec method [" + targetMethod.getName() + "] interceptor failure [" + e + "]");}try {call = superCall.call();} catch (Throwable e) {try {aroundInterceptor.handleMethodException(clazz,targetMethod,targetMethodArgs,targetMethod.getParameterTypes(),e);} catch (Throwable t) {System.out.println("class [" + clazz.getName() + "] handle static [" + targetMethod.getName() + "] interceptor failure [" + e + "]");e.printStackTrace();}throw e;} finally {try {aroundInterceptor.afterMethod(clazz,targetMethod,targetMethodArgs,targetMethod.getParameterTypes(),call);} catch (Throwable e) {e.printStackTrace();System.out.println("class [" + clazz.getName() + "] after exec method [" + targetMethod.getName() + "] interceptor failure [" + e + "]");}System.out.println("targetMethodName : " + targetMethod.getName());}return call;}
    }
    
  • 实例方法拦截类

    package cn.git.agent.enhance;import net.bytebuddy.implementation.bind.annotation.*;import java.lang.reflect.Method;
    import java.util.concurrent.Callable;/*** @description: 实例方法byteBuddy拦截器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-25*/
    public class InstanceMethodsInterceptor {/*** 实例方法环绕通知器*/private InstanceMethodAroundInterceptor aroundInterceptor;/*** 构造方法** @param interceptorClassName* @param classLoader*/public InstanceMethodsInterceptor(String interceptorClassName,ClassLoader classLoader) {}/*** 被标注 RuntimeType 注解的方法就是拦截方法,此时返回的值与返回参数可以与被拦截的方法不一致* byteBuddy会在运行期间给被拦截的方法参数进行赋值* @return*/@RuntimeTypepublic Object intercept(@This Object targetObject,@Origin Method targetMethod,@AllArguments Object[] targetMethodArgs,@SuperCall Callable<?> superCall) throws Throwable {Object call = null;// 执行后前置方法try {aroundInterceptor.beforeMethod(targetObject, targetMethod, targetMethodArgs, targetMethod.getParameterTypes());} catch (Exception e) {e.printStackTrace();System.out.println("class [" + targetObject.getClass().getName() + "] before exec method [" + targetMethod.getName() + "] interceptor failure [" + e + "]");}// 执行原方法try {call = superCall.call();} catch (Throwable e) {try {// 执行异常处理方法aroundInterceptor.handleMethodException(targetObject,targetMethod,targetMethodArgs,targetMethod.getParameterTypes(),e);} catch (Throwable t) {System.out.println("class [" + targetObject.getClass().getName() + "] handle instance [" + targetMethod.getName() + "] interceptor failure [" + e + "]");e.printStackTrace();}throw e;} finally {try {// 执行后置方法aroundInterceptor.afterMethod(targetObject,targetMethod,targetMethodArgs,targetMethod.getParameterTypes(),call);} catch (Throwable e) {e.printStackTrace();System.out.println("class [" + targetObject.getClass().getName() + "] after exec method [" + targetMethod.getName() + "] interceptor failure [" + e + "]");}System.out.println("targetMethodName : " + targetMethod.getName());}return call;}
    }
    
  • 构造方法拦截类

    package cn.git.agent.enhance;import net.bytebuddy.implementation.bind.annotation.AllArguments;
    import net.bytebuddy.implementation.bind.annotation.RuntimeType;
    import net.bytebuddy.implementation.bind.annotation.This;/*** @description: 构造方法拦截器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-25*/
    public class ConstructorMethodsInterceptor {/*** 静态方法环绕通知器*/private ConstructorMethodAroundInterceptor aroundInterceptor;/*** 构造方法** @param interceptorClassName* @param classLoader*/public ConstructorMethodsInterceptor(String interceptorClassName,ClassLoader classLoader) {}/*** 被标注 RuntimeType 注解的方法就是拦截方法,此时返回的值与返回参数可以与被拦截的方法不一致* byteBuddy会在运行期间给被拦截的方法参数进行赋值* @return*/@RuntimeTypepublic Object intercept(@This Object obj,@AllArguments Object[] allArguments) throws Throwable {Object call = null;try {aroundInterceptor.onConstruct(obj, allArguments);} catch (Throwable t) {t.printStackTrace();}return call;}
    }

在此基础上,我们还定义了三个环绕切面类 MethodAroundInterceptor,其中切面类里面有前置方法,后置方法以及异常执行方法,可以在增强方法中进行执行,那么我们具体实现代码如下:

  • 静态方法环绕执行拦截器

    package cn.git.agent.enhance;import java.lang.reflect.Method;/*** @description: 静态方法的interceptor必须实现此接口* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-25*/
    public interface StaticMethodAroundInterceptor {/*** 前置方法* @param clazz* @param method* @param allArguments* @param paramTypes*/void beforeMethod(Class clazz,Method method,Object[] allArguments,Class<?>[] paramTypes);/*** 后置方法,最終通知,不管原方法是否執行都會執行,finally裡面邏輯** @param clazz* @param method* @param allArguments* @param paramTypes* @param ret*/void afterMethod(Class clazz,Method method,Object[] allArguments,Class<?>[] paramTypes,Object ret);/*** 异常方法* @param clazz* @param method* @param allArguments* @param paramTypes* @param t*/void handleMethodException(Class clazz,Method method,Object[] allArguments,Class<?>[] paramTypes,Throwable t);
    }
  • 实例方法环绕拦截器

    package cn.git.agent.enhance;import java.lang.reflect.Method;/*** @description: 静态方法的interceptor必须实现此接口* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-25*/
    public interface InstanceMethodAroundInterceptor {/*** 前置方法* @param objectInstance* @param method* @param allArguments* @param paramTypes*/void beforeMethod(Object objectInstance,Method method,Object[] allArguments,Class<?>[] paramTypes);/*** 后置方法,最終通知,不管原方法是否執行都會執行,finally裡面邏輯** @param objectInstance* @param method* @param allArguments* @param paramTypes* @param ret*/void afterMethod(Object objectInstance,Method method,Object[] allArguments,Class<?>[] paramTypes,Object ret);/*** 异常方法* @param objectInstance* @param method* @param allArguments* @param paramTypes* @param t*/void handleMethodException(Object objectInstance,Method method,Object[] allArguments,Class<?>[] paramTypes,Throwable t);
    }
    
  • 构造方法实例拦截器

    package cn.git.agent.enhance;/*** @description: 构造方法拦截器环绕通知类,构造方法拦截器必须实现此接口* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-25*/
    public interface ConstructorMethodAroundInterceptor {/*** 拦截构造方法, 在构造方法执行后调用** @param obj               拦截到的对象,构造器返回对象* @param allArguments      拦截到的所有参数*/void onConstruct(Object obj,Object[] allArguments);
    }
    

4.7.3 基础插件实现环绕增强方法

我们基础插件需要实现我们的环绕增强方法,我们的主要逻辑就在这实现的方法之中,我们的mysql以及springMVC具体的实现逻辑简单逻辑如下:

  • mysql5环绕增强方法

    package cn.git.agent.interceptor;import cn.git.agent.enhance.InstanceMethodAroundInterceptor;import java.lang.reflect.Method;/*** @description: mysql5定义拦截器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
    public class Mysql5Interceptor implements InstanceMethodAroundInterceptor {/*** 前置方法** @param objectInstance* @param method* @param allArguments* @param paramTypes*/@Overridepublic void beforeMethod(Object objectInstance, Method method, Object[] allArguments, Class<?>[] paramTypes) {System.out.println("mysql5 具体方法 beforeMethod 执行开始");}/*** 后置方法,最終通知,不管原方法是否執行都會執行,finally裡面邏輯** @param objectInstance* @param method* @param allArguments* @param paramTypes* @param ret*/@Overridepublic void afterMethod(Object objectInstance, Method method, Object[] allArguments, Class<?>[] paramTypes, Object ret) {System.out.println("mysql5 具体方法 afterMethod 执行开始");}/*** 异常方法** @param objectInstance* @param method* @param allArguments* @param paramTypes* @param t*/@Overridepublic void handleMethodException(Object objectInstance, Method method, Object[] allArguments, Class<?>[] paramTypes, Throwable t) {System.out.println("mysql5 具体方法 handleMethodException 执行开始 error : " + t.getMessage());}
    }
    
  • springMVC的环绕增强方法

    package cn.git.agent.interceptor;import cn.git.agent.enhance.InstanceMethodAroundInterceptor;import java.lang.reflect.Method;/*** @description: spirngMVC拦截器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-24*/
    public class SpringMVCInterceptor implements InstanceMethodAroundInterceptor {/*** 前置方法** @param objectInstance* @param method* @param allArguments* @param paramTypes*/@Overridepublic void beforeMethod(Object objectInstance, Method method, Object[] allArguments, Class<?>[] paramTypes) {System.out.println("SpringMVCInterceptor 具体方法 beforeMethod 执行开始");}/*** 后置方法,最終通知,不管原方法是否執行都會執行,finally裡面邏輯** @param objectInstance* @param method* @param allArguments* @param paramTypes* @param ret*/@Overridepublic void afterMethod(Object objectInstance, Method method, Object[] allArguments, Class<?>[] paramTypes, Object ret) {System.out.println("SpringMVCInterceptor 具体方法 afterMethod 执行开始");}/*** 异常方法** @param objectInstance* @param method* @param allArguments* @param paramTypes* @param t*/@Overridepublic void handleMethodException(Object objectInstance, Method method, Object[] allArguments, Class<?>[] paramTypes, Throwable t) {System.out.println("SpringMVCInterceptor 具体方法 handleMethodException 执行开始 error: " + t.getMessage());}
    }
    

4.8 扩展新属性存储额外信息

我们在实例类型以及构造器类型方法调用的时候,before,handle以及after方法调用,我们希望他们可以进行中间值的传递,比如在before设置一个值,在after中进行使用,那么我们需要在增强类中新增一个比较特殊的属性信息,这个属性需要实现get set方法。

并且我们新增的时候每一个类只能新增一次,否则同一属性新增到类中会有问题。首先我们新增一个 EnhancedContext 用于设置增强以及扩展属性值

package cn.git.agent.enhance;/** * @description: 处理一个类的上下文状态* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-26*/
public class EnhancedContext {/*** 是否被增强*/private boolean isEnhanced = false;/*** 是否被扩展,新增属性 CONTEXT_ATTR_NAME*/private boolean objectExtended = false;public boolean isEnhanced() {return isEnhanced;}public boolean isObjectExtended() {return objectExtended;}public void initializationStageCompleted() {this.isEnhanced = true;}public void objectExtendedCompleted() {this.objectExtended = true;}
}

我们还需要新增一个接口,接口定义了新增字段,并且还要新增字段的get 以及set方法,我们先新增一个基础接口类

package cn.git.agent.enhance;/*** @description: 被增强的实例信息,所有需要增强构造或者实例的字节码都需要实现此接口* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-26*/
public interface EnhancedInstance {/*** 获取被增强的实例* @return*/Object getSkyWalkingDynamicField();/*** 设置被增强的参数值* @param value*/void setSkyWalkingDynamicField(Object value);}

我们一个类执行处理一次,所以我们在最开始的agent类入口新增一个EnhancedContext,后期transformer在操作一个typeDescription都是对应一个EnhancedContext,这样就可以判定全局唯一执行一次。

@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,TypeDescription typeDescription,ClassLoader classLoader,JavaModule javaModule) {System.out.println("Transformer方法加载 : typeDescription.getActualName: " + typeDescription.getActualName());System.out.println("Transformer方法加载 : typeDescription.getTypeName: " + typeDescription.getTypeName());// 获取插件列表List<AbstractClassEnhancePluginDefine> pluginDefineList = pluginFinder.find(typeDescription);if (pluginDefineList.size() > 0) {// 创建动态类DynamicType.Builder<?> newBuilder = builder;// 创建EnhancedContext对象EnhancedContext enhancedContext = new EnhancedContext();for (AbstractClassEnhancePluginDefine pluginDefine : pluginDefineList) {DynamicType.Builder<?> possiableBuilder = pluginDefine.define(typeDescription, newBuilder, classLoader, enhancedContext);if (possiableBuilder != null) {newBuilder = possiableBuilder;}}// 打印增强信息if (enhancedContext.isEnhanced()) {System.out.println("使用" + pluginDefineList.get(0).getClass().getName() + "增强" + typeDescription.getActualName() + "成功");}return newBuilder;}return builder;
}

我们修改了pluginDefine.define的方法参数,新增了enhancedContext,并且后面的enhance方法我们也有变动,在define方法中,enhance方法执行完毕我们要设置增强类型设置true已增强状态

public DynamicType.Builder<?> define(TypeDescription typeDescription,DynamicType.Builder<?> builder,ClassLoader classLoader,EnhancedContext enhancedContext) {// 增强插件类的名字 eg: cn.git.agent.Mysql5InstrumentationString pluginsDefineClassName = this.getClass().getName();// 要增强的类名称 eg: com.mysql.jdbc.PreparedStatementString typeName = typeDescription.getTypeName();System.out.println("准备使用" + pluginsDefineClassName + "增强" + typeName + "开始");DynamicType.Builder<?> newBuilder = this.enhance(typeDescription, builder, classLoader, enhancedContext);// 调用初始化完成,增强类型设置true已增强enhancedContext.initializationStageCompleted();System.out.println("使用" + pluginsDefineClassName + "增强" + typeName + "成功");return newBuilder;
}

在我们的enhanceInstance中,我们需要判定类是否已经新增了属性,被增强了,如果没有则新增自定义属性值,新增get set方法

// 为字节码新增属性,三个参数 名称,类型,修饰符 private | volatile ,并且新增get set方法
// 对于同一个typeDescription,只能定义一个属性,否则会报错,执行一次
/*** isAssignableTo作用* 如果 typeDescription 描述的是 EnhancedInstance 本身,返回 true。* 如果 typeDescription 描述的是 EnhancedInstance 的子类或实现了 EnhancedInstance 接口的类,也返回 true。* 否则返回 false。*/
if (!typeDescription.isAssignableTo(EnhancedInstance.class)) {// 没有被扩展过判定if (!enhancedContext.isObjectExtended()) {builder.defineField(CONTEXT_ATTR_NAME,Object.class,Opcodes.ACC_PRIVATE | Opcodes.ACC_VOLATILE)// 指定实现的接口,get set.implement(EnhancedInstance.class).intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME));// 修改扩展状态为已扩展过enhancedContext.objectExtendedCompleted();}
}

我们修改构造方法以及实例方法增强interceptor,修改targetObject类型为EnhancedInstance,并且修改before,after以及exception方法类型,同样需要修改已经实现的mysql以及mvc对应的 Mysql5Interceptor以及 SpringMVCInterceptor参数类型为 EnhancedInstance,此部分代码就不再赘述了。

4.9 自定义类加载器加载插件

我们需要新增一个类加载器,我们需要加载我们打包后的plugins目录中的jar包文件,而且我们自定义的jar包文件中,resources目录下我们新增了一个自定义的配置描述文件,描述我们每个插件的Instrumentation类全名地址,这样可以使用类全名找到每个插件的定义内容,然后每个插件都可以使用顶层父类接收,这样便可以加载全部插件的基本信息。

首先我们新增一个PluginBootstrap启动加载类,用于读取插件的jar包

package cn.git.agent;import cn.git.agent.loader.AgentClassLoader;import java.net.URL;
import java.util.ArrayList;
import java.util.List;/** * @description: 插件启动类* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-26*/
public class PluginBootstrap {/*** 加载所有插件* 因为是自定义路径下的jar包,所以需要手动加载*  1.获取到agent-jar的路径*  2.使用自定义类加载器加载插件** @return*/public List<AbstractClassEnhancePluginDefine> loadPlugins() {AgentClassLoader.initDefaultClassLoader();PluginResourceResolver resourceResolver = new PluginResourceResolver();List<URL> resources = resourceResolver.getResources();if (resources == null && resources.size() == 0) {System.out.println("no plugins define file git-plugins.def found !");return new ArrayList<>();}for (URL resource : resources) {try {PluginCfg.INSTANCE.load(resource.openStream());} catch (Exception e) {e.printStackTrace();System.out.println("load plugins define file git-plugins.def failed !");}}// 拿到全类目,通过反射获取到实例对象,使用AbstractClassEnhancePluginDefine进行接收List<PluginDefine> pluginClassList = PluginCfg.INSTANCE.getPluginClassList();List<AbstractClassEnhancePluginDefine> pluginDefineList = new ArrayList<>();for (PluginDefine pluginDefine : pluginClassList) {try {AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine) Class.forName(pluginDefine.getDefineClass(),true,AgentClassLoader.getDefaultClassLoader()).newInstance();pluginDefineList.add(plugin);} catch (Exception e) {e.printStackTrace();System.out.println("load plugin define class " + pluginDefine.getDefineClass() + " failed !");}}return pluginDefineList;}
}

新增一个类加载器的子类,用于新增一个类加载器,加载自己自定义的目录jar文件信息

package cn.git.agent.loader;import cn.git.agent.PluginBootstrap;
import cn.git.agent.boot.AgentPackagePath;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;/*** @description: 用于加载插件和插件的拦截器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-26*/
public class AgentClassLoader extends ClassLoader {/*** 默认的类加载器* 用于定义 Mysql5Instrumentation等类,除了插件的interceptor*/private static AgentClassLoader DEFAULT_CLASS_LOADER;/*** 插件的类路径*/private List<File> classPath;private List<Jar> allJars;/*** 加载jar文件的锁,避免多次加载*/private ReentrantLock jarScanLock = new ReentrantLock();/*** 构造函数** @param parent*/public AgentClassLoader(ClassLoader parent) {super(parent);// agent.jar的目录File agentJarDir = AgentPackagePath.getPath();classPath = new LinkedList<>();classPath.add(new File(agentJarDir, "plugins"));}public static AgentClassLoader getDefaultClassLoader() {return DEFAULT_CLASS_LOADER;}/*** 初始化默认的类加载器*/public static void initDefaultClassLoader() {if (DEFAULT_CLASS_LOADER == null) {DEFAULT_CLASS_LOADER = new AgentClassLoader(PluginBootstrap.class.getClassLoader());}}/*** 重写findClass方法*  loadClass -> 自己回调 findClass(自己定义自己的类加载器) -> defineClass* @param name* @return* @throws ClassNotFoundException*/@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {List<Jar> allJars = getAllJars();// 获取类名String path = name.replace(".", "/").concat(".class");for (Jar jar : allJars) {JarEntry entry = jar.jarFile.getJarEntry(path);if (entry == null) {continue;}// 获取jar文件try {URL url = new URL("jar:file" + jar.sourceFile.getAbsolutePath() + "!/" + path);byte[] bytes = IOUtils.toByteArray(url);return defineClass(name, bytes, 0, bytes.length);} catch (Exception e) {e.printStackTrace();System.out.println("Can not load jar file:" + jar.sourceFile.getAbsolutePath());}}throw new ClassNotFoundException("can not find " + name);}/*** 重写getResources方法** @param name* @return* @throws IOException*/@Overridepublic Enumeration<URL> getResources(String name) throws IOException {List<URL> allResources = new LinkedList<>();List<Jar> allJars = getAllJars();for (Jar jar : allJars) {JarEntry jarEntry = jar.jarFile.getJarEntry(name);if (jarEntry != null) {allResources.add(new URL("jar:file" + jar.sourceFile.getAbsolutePath() + "!/" + name));}}// 迭代Iterator<URL> iterator = allResources.iterator();return new Enumeration<URL>() {@Overridepublic boolean hasMoreElements() {return iterator.hasNext();}@Overridepublic URL nextElement() {return iterator.next();}};}/*** 重写getResource方法** @param name* @return*/@Overridepublic URL getResource(String name) {for (Jar jar : allJars) {JarEntry jarEntry = jar.jarFile.getJarEntry(name);if (jarEntry != null) {try {return new URL("jar:file" + jar.sourceFile.getAbsolutePath() + "!/" + name);} catch (Exception e) {System.out.println("Can not load jar file:" + jar.sourceFile.getAbsolutePath());e.printStackTrace();}}}return null;}/*** 获取所有的jar文件* @return*/private List<Jar> getAllJars() {if (allJars == null) {jarScanLock.lock();try {// 避免多次加载if (allJars == null) {allJars = doGetJar();}} finally {jarScanLock.unlock();}}return allJars;}/*** 获取所有的jar文件** @return*/private List<Jar> doGetJar() {// 返回参数List<Jar> list = new LinkedList<>();//eg: D:/apm-sniffer/apm-agent-core/target/classes/pluginsfor (File path : classPath) {if (path.exists() && path.isDirectory()) {// jarFile名称String[] jarFileNames = path.list(new FilenameFilter() {@Overridepublic boolean accept(File dir, String name) {return name.endsWith(".jar");}});if (ArrayUtils.isEmpty(jarFileNames)) {continue;}for (String jarFileName : jarFileNames) {File jarSourceFile = new File(path, jarFileName);Jar jar = null;try {jar = new Jar(new JarFile(jarSourceFile), jarSourceFile);list.add(jar);System.out.println("Load jar file:" + jarSourceFile.getAbsolutePath());} catch (IOException e) {e.printStackTrace();System.out.println("Can not load jar file:" + jarSourceFile.getAbsolutePath());}}}}return list;}/** * @description: 插件的类加载器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-26*/@RequiredArgsConstructorprivate static class Jar {/*** jar文件*/private final JarFile jarFile;/*** jar文件对应的目录*/private final File sourceFile;}
}

新增一个PluginResourceResolver,获取插件资源目录(/plugins目录下的所有jar内部的git-plugins.def文件)

package cn.git.agent;import cn.git.agent.loader.AgentClassLoader;import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;/** * @description: 插件资源解析器* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-27*/
public class PluginResourceResolver {/*** 获取插件资源目录(/plugins目录下的所有jar内部的git-plugins.def文件)** @return*/public List<URL> getResources() {// 返回参数List<URL> cfgUrlPathList = new ArrayList<>();try {// 获取插件类加载器,需要重写ClassLoader以及getResources方法Enumeration<URL> urls = AgentClassLoader.getDefaultClassLoader().getResources("git-plugins.def");while (urls.hasMoreElements()) {URL url = urls.nextElement();cfgUrlPathList.add(url);System.out.println("获取插件资源 [git-plugins.def] 成功, url: " + url);}return cfgUrlPathList;} catch (Exception e) {e.printStackTrace();System.out.println("获取插件资源 [git-plugins.def] 失败");}return null;}
}

新增两个接收参数类,PluginDefine,PluginCfg 插件配置类,PluginCfg 将每个插件中的git-plugins.def转义为PluginDefine

package cn.git.agent;import org.apache.commons.lang3.StringUtils;/*** @description: 插件定义类,git- plugins.def 文件中的对应关系* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-27*/
public class PluginDefine {/*** 插件名称 eg: mysql5,springmvc 。。。。。*/private String name;/*** The class name of plugin defined.*/private String defineClass;private PluginDefine(String name, String defineClass) {this.name = name;this.defineClass = defineClass;}public static PluginDefine build(String define) {if (StringUtils.isEmpty(define)) {throw new RuntimeException(define);}String[] pluginDefine = define.split("=");if (pluginDefine.length != 2) {throw new RuntimeException(define);}String pluginName = pluginDefine[0];String defineClass = pluginDefine[1];return new PluginDefine(pluginName, defineClass);}public String getDefineClass() {return defineClass;}public String getName() {return name;}}

PluginCfg 主要内容如下:

package cn.git.agent;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;/** * @description: 插件配置文件,转义git-plugins.def文件到PluginDefine实例* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-27*/
public enum PluginCfg {INSTANCE;/*** 存放所有插件.def文件中,构造出来的 PluginDefine 实例*/private List<PluginDefine> pluginClassList = new ArrayList<PluginDefine>();/*** 加载插件配置文件,转换为PluginDefine实例** @param input* @throws IOException*/void load(InputStream input) throws IOException {try {BufferedReader reader = new BufferedReader(new InputStreamReader(input));String pluginDefine;while ((pluginDefine = reader.readLine()) != null) {try {if (pluginDefine.trim().length() == 0 || pluginDefine.startsWith("#")) {continue;}PluginDefine plugin = PluginDefine.build(pluginDefine);pluginClassList.add(plugin);} catch (Exception e) {System.out.println("load plugin error: " + pluginDefine);}}} finally {input.close();}}/*** 获取所有插件* @return*/public List<PluginDefine> getPluginClassList() {return pluginClassList;}
}

4.10 拦截器类加载器

我们定义一个类,进行拦截类加载器,自定义类加载器,要想插件拦截器中能够访问到被拦截的类,需要是同一个类加载器或者子类类加载器

eg : classA被拦截,ClassLoaderA加载,那么拦截器也必须是ClassLoaderA加载,否则无法访问到classA,

我们在enhance 对应Interceptor传入classLoader中,对应typeDescription的classLoader,所以我们需要定义其相同类或者其子类的类加载器才可以,具体实现代码如下:

package cn.git.agent.loader;/*** @description: 用于加载插件当中的拦截器interceptor* @program: bank-credit-sy* @author: lixuchun* @create: 2024-12-27*/
public class InterceptorInstanceLoader {/*** 加载拦截器* @param interceptorClassName 拦截器全类目* @param targetClassLoader 自定义类加载器,要想插件拦截器中能够访问到被拦截的类,需要是同一个类加载器或者子类类加载器*                    eg : classA被拦截,ClassLoaderA加载,那么拦截器也必须是ClassLoaderA加载,否则无法访问到classA* @param <T> ConstructorMethodAroundInterceptor, MethodAroundInterceptor,* @return*/public static <T> T load(String interceptorClassName, ClassLoader targetClassLoader) throws ClassNotFoundException,IllegalAccessException, InstantiationException {if (targetClassLoader == null) {targetClassLoader = InterceptorInstanceLoader.class.getClassLoader();}AgentClassLoader agentClassLoader = new AgentClassLoader(targetClassLoader);Object o = Class.forName(interceptorClassName, true, agentClassLoader).newInstance();return (T) o;}
}

对应的三个MethodsInterceptor里面的构造方法也需要修改,此处以InstanceMethodsInterceptor为例,修改代码如下:

/*** 构造方法** @param interceptorClassName* @param classLoader*/
public InstanceMethodsInterceptor(String interceptorClassName,ClassLoader classLoader) {try {aroundInterceptor = InterceptorInstanceLoader.load(interceptorClassName, classLoader);} catch (Exception e) {e.printStackTrace();System.out.println("获取构造方法拦截器失败 ConstructorMethodsInterceptor");}
}

5. 简单验证

我们对项目进行编译,然后将编译成功的插件放入到我们预订的plugins目录下

在这里插入图片描述

然后我们设置探针部分,在server启动的时候添加探针参数

-javaagent:D:\idea_workspace\bytebuddy-demo\git-agent-demo\dist\apm-agent-1.0.SNAPSHOT-jar-with-dependencies.jar

然后启动服务,发现探针增强部分已经进行了加载,具体内容如下:

在这里插入图片描述

我们再次访问前端页面,查看我们的增强信息是否打印 http://localhost:8080/userInfo/list/user,发现增强部分已经进行了打印

在这里插入图片描述


http://www.ppmy.cn/news/1558933.html

相关文章

Large Language Model based Multi-Agents: A Survey of Progress and Challenges

一、摘要 背景&#xff1a; 大型语言模型&#xff08;LLMs&#xff09;在多种任务中取得了显著的成功&#xff0c;展现出与人类相媲美的规划和推理能力。LLMs被用作自主智能体&#xff0c;自动执行任务&#xff0c;尤其在基于单个LLM的规划或决策智能体的基础上&#xff0c;基于…

设计模式的分类及作用

设计模式&#xff08;Design Patterns&#xff09;是软件开发中一种经过验证的、可重用的解决方案&#xff0c;用来解决在特定场景下的常见问题。设计模式可以帮助程序员在面对复杂问题时&#xff0c;通过已经被验证过的方式来设计系统&#xff0c;使得代码更加清晰、可维护、可…

php中laravel基于rabbit的异步队列实践与原理

在 Laravel 中&#xff0c;RabbitMQ 是一个常用的消息队列服务&#xff0c;它可以用于异步任务处理。Laravel 默认支持多种队列驱动&#xff0c;其中就包括 RabbitMQ。通过 RabbitMQ&#xff0c;你可以实现高效、可靠的消息传递和任务处理&#xff0c;尤其适用于需要分布式系统…

文档大师:打造一站式 Word 报告解决方案1

前言 在政府、医院、银行、财务以及销售等领域&#xff0c;常常需要创建各种报告文件来展开工作汇报&#xff0c;譬如季度销售报告、年度总结报告、体检报告和保险合同等。在没有报表工具支持之前&#xff0c;这类报告主要通过 Word 制作&#xff0c;费时费力且难以维护&#…

通过端口测试验证网络安全策略

基于网络安全需求&#xff0c;项目中的主机间可能会有不同的网络安全策略&#xff0c;这当然是好的&#xff0c;但很多时候&#xff0c;在解决网络安全问题的时候&#xff0c;同时引入了新的问题&#xff0c;如k8s集群必须在主机间开放udp端口&#xff0c;否则集群不能正常的运…

人工智能之基于阿里云进行人脸特征检测部署

人工智能之基于阿里云进行人脸特征检测部署 需求描述 基于阿里云搭建真人人脸68个关键点检测模型&#xff0c;模型名称&#xff1a;Damo_XR_Lab/cv_human_68-facial-landmark-detection使用上述模型进行人脸关键点识别&#xff0c;模型地址 业务实现 阿里云配置 阿里云配置…

docker基础命令入门、镜像、运行容器、操作容器

一. Docker 基础入门相关命令 1.1 启动Docker 1.2 查看 Docker 运行状态 1.3 停止 Dokcer 1.4 重启Docker 1.5 配置开机启动 docker 1.6 查看 docker 所有命令 二. Docker 镜像相关命令 2.1 docker search 镜像名称——(查询某个镜像) 2.2 docker pull 镜像名称:version…

MFC扩展库BCGControlBar Pro v36.0 - 可视化管理器等全新升级

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v36.0已全新发布了&#xff0c;这个版本改进网格控件的性能、增强工具栏编辑器功能等&am…