Bytebuddy委托拦截样例

This commit is contained in:
kazusa 2025-07-23 15:29:18 +08:00
parent 9d36ad9121
commit 27adffa183
11 changed files with 1295 additions and 10 deletions

View File

@ -0,0 +1,233 @@
# ByteBuddy Agent 拦截演示项目
## 项目概述
这是一个完整的ByteBuddy Java Agent实现示例展示了如何使用AgentBuilder进行字节码增强和方法拦截。项目演示了多种目标类匹配方式包括类名匹配、注解匹配、包路径匹配等。
## 核心特性
**使用AgentBuilder方式进行字节码增强**
**使用委托方式对目标方法增强**
**设定用来增强方法的拦截器**
**在委托类中使用拦截器对目标方法前后增强**
**完成能在方法调用前后打印入参和返回结果的插件**
✅ **展示多种目标类匹配方式(类名、注解、包路径等)**
## 项目结构
```
src/main/java/io/github/kazusa/pressmonster/demo/
├── agent/ # Agent核心代码
│ ├── SimpleAgent.java # Agent主类展示多种匹配方式
│ ├── MethodInterceptor.java # 拦截器接口
│ ├── LoggingInterceptor.java # 具体的日志拦截器实现
│ └── DelegateMethodHandler.java # 委托方法处理器
├── annotation/ # 自定义注解
│ └── Monitored.java # 用于注解匹配演示
├── service/ # 示例服务类
│ ├── UserService.java # 普通服务类,用于类名匹配
│ └── OrderService.java # 带注解的服务类,用于注解匹配
├── DemoApplication.java # 演示应用主类
├── SampleController.java # Spring Controller示例
└── run-demo.sh # 便捷运行脚本
```
## 拦截匹配方式
### 1. 精确类名匹配
```java
named("io.github.kazusa.pressmonster.demo.SampleController")
```
### 2. Spring注解匹配
```java
isAnnotatedWith(RestController.class)
```
### 3. 自定义注解匹配
```java
isAnnotatedWith(named("io.github.kazusa.pressmonster.demo.annotation.Monitored"))
```
### 4. 包路径前缀匹配
```java
nameStartsWith("io.github.kazusa.pressmonster.demo.service")
```
### 5. 类名后缀匹配
```java
nameEndsWith("Service")
```
### 6. 类名包含匹配
```java
nameContains("Controller")
```
### 7. 复合条件匹配
```java
// 使用OR条件组合多种匹配方式
named("className")
.or(isAnnotatedWith(SomeAnnotation.class))
.or(nameStartsWith("packagePrefix"))
.and(not(nameContains("Test"))) // 排除测试类
```
## 快速开始
### 方式1: 使用运行脚本(推荐)
```bash
# 直接运行脚本
./run-demo.sh
# 或者
bash run-demo.sh
```
脚本会自动编译、打包并提供3种运行选择
1. 不使用Agent运行对比效果
2. 使用Agent运行展示拦截效果
3. 同时运行两种方式进行对比
### 方式2: 手动编译运行
```bash
# 1. 编译和打包
mvn clean package -DskipTests
# 2. 不使用Agent运行看不到拦截日志
java -jar target/demo-1.0.0-SNAPSHOT.jar
# 3. 使用Agent运行可以看到拦截日志
java -javaagent:target/demo-1.0.0-SNAPSHOT.jar -jar target/demo-1.0.0-SNAPSHOT.jar
```
## 预期输出示例
### 使用Agent运行时的输出
```
========================================
Simple ByteBuddy Agent 启动中...
Agent参数: 无
========================================
[AGENT] 成功增强类: io.github.kazusa.pressmonster.demo.SampleController (匹配方式: SPRING_CONTROLLER)
[AGENT] 成功增强类: io.github.kazusa.pressmonster.demo.service.UserService (匹配方式: SERVICE_PACKAGE)
[AGENT] 成功增强类: io.github.kazusa.pressmonster.demo.service.OrderService (匹配方式: SERVICE_PACKAGE)
[AGENT] ByteBuddy Agent 安装完成!
========================================
=== ByteBuddy Agent 拦截演示应用 ===
========================================
🔍 测试1: @RestController注解匹配
目标类: SampleController
匹配规则: isAnnotatedWith(RestController.class)
----------------------------------------
[INTERCEPT] [SPRING_CONTROLLER] Before method: SampleController.getUsers()
[INTERCEPT] [SPRING_CONTROLLER] Arguments: []
SampleController.getUsers() 执行中...
[INTERCEPT] [SPRING_CONTROLLER] After method: SampleController.getUsers()
[INTERCEPT] [SPRING_CONTROLLER] Return: {users=[user1, user2, user3], total=3}
[INTERCEPT] [SPRING_CONTROLLER] Duration: 15.23ms
[INTERCEPT] [SPRING_CONTROLLER] ==========================================
```
## 核心实现原理
### 1. Agent入口点 (SimpleAgent.java)
- 实现`premain`方法作为Agent入口
- 配置AgentBuilder的匹配规则和转换逻辑
- 使用`MethodDelegation.to()`进行方法委托
### 2. 委托处理器 (DelegateMethodHandler.java)
- 使用`@RuntimeType`注解支持动态类型匹配
- 集成拦截器调用逻辑
- 处理原方法执行和异常捕获
### 3. 拦截器 (LoggingInterceptor.java)
- 实现`beforeMethod`、`afterMethod`、`handleException`方法
- 提供参数和返回值的格式化输出
- 记录方法执行时间和性能统计
### 4. Maven配置
- 配置`Premain-Class`和相关Agent属性
- 使用Shade插件打包依赖到Fat JAR
- 支持同时作为普通应用和Agent运行
## 扩展功能
### 添加新的匹配规则
在`SimpleAgent.buildTypeMatchers()`方法中添加新的匹配条件:
```java
.or(nameMatches(".*Controller.*")) // 正则表达式匹配
.or(isInterface()) // 匹配接口
.or(hasSuperType(named("某个父类"))) // 匹配特定父类的子类
```
### 自定义拦截器
实现`MethodInterceptor`接口创建自定义拦截器:
```java
public class CustomInterceptor implements MethodInterceptor {
@Override
public void beforeMethod(Object target, Method method, Object[] args, String matchType) {
// 自定义前置逻辑
}
@Override
public Object afterMethod(Object target, Method method, Object[] args, Object result,
String matchType, long startTime) {
// 自定义后置逻辑
return result;
}
@Override
public void handleException(Object target, Method method, Object[] args,
Throwable throwable, String matchType) {
// 自定义异常处理逻辑
}
}
```
## 依赖说明
- **ByteBuddy**: 1.14.9 - 字节码操作核心库
- **Spring Web**: 用于@RestController注解支持
- **Jackson**: JSON序列化可选
- **SLF4J + Logback**: 日志框架
## 注意事项
1. **Java版本**: 支持Java 8+
2. **ClassLoader隔离**: Agent代码与应用代码使用不同的ClassLoader
3. **性能影响**: 拦截会带来额外的性能开销,生产环境需要谨慎使用
4. **异常处理**: 拦截器异常不应影响原始方法的执行逻辑
## 故障排除
### 常见问题
**Q: Agent没有生效看不到拦截日志**
A: 检查是否使用了`-javaagent`参数确保JAR文件路径正确
**Q: 编译失败**
A: 检查Java版本和Maven配置确保所有依赖都能正确下载
**Q: 某些类没有被拦截**
A: 检查匹配规则,确认目标类符合预期的匹配条件
**Q: 运行时出现ClassNotFoundException**
A: 使用Maven Shade插件打包所有依赖到Fat JAR
## 进一步学习
- [ByteBuddy官方文档](https://bytebuddy.net/)
- [Java Instrumentation API](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html)
- [Java Agent开发指南](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html)
---
*这个项目完整展示了ByteBuddy Agent的核心功能可以作为学习字节码增强和AOP编程的参考示例。*

View File

@ -32,6 +32,18 @@
<artifactId>press-monster-common</artifactId>
</dependency>
<!-- ByteBuddy for Agent Development -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.9</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.14.9</version>
</dependency>
<!-- Spring Web for demo controllers -->
<dependency>
<groupId>org.springframework</groupId>
@ -59,4 +71,71 @@
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>8</source>
<target>8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- Maven JAR Plugin - 配置Agent的MANIFEST.MF -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>io.github.kazusa.pressmonster.demo.DemoApplication</mainClass>
</manifest>
<manifestEntries>
<!-- Java Agent相关配置 -->
<Premain-Class>io.github.kazusa.pressmonster.demo.agent.SimpleAgent</Premain-Class>
<Agent-Class>io.github.kazusa.pressmonster.demo.agent.SimpleAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!-- Maven Shade Plugin - 打包所有依赖到一个Fat JAR -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.github.kazusa.pressmonster.demo.DemoApplication</mainClass>
<manifestEntries>
<Premain-Class>io.github.kazusa.pressmonster.demo.agent.SimpleAgent</Premain-Class>
<Agent-Class>io.github.kazusa.pressmonster.demo.agent.SimpleAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,106 @@
#!/bin/bash
# ByteBuddy Agent Demo 运行脚本
echo "========================================="
echo "ByteBuddy Agent 拦截演示脚本"
echo "========================================="
# 设置项目目录
PROJECT_DIR="/Users/kazusa/project/java/press-monster-java/press-monster-test/demo"
cd "$PROJECT_DIR"
echo "当前目录: $(pwd)"
# 清理并编译项目
echo -e "\n🔨 编译项目..."
mvn clean compile > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "❌ 编译失败,请检查代码"
mvn clean compile
exit 1
fi
# 打包项目
echo "📦 打包项目..."
mvn package -DskipTests > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "❌ 打包失败,请检查配置"
mvn package -DskipTests
exit 1
fi
# 检查jar文件是否存在
JAR_FILE="$PROJECT_DIR/target/demo-1.0.0-SNAPSHOT.jar"
if [ ! -f "$JAR_FILE" ]; then
echo "❌ JAR文件不存在: $JAR_FILE"
exit 1
fi
echo "✅ 编译和打包完成"
echo -e "\n========================================="
echo "运行选项:"
echo "1. 不使用Agent运行对比效果"
echo "2. 使用Agent运行展示拦截效果"
echo "========================================="
# 提供两种运行方式供用户选择
echo -e "\n选择运行方式"
echo "输入 1 - 不使用Agent运行"
echo "输入 2 - 使用Agent运行"
echo "输入 3 - 同时运行两种方式进行对比"
read -p "请选择 (1/2/3): " choice
case $choice in
1)
echo -e "\n🚀 运行方式1: 不使用Agent"
echo "命令: java -jar $JAR_FILE"
echo "----------------------------------------"
java -jar "$JAR_FILE"
;;
2)
echo -e "\n🚀 运行方式2: 使用Agent"
echo "命令: java -javaagent:$JAR_FILE -jar $JAR_FILE"
echo "----------------------------------------"
java -javaagent:"$JAR_FILE" -jar "$JAR_FILE"
;;
3)
echo -e "\n🚀 对比运行:"
echo -e "\n【第一次运行 - 不使用Agent】"
echo "命令: java -jar $JAR_FILE"
echo "----------------------------------------"
java -jar "$JAR_FILE"
echo -e "\n\n【第二次运行 - 使用Agent】"
echo "命令: java -javaagent:$JAR_FILE -jar $JAR_FILE"
echo "----------------------------------------"
java -javaagent:"$JAR_FILE" -jar "$JAR_FILE"
;;
*)
echo "❌ 无效选择默认使用Agent运行"
echo -e "\n🚀 使用Agent运行:"
echo "命令: java -javaagent:$JAR_FILE -jar $JAR_FILE"
echo "----------------------------------------"
java -javaagent:"$JAR_FILE" -jar "$JAR_FILE"
;;
esac
echo -e "\n========================================="
echo "演示完成!"
echo "========================================="
# 显示帮助信息
echo -e "\n💡 说明:"
echo "- 第一种方式:普通运行,不会看到拦截日志"
echo "- 第二种方式Agent运行会看到详细的拦截日志"
echo "- Agent会拦截所有匹配的类和方法打印参数和返回值"
echo ""
echo "📝 匹配规则:"
echo "1. @RestController注解的类 (SampleController)"
echo "2. service包下的类 (UserService, OrderService)"
echo "3. 以Service结尾的类名"
echo "4. @Monitored注解的类"
echo ""
echo "🔧 如需修改拦截规则,请编辑:"
echo " src/main/java/io/github/kazusa/pressmonster/demo/agent/SimpleAgent.java"

