当前位置: 首页 > news >正文

java agent 字节码增强demo实现,无修改增强服务

SkyWalking 探针原理与手写 Java 探针 Demo

SkyWalking 探针原理深入分析

SkyWalking 探针的核心是基于字节码增强技术,通过 Java Agent 机制在类加载时修改字节码来收集监控数据。其主要工作原理如下:

1. Java Agent 机制

  • 加载时机:通过 JVM 的 -javaagent 参数在应用启动时加载
  • ** premain 方法**:代理 jar 包中的 premain 方法是入口点
  • Instrumentation API:提供修改字节码的能力

2. 字节码增强技术

  • ASM/ByteBuddy:SkyWalking 使用 ByteBuddy 库操作字节码
  • 拦截点:在特定方法前后插入监控代码
  • 上下文传播:通过修改方法参数或线程上下文传递追踪信息

3. 数据收集与上报

  • Trace 生成:为每个请求生成全局唯一的 Trace ID
  • Span 创建:记录方法调用的开始时间、结束时间和元数据
  • 异步上报:通过 gRPC/HTTP 将数据发送到 SkyWalking OAP 服务器

手写 Java 探针 Demo:项目创建指南

对于手写 Java 探针 Demo,您不需要创建 Spring 项目,一个标准的 Maven 项目就足够了。下面是在 IntelliJ IDEA 中创建项目的详细步骤。

项目结构规划

建议创建两个独立的 Maven 项目:

  1. agent-project - 探针项目(Java Agent)
  2. target-project - 目标应用项目(被增强的应用)

在 IntelliJ IDEA 中创建项目

第一步:创建探针项目(agent-project)

  1. 打开 IntelliJ IDEA,选择 “File” → “New” → “Project”

  2. 选择 “Maven” 作为项目类型,点击 “Next”

  3. 填写项目信息:

    • GroupId: com.demo
    • ArtifactId: agent-project
    • Version: 1.0-SNAPSHOT
  4. 点击 “Finish” 创建项目

  5. 修改 pom.xml 文件,添加 ByteBuddy 依赖和打包配置:

<?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>com.demo</groupId><artifactId>agent-project</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><byte-buddy.version>1.14.8</byte-buddy.version></properties><dependencies><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>${byte-buddy.version}</version></dependency><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy-agent</artifactId><version>${byte-buddy.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.2.2</version><configuration><archive><manifest><addClasspath>true</addClasspath></manifest><manifestEntries><Premain-Class>com.demo.agent.Agent</Premain-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.3.0</version><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><manifest><addClasspath>true</addClasspath></manifest><manifestEntries><Premain-Class>com.demo.agent.Agent</Premain-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration><executions><execution><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin></plugins></build>
</project>
  1. 创建包结构:右键 src/main/java → “New” → “Package”,命名为 com.demo.agent

  2. 在包中创建 Java 类:

    • Agent.java - 探针入口类
    • MethodTimerInterceptor.java - 方法拦截器

第二步:创建目标应用项目(target-project)

这里的目标应用就可以自己创建,只是需要启动时添加vm option :-javaagent:jar包路径

代码实现

Agent 项目代码

Agent.java:

package com.demo.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.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;import static net.bytebuddy.matcher.ElementMatchers.*;public class Agent {public static void premain(String args, Instrumentation inst) {System.out.println("bzy111111111111 Demo APM Agent is running!");new AgentBuilder.Default()// 匹配需要增强的类(Controller, Service, Repository).type(ElementMatchers.nameMatches(".*Controller").or(ElementMatchers.nameMatches(".*Service")).or(ElementMatchers.nameMatches(".*Repository"))).transform(new AgentBuilder.Transformer() {@Overridepublic DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,ClassLoader classLoader, JavaModule javaModule,ProtectionDomain protectionDomain) {// 根据类名选择拦截器Class<?> interceptorClass = getInterceptorForClass(typeDescription.getSimpleName());return builder.method(isPublic().and(not(isStatic())).and(not(isConstructor()))).intercept(MethodDelegation.to(interceptorClass));}}).installOn(inst); // 关键:安装到 Instrumentation}/*** 根据类名返回对应的拦截器类*/private static Class<?> getInterceptorForClass(String className) {if (className.endsWith("Controller")) {return TraceInterceptor.class;} else if (className.endsWith("Service") || className.endsWith("Repository")) {return LoggingInterceptor.class;}// 默认使用 TraceInterceptorreturn null;}public static void agentmain(String args, Instrumentation inst) {premain(args, inst);}
}

LoggingInterceptor

package com.demo.agent;import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.implementation.bind.annotation.AllArguments;import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.Callable;public class LoggingInterceptor {@RuntimeTypepublic static Object intercept(@Origin Method method,@AllArguments Object[] allArguments,@SuperCall Callable<?> callable) throws Exception {String className = method.getDeclaringClass().getName();String methodName = method.getName();System.out.println("bzy111111111 intercept");System.out.println(method + "allArguments" + Arrays.toString(allArguments) + "call" + callable.toString());// 记录方法入参StringBuilder argsInfo = new StringBuilder();if (allArguments != null && allArguments.length > 0) {for (int i = 0; i < allArguments.length; i++) {if (i > 0) argsInfo.append(", ");argsInfo.append("arg").append(i).append("=");if (allArguments[i] != null) {argsInfo.append(allArguments[i].toString());} else {argsInfo.append("null");}}}System.out.printf("[LOG-INFO] traceId=%s, class=%s, method=%s, action=start, parameters=%s%n",TraceContext.getCurrentTraceId(), className, methodName, argsInfo.toString());long startTime = System.currentTimeMillis();try {Object result = callable.call();// 记录方法返回结果(简化处理,只记录基本类型和字符串)String resultInfo = result != null ? result.toString() : "null";if (resultInfo.length() > 100) {resultInfo = resultInfo.substring(0, 100) + "...";}long duration = System.currentTimeMillis() - startTime;System.out.printf("[LOG-INFO] traceId=%s, class=%s, method=%s, action=end, duration=%dms, result=%s%n",TraceContext.getCurrentTraceId(), className, methodName, duration, resultInfo);return result;} catch (Exception e) {long duration = System.currentTimeMillis() - startTime;System.out.printf("[LOG-ERROR] traceId=%s, class=%s, method=%s, action=error, duration=%dms, error=%s%n",TraceContext.getCurrentTraceId(), className, methodName, duration, e.getMessage());throw e;}}
}

TraceInterceptor

package com.demo.agent;import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;import java.lang.reflect.Method;
import java.util.concurrent.Callable;public class TraceInterceptor {@RuntimeTypepublic static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception {String className = method.getDeclaringClass().getName();String methodName = method.getName();String spanName = className + "." + methodName;TraceContext.startSpan(spanName, className, methodName);System.out.println("bzy111111111 TraceInterceptor");try {return callable.call();} catch (Exception e) {// 记录异常信息System.out.printf("[TRACE-ERROR] traceId=%s, spanId=%s, class=%s, method=%s, error=%s%n",TraceContext.getCurrentTraceId(), TraceContext.getCurrentSpanId(),className, methodName, e.getMessage());throw e;} finally {TraceContext.endSpan();}}
}

TraceContext

package com.demo.agent;import com.demo.agent.utils.IdGenerator;import java.util.Stack;public class TraceContext {private static final ThreadLocal<Stack<TraceSpan>> traceStack = ThreadLocal.withInitial(Stack::new);private static final ThreadLocal<String> traceId = new ThreadLocal<>();public static void startSpan(String spanName, String className, String methodName) {TraceSpan span = new TraceSpan();span.spanId = IdGenerator.generateSpanId();span.spanName = spanName;span.className = className;span.methodName = methodName;span.startTime = System.currentTimeMillis();if (traceId.get() == null) {traceId.set(IdGenerator.generateTraceId());}span.traceId = traceId.get();if (!traceStack.get().isEmpty()) {TraceSpan parentSpan = traceStack.get().peek();span.parentSpanId = parentSpan.spanId;}traceStack.get().push(span);// 输出开始日志System.out.printf("[TRACE-START] traceId=%s, spanId=%s, parentSpanId=%s, spanName=%s, class=%s, method=%s%n",span.traceId, span.spanId, span.parentSpanId, span.spanName, className, methodName);}public static void endSpan() {if (traceStack.get().isEmpty()) {return;}TraceSpan span = traceStack.get().pop();span.endTime = System.currentTimeMillis();long duration = span.endTime - span.startTime;// 输出结束日志System.out.printf("[TRACE-END] traceId=%s, spanId=%s, duration=%dms, class=%s, method=%s%n",span.traceId, span.spanId, duration, span.className, span.methodName);// 如果是根span,清除traceIdif (traceStack.get().isEmpty()) {traceId.remove();}}public static String getCurrentTraceId() {return traceId.get();}public static String getCurrentSpanId() {if (traceStack.get().isEmpty()) {return null;}return traceStack.get().peek().spanId;}static class TraceSpan {String traceId;String spanId;String parentSpanId;String spanName;String className;String methodName;long startTime;long endTime;}
}

MethodTimerInterceptor.java:

package com.demo.agent;import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;import java.lang.reflect.Method;
import java.util.concurrent.Callable;public class MethodTimerInterceptor {@RuntimeTypepublic static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception {System.out.println("bzy111111111 TraceInterceptor");System.out.println(method.toString() + "call" + callable.toString());long start = System.currentTimeMillis();try {return callable.call();} finally {long end = System.currentTimeMillis();System.out.println("bzy Method " + method.getName() + " executed in " + (end - start) + "ms");}}
}
``**IdGenerator**
~~~
package com.demo.agent.utils;import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;public class IdGenerator {private static final AtomicInteger spanCounter = new AtomicInteger(1);public static String generateTraceId() {return UUID.randomUUID().toString().replace("-", "").substring(0, 16);}public static String generateSpanId() {return Integer.toHexString(spanCounter.getAndIncrement());}
}
~~~## 在 IDEA 中直接运行1. 打开目标项目的运行配置:- 点击右上角的运行配置下拉菜单 → "Edit Configurations"2. 添加新的 "Application" 配置:- Main class: `com.demo.app.DemoApplication`- VM options: `-javaagent:/path/to/agent-project/target/agent-project-1.0-SNAPSHOT-jar-with-dependencies.jar`- 将 `/path/to/` 替换为实际的 agent-project 路径3. 点击 "Apply" 然后 "OK"4. 现在可以直接在 IDEA 中运行目标应用,并看到探针的效果# 对比 SkyWalking 探针我们这个简单Demo实现了SkyWalking探针的基本原理,但实际SkyWalking要复杂得多:1. **上下文传播**SkyWalking 能够跨线程、跨进程传播追踪上下文
2. **多种组件支持**:支持HTTP、数据库、消息队列等多种组件的自动增强
3. **数据上报**:通过gRPC将数据异步上报到收集器
4. **性能优化**:采用缓存、采样等策略降低性能开销
5. **插件体系**:支持通过插件扩展对各种框架的增强## 总结通过这个Demo,我们可以看到Java探针的基本工作原理:
1. 通过Java Agent机制在应用启动时加载
2. 使用字节码增强技术修改类的字节码
3. 在方法执行前后插入监控代码
4. 收集并输出监控数据SkyWalkingAPM工具的核心原理与此类似,但实现了更复杂的功能和更完善的生态系统。理解这个基本原理有助于更好地使用和调试分布式追踪系统。
http://www.dtcms.com/a/357914.html

相关文章:

  • 从零开始的python学习——注释与运算符
  • Codeforces Round 1033 (Div. 2) and CodeNite 2025 vp补题
  • Oracle 数据库性能调优:从瓶颈诊断到精准优化之道
  • DeepSeek应用技巧-通过MCP打造数据分析助手
  • 现代 Linux 发行版为何忽略Shell脚本的SUID位?
  • 深入 MySQL SELECT 查询
  • windows 谷歌浏览器把英文改成中文
  • 面试 八股文 经典题目 - Mysql部分(一)
  • 数据结构--栈(Stack) 队列(Queue)
  • 从单机到分布式:Python 爬虫架构演进
  • kmp 算法
  • 【MLLM】多模态理解Ovis2.5模型架构和训练流程
  • 模式组合应用-组合模式
  • 加速智能经济发展:如何助力“人工智能+”战略在实时视频领域的落地
  • 时间轴组件开发:实现灵活的时间范围选择
  • More Effective C++ 条款17: 考虑使用缓式评估(Consider Using Lazy Evaluation)
  • centos7.9的openssh漏洞修复脚本
  • 软考 系统架构设计师系列知识点之杂项集萃(137)
  • 响应式编程框架Reactor【5】
  • PostgreSQL表空间(Tablespace)作用(管理数据库对象的存储位置)(pg_default、pg_global)
  • STL库——list(类模拟实现)
  • 将LLM模型“钉”在电路板上:用电阻矩阵实现物理推理引擎
  • Nacos-3.0.3 适配PostgreSQL数据库
  • openGauss笔记
  • rabbitMQ延时队列实现,怎么保证消息的幂等
  • HTML 核心元素实战:超链接、iframe 框架与 form 表单全面解析
  • 【WDG协议栈】AUTOSAR架构下WDG模块软硬件功能详解
  • 基于单片机指纹考勤系统/智能考勤
  • ⸢ 叁 ⸥ ⤳ 默认安全:概述与建设思路
  • 【Day 33】Linux-MySQL 备份与恢复详解