View File

@ -1,27 +1,164 @@
package io.github.kazusa.pressmonster.demo;
import io.github.kazusa.pressmonster.demo.service.OrderService;
import io.github.kazusa.pressmonster.demo.service.UserService;
import java.util.HashMap;
import java.util.Map;
/**
* 演示应用
* 展示Press Monster Agent的功能
* 展示ByteBuddy Agent的多种拦截匹配方式
*
* 运行方式
* 1. 作为普通应用运行java -jar demo.jar
* 2. 作为Agent运行java -javaagent:demo.jar -jar demo.jar
*/
public class DemoApplication {
public static void main(String[] args) {
System.out.println("=== Press Monster Agent 演示 ===");
System.out.println("========================================");
System.out.println("=== ByteBuddy Agent 拦截演示应用 ===");
System.out.println("========================================");
// 模拟应用运行
try {
// 等待一段时间让Agent收集信息
Thread.sleep(2000);
SampleController controller = new SampleController();
controller.getUsers();
// 输出收集到的API信息
printApiEndpoints();
// 等待Agent完全加载
Thread.sleep(1000);
System.out.println("\n开始测试不同类型的拦截匹配...\n");
// 1. 测试@RestController注解匹配 - SampleController
testControllerMethods();
// 2. 测试包路径和类名后缀匹配 - UserService
testUserServiceMethods();
// 3. 测试自定义@Monitored注解匹配 - OrderService
testOrderServiceMethods();
// 4. 测试异常处理
testExceptionHandling();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("\n========================================");
System.out.println("=== 所有测试完成 ===");
System.out.println("========================================");
}
private static void printApiEndpoints() {}
/**
* 测试Controller方法@RestController注解匹配
*/
private static void testControllerMethods() {
System.out.println("🔍 测试1: @RestController注解匹配");
System.out.println("目标类: SampleController");
System.out.println("匹配规则: isAnnotatedWith(RestController.class)");
System.out.println("----------------------------------------");
SampleController controller = new SampleController();
// 测试无参数方法
controller.getUsers();
// 测试带参数方法
controller.getUserById("user123");
// 测试复杂参数方法
Map<String, Object> userData = new HashMap<>();
userData.put("name", "张三");
userData.put("email", "zhangsan@example.com");
controller.createUser(userData);
System.out.println();
}
/**
* 测试UserService方法包路径前缀 + 类名后缀匹配
*/
private static void testUserServiceMethods() {
System.out.println("🔍 测试2: 包路径前缀 + 类名后缀匹配");
System.out.println("目标类: UserService");
System.out.println("匹配规则: nameStartsWith(\"io.github.kazusa.pressmonster.demo.service\") OR nameEndsWith(\"Service\")");
System.out.println("----------------------------------------");
UserService userService = new UserService();
// 测试查询方法
userService.findUserById("user456");
// 测试列表方法
userService.getAllUsers();
// 测试创建方法
userService.createUser("李四", "lisi@example.com");
// 测试更新方法
Map<String, Object> updateData = new HashMap<>();
updateData.put("name", "李四(已更新)");
userService.updateUser("user456", updateData);
System.out.println();
}
/**
* 测试OrderService方法自定义@Monitored注解匹配
*/
private static void testOrderServiceMethods() {
System.out.println("🔍 测试3: 自定义@Monitored注解匹配");
System.out.println("目标类: OrderService");
System.out.println("匹配规则: isAnnotatedWith(named(\"io.github.kazusa.pressmonster.demo.annotation.Monitored\"))");
System.out.println("----------------------------------------");
OrderService orderService = new OrderService();
// 测试查询订单
orderService.findOrderById("order789");
// 测试用户订单列表
orderService.getOrdersByUserId("user123");
// 测试创建订单
orderService.createOrder("user456", 299.99, "商品A x2");
// 测试更新订单状态
orderService.updateOrderStatus("order789", "shipped");
// 测试取消订单
orderService.cancelOrder("order789", "用户要求取消");
System.out.println();
}
/**
* 测试异常处理
*/
private static void testExceptionHandling() {
System.out.println("🔍 测试4: 异常处理拦截");
System.out.println("目标方法: OrderService.calculateTotalAmount()");
System.out.println("----------------------------------------");
OrderService orderService = new OrderService();
try {
// 测试正常情况
String[] validOrderIds = {"order1", "order2", "order3"};
double total = orderService.calculateTotalAmount(validOrderIds);
System.out.println("计算结果: " + total);
} catch (Exception e) {
System.out.println("捕获到异常: " + e.getMessage());
}
try {
// 测试异常情况
orderService.calculateTotalAmount(null);
} catch (Exception e) {
System.out.println("成功捕获预期异常: " + e.getClass().getSimpleName() + " - " + e.getMessage());
}
System.out.println();
}
}

View File

@ -0,0 +1,87 @@
package io.github.kazusa.pressmonster.demo.agent;
import net.bytebuddy.implementation.bind.annotation.*;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
/**
* 委托方法处理器
* 使用ByteBuddy的MethodDelegation机制拦截目标方法并调用拦截器
* 这是实现字节码增强的核心类
*/
public class DelegateMethodHandler {
// 拦截器实例用于处理方法拦截逻辑
private final MethodInterceptor interceptor;
// 匹配类型用于标识是通过什么方式匹配到的目标类
private final String matchType;
/**
* 构造函数
* @param interceptor 拦截器实例
* @param matchType 匹配类型标识
*/
public DelegateMethodHandler(MethodInterceptor interceptor, String matchType) {
this.interceptor = interceptor;
this.matchType = matchType;
}
/**
* 使用@RuntimeType注解的拦截方法
* 这是ByteBuddy委托的核心方法所有被拦截的方法调用都会委托到这里
*
* @param target 目标对象实例被拦截的对象
* @param method 被拦截的方法
* @param args 方法的所有参数
* @param zuper 原方法的调用引用用于调用原始方法
* @return 方法的返回值
* @throws Throwable 可能抛出的异常
*/
@RuntimeType
public Object intercept(@This Object target, // 目标对象
@Origin Method method, // 原始方法
@AllArguments Object[] args, // 所有参数
@SuperCall Callable<?> zuper) // 原方法调用
throws Throwable {
long startTime = System.nanoTime();
Object result = null;
try {
// 1. 调用拦截器的beforeMethod方法
interceptor.beforeMethod(target, method, args, matchType);
// 2. 调用原始方法
result = zuper.call();
// 3. 调用拦截器的afterMethod方法
result = interceptor.afterMethod(target, method, args, result, matchType, startTime);
return result;
} catch (Throwable throwable) {
// 4. 如果出现异常调用拦截器的handleException方法
interceptor.handleException(target, method, args, throwable, matchType);
// 重新抛出异常保持原有的异常处理流程
throw throwable;
}
}
/**
* 创建委托方法处理器的工厂方法
* 用于在Agent中创建不同匹配类型的处理器
*/
public static DelegateMethodHandler create(String matchType) {
return new DelegateMethodHandler(new LoggingInterceptor(), matchType);
}
/**
* 创建带自定义拦截器的委托方法处理器
*/
public static DelegateMethodHandler create(MethodInterceptor interceptor, String matchType) {
return new DelegateMethodHandler(interceptor, matchType);
}
}

View File

@ -0,0 +1,147 @@
package io.github.kazusa.pressmonster.demo.agent;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 日志拦截器实现
* 实现方法调用前后的日志记录包括参数返回值和执行时间
*/
public class LoggingInterceptor implements MethodInterceptor {
private static final String LOG_PREFIX = "[INTERCEPT]";
@Override
public void beforeMethod(Object target, Method method, Object[] args, String matchType) throws Throwable {
String className = target.getClass().getSimpleName();
String methodName = method.getName();
System.out.println(String.format("%s [%s] Before method: %s.%s()",
LOG_PREFIX, matchType, className, methodName));
if (args != null && args.length > 0) {
System.out.println(String.format("%s [%s] Arguments: %s",
LOG_PREFIX, matchType, formatArguments(args)));
} else {
System.out.println(String.format("%s [%s] Arguments: []",
LOG_PREFIX, matchType));
}
}
@Override
public Object afterMethod(Object target, Method method, Object[] args, Object result,
String matchType, long startTime) throws Throwable {
String className = target.getClass().getSimpleName();
String methodName = method.getName();
long duration = System.nanoTime() - startTime;
double durationMs = duration / 1_000_000.0;
System.out.println(String.format("%s [%s] After method: %s.%s()",
LOG_PREFIX, matchType, className, methodName));
if (result != null) {
System.out.println(String.format("%s [%s] Return: %s",
LOG_PREFIX, matchType, formatReturnValue(result)));
} else {
System.out.println(String.format("%s [%s] Return: null",
LOG_PREFIX, matchType));
}
System.out.println(String.format("%s [%s] Duration: %.2fms",
LOG_PREFIX, matchType, durationMs));
System.out.println(String.format("%s [%s] ==========================================",
LOG_PREFIX, matchType));
return result;
}
@Override
public void handleException(Object target, Method method, Object[] args,
Throwable throwable, String matchType) throws Throwable {
String className = target.getClass().getSimpleName();
String methodName = method.getName();
System.err.println(String.format("%s [%s] Exception in method: %s.%s()",
LOG_PREFIX, matchType, className, methodName));
System.err.println(String.format("%s [%s] Exception type: %s",
LOG_PREFIX, matchType, throwable.getClass().getSimpleName()));
System.err.println(String.format("%s [%s] Exception message: %s",
LOG_PREFIX, matchType, throwable.getMessage()));
// 重新抛出异常保持原有的异常处理逻辑
throw throwable;
}
/**
* 格式化方法参数
*/
private String formatArguments(Object[] args) {
if (args == null || args.length == 0) {
return "[]";
}
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < args.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(formatSingleValue(args[i]));
}
sb.append("]");
return sb.toString();
}
/**
* 格式化返回值
*/
private String formatReturnValue(Object value) {
return formatSingleValue(value);
}
/**
* 格式化单个值
*/
private String formatSingleValue(Object value) {
if (value == null) {
return "null";
}
// 如果是字符串添加引号
if (value instanceof String) {
return "\"" + value + "\"";
}
// 如果是数组使用Arrays.toString
if (value.getClass().isArray()) {
if (value instanceof Object[]) {
return Arrays.toString((Object[]) value);
} else if (value instanceof int[]) {
return Arrays.toString((int[]) value);
} else if (value instanceof long[]) {
return Arrays.toString((long[]) value);
} else if (value instanceof boolean[]) {
return Arrays.toString((boolean[]) value);
} else if (value instanceof double[]) {
return Arrays.toString((double[]) value);
} else if (value instanceof float[]) {
return Arrays.toString((float[]) value);
} else if (value instanceof byte[]) {
return Arrays.toString((byte[]) value);
} else if (value instanceof char[]) {
return Arrays.toString((char[]) value);
} else if (value instanceof short[]) {
return Arrays.toString((short[]) value);
}
}
// 对于复杂对象限制输出长度避免过长
String str = value.toString();
if (str.length() > 200) {
return str.substring(0, 200) + "... (truncated)";
}
return str;
}
}

View File

@ -0,0 +1,49 @@
package io.github.kazusa.pressmonster.demo.agent;
import java.lang.reflect.Method;
/**
* 方法拦截器接口
* 定义方法拦截的标准接口实现AOP切面功能
*/
public interface MethodInterceptor {
/**
* 方法执行前的拦截处理
*
* @param target 目标对象实例
* @param method 被拦截的方法
* @param args 方法参数
* @param matchType 匹配类型用于标识是通过什么方式匹配到的
* @throws Throwable 可能抛出的异常
*/
void beforeMethod(Object target, Method method, Object[] args, String matchType) throws Throwable;
/**
* 方法执行后的拦截处理
*
* @param target 目标对象实例
* @param method 被拦截的方法
* @param args 方法参数
* @param result 方法返回结果
* @param matchType 匹配类型
* @param startTime 方法开始执行时间纳秒
* @return 可能修改后的返回结果
* @throws Throwable 可能抛出的异常
*/
Object afterMethod(Object target, Method method, Object[] args, Object result,
String matchType, long startTime) throws Throwable;
/**
* 方法执行异常时的拦截处理
*
* @param target 目标对象实例
* @param method 被拦截的方法
* @param args 方法参数
* @param throwable 抛出的异常
* @param matchType 匹配类型
* @throws Throwable 可能重新抛出的异常
*/
void handleException(Object target, Method method, Object[] args,
Throwable throwable, String matchType) throws Throwable;
}

View File

@ -0,0 +1,147 @@
package io.github.kazusa.pressmonster.demo.agent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
// import org.springframework.web.bind.annotation.RestController; // 注释掉避免ClassNotFoundException
import java.lang.instrument.Instrumentation;
import static net.bytebuddy.matcher.ElementMatchers.*;
/**
* 简单Agent示例
* 展示使用ByteBuddy AgentBuilder的多种目标类匹配方式
* 这是一个完整的Java Agent实现演示了字节码增强的核心技术
*/
public class SimpleAgent {
/**
* Java Agent的入口方法
* JVM在启动时会调用这个方法通过-javaagent参数指定
*
* @param agentArgs Agent参数
* @param inst Instrumentation实例用于字节码操作
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("========================================");
System.out.println("Simple ByteBuddy Agent 启动中...");
System.out.println("Agent参数: " + (agentArgs != null ? agentArgs : ""));
System.out.println("========================================");
try {
// 创建AgentBuilder实例配置多种匹配方式
new AgentBuilder.Default()
// 配置匹配条件展示多种目标类匹配方式
.type(buildTypeMatchers())
// 配置方法匹配匹配所有public方法排除Object的基础方法
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.method(
isPublic()
.and(not(isDeclaredBy(Object.class))) // 排除Object类的方法
.and(not(isConstructor())) // 排除构造函数
.and(not(isStatic())) // 排除静态方法可选
)
// 使用MethodDelegation.to()进行方法委托
.intercept(MethodDelegation.to(DelegateMethodHandler.create(determineMatchType(typeDescription.getName()))))
)
// 配置监听器用于调试和日志
.with(new AgentBuilder.Listener() {
@Override
public void onDiscovery(String typeName, ClassLoader classLoader,
net.bytebuddy.utility.JavaModule module, boolean loaded) {}
@Override
public void onTransformation(net.bytebuddy.description.type.TypeDescription typeDescription,
ClassLoader classLoader, net.bytebuddy.utility.JavaModule module,
boolean loaded, net.bytebuddy.dynamic.DynamicType dynamicType) {
System.out.println("[AGENT] 成功增强类: " + typeDescription.getName() +
" (匹配方式: " + determineMatchType(typeDescription.getName()) + ")");
}
@Override
public void onIgnored(net.bytebuddy.description.type.TypeDescription typeDescription,
ClassLoader classLoader, net.bytebuddy.utility.JavaModule module,
boolean loaded) {}
@Override
public void onError(String typeName, ClassLoader classLoader,
net.bytebuddy.utility.JavaModule module, boolean loaded, Throwable throwable) {
System.err.println("[AGENT] 增强类失败: " + typeName);
throwable.printStackTrace();
}
@Override
public void onComplete(String typeName, ClassLoader classLoader,
net.bytebuddy.utility.JavaModule module, boolean loaded) {}
})
// 安装到JVM
.installOn(inst);
System.out.println("[AGENT] ByteBuddy Agent 安装完成!");
} catch (Exception e) {
System.err.println("[AGENT] Agent安装失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 构建类型匹配器 - 展示多种匹配方式
* 这里演示了ByteBuddy支持的各种目标类匹配方式
*/
private static net.bytebuddy.matcher.ElementMatcher.Junction<net.bytebuddy.description.type.TypeDescription> buildTypeMatchers() {
return
// 方式1精确类名匹配
named("io.github.kazusa.pressmonster.demo.SampleController")
// 方式2Spring @RestController注解匹配使用字符串名称避免类加载问题
.or(isAnnotatedWith(named("org.springframework.web.bind.annotation.RestController")))
// 方式3自定义@Monitored注解匹配
.or(isAnnotatedWith(named("io.github.kazusa.pressmonster.demo.annotation.Monitored")))
// 方式4包路径前缀匹配
.or(nameStartsWith("io.github.kazusa.pressmonster.demo.service"))
// 方式5类名后缀匹配
.or(nameEndsWith("Service"))
// 方式6类名包含匹配
.or(nameContains("Controller"))
// 方式7多包路径匹配OR条件
.or(nameStartsWith("io.github.kazusa.pressmonster.demo.controller")
.or(nameStartsWith("io.github.kazusa.pressmonster.demo.api")))
// 排除条件排除测试类和内部类
.and(not(nameContains("Test")))
.and(not(nameContains("$"))) // 排除内部类
// 排除Java核心类和第三方库
.and(not(nameStartsWith("java.")))
.and(not(nameStartsWith("javax.")))
.and(not(nameStartsWith("sun.")))
.and(not(nameStartsWith("com.sun.")))
.and(not(nameStartsWith("org.springframework.")))
.and(not(nameStartsWith("net.bytebuddy.")));
}
/**
* 根据类名判断匹配类型
* 用于在日志中显示是通过什么方式匹配到的目标类
*/
private static String determineMatchType(String className) {
if ("io.github.kazusa.pressmonster.demo.SampleController".equals(className)) {
return "EXACT_NAME";
} else if (className.contains("Controller")) {
return "SPRING_CONTROLLER";
} else if (className.startsWith("io.github.kazusa.pressmonster.demo.service")) {
return "SERVICE_PACKAGE";
} else if (className.endsWith("Service")) {
return "SERVICE_SUFFIX";
} else {
return "OTHER_RULE";
}
}
}

View File

@ -0,0 +1,36 @@
package io.github.kazusa.pressmonster.demo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义监控注解
* 用于演示基于注解的拦截匹配方式
* 可以标记在类或方法上被标记的类/方法将被Agent拦截
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitored {
/**
* 监控描述信息
*/
String value() default "";
/**
* 是否启用性能监控
*/
boolean enablePerformance() default true;
/**
* 是否记录参数
*/
boolean logParameters() default true;
/**
* 是否记录返回值
*/
boolean logReturn() default true;
}

View File

@ -0,0 +1,147 @@
package io.github.kazusa.pressmonster.demo.service;
import io.github.kazusa.pressmonster.demo.annotation.Monitored;
import java.util.HashMap;
import java.util.Map;
/**
* 订单服务类
* 使用@Monitored注解标记用于演示基于自定义注解的拦截匹配
* 这个类会被Agent通过以下规则匹配
* 1. 自定义注解匹配isAnnotatedWith(named("io.github.kazusa.pressmonster.demo.annotation.Monitored"))
* 2. 包路径前缀匹配nameStartsWith("io.github.kazusa.pressmonster.demo.service")
* 3. 类名后缀匹配nameEndsWith("Service")
*/
@Monitored(value = "订单服务监控", enablePerformance = true, logParameters = true, logReturn = true)
public class OrderService {
/**
* 根据订单ID查找订单信息
*/
@Monitored("查询订单详情")
public Map<String, Object> findOrderById(String orderId) {
System.out.println("OrderService.findOrderById() 执行中...");
// 模拟数据库查询延时
try {
Thread.sleep(15);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Map<String, Object> order = new HashMap<>();
order.put("id", orderId);
order.put("userId", "user123");
order.put("amount", 299.99);
order.put("status", "completed");
order.put("createTime", System.currentTimeMillis() - 86400000); // 一天前
return order;
}
/**
* 根据用户ID获取订单列表
*/
public Map<String, Object> getOrdersByUserId(String userId) {
System.out.println("OrderService.getOrdersByUserId() 执行中...");
// 模拟数据库查询延时
try {
Thread.sleep(25);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Map<String, Object> result = new HashMap<>();
result.put("orders", new String[]{"order001", "order002", "order003"});
result.put("userId", userId);
result.put("total", 3);
result.put("totalAmount", 899.97);
return result;
}
/**
* 创建新订单
*/
@Monitored(value = "创建订单", logParameters = true)
public Map<String, Object> createOrder(String userId, double amount, String productInfo) {
System.out.println("OrderService.createOrder() 执行中...");
// 模拟业务逻辑延时
try {
Thread.sleep(40);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
String newOrderId = "order_" + System.currentTimeMillis();
Map<String, Object> newOrder = new HashMap<>();
newOrder.put("id", newOrderId);
newOrder.put("userId", userId);
newOrder.put("amount", amount);
newOrder.put("productInfo", productInfo);
newOrder.put("status", "pending");
newOrder.put("createTime", System.currentTimeMillis());
return newOrder;
}
/**
* 更新订单状态
*/
public boolean updateOrderStatus(String orderId, String newStatus) {
System.out.println("OrderService.updateOrderStatus() 执行中...");
// 模拟数据库操作延时
try {
Thread.sleep(20);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 模拟状态更新成功
return true;
}
/**
* 取消订单
*/
@Monitored("取消订单")
public boolean cancelOrder(String orderId, String reason) {
System.out.println("OrderService.cancelOrder() 执行中...");
// 模拟业务逻辑延时
try {
Thread.sleep(30);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 模拟取消成功
return true;
}
/**
* 计算订单总金额演示可能出现异常的方法
*/
public double calculateTotalAmount(String[] orderIds) {
System.out.println("OrderService.calculateTotalAmount() 执行中...");
if (orderIds == null || orderIds.length == 0) {
throw new IllegalArgumentException("订单ID列表不能为空");
}
// 模拟计算延时
try {
Thread.sleep(35);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 模拟计算总金额
return orderIds.length * 199.99;
}
}

View File

@ -0,0 +1,117 @@
package io.github.kazusa.pressmonster.demo.service;
import java.util.HashMap;
import java.util.Map;
/**
* 用户服务类
* 用于演示基于包路径和类名后缀的拦截匹配
* 这个类会被Agent通过以下规则匹配
* 1. 包路径前缀匹配nameStartsWith("io.github.kazusa.pressmonster.demo.service")
* 2. 类名后缀匹配nameEndsWith("Service")
*/
public class UserService {
/**
* 根据用户ID查找用户信息
*/
public Map<String, Object> findUserById(String userId) {
System.out.println("UserService.findUserById() 执行中...");
// 模拟数据库查询延时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Map<String, Object> user = new HashMap<>();
user.put("id", userId);
user.put("name", "User " + userId);
user.put("email", "user" + userId + "@example.com");
user.put("status", "active");
return user;
}
/**
* 获取所有用户列表
*/
public Map<String, Object> getAllUsers() {
System.out.println("UserService.getAllUsers() 执行中...");
// 模拟数据库查询延时
try {
Thread.sleep(20);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Map<String, Object> result = new HashMap<>();
result.put("users", new String[]{"user1", "user2", "user3", "user4", "user5"});
result.put("total", 5);
result.put("page", 1);
result.put("pageSize", 10);
return result;
}
/**
* 创建新用户
*/
public Map<String, Object> createUser(String name, String email) {
System.out.println("UserService.createUser() 执行中...");
// 模拟数据库操作延时
try {
Thread.sleep(30);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
String newUserId = "user_" + System.currentTimeMillis();
Map<String, Object> newUser = new HashMap<>();
newUser.put("id", newUserId);
newUser.put("name", name);
newUser.put("email", email);
newUser.put("status", "active");
newUser.put("createTime", System.currentTimeMillis());
return newUser;
}
/**
* 更新用户信息
*/
public boolean updateUser(String userId, Map<String, Object> updateData) {
System.out.println("UserService.updateUser() 执行中...");
// 模拟数据库操作延时
try {
Thread.sleep(25);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 模拟更新成功
return true;
}
/**
* 删除用户
*/
public boolean deleteUser(String userId) {
System.out.println("UserService.deleteUser() 执行中...");
// 模拟数据库操作延时
try {
Thread.sleep(15);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 模拟删除成功
return true;
}
}