diff --git a/press-monster-test/demo/README.md b/press-monster-test/demo/README.md
new file mode 100644
index 0000000..a9ca31c
--- /dev/null
+++ b/press-monster-test/demo/README.md
@@ -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编程的参考示例。*
\ No newline at end of file
diff --git a/press-monster-test/demo/pom.xml b/press-monster-test/demo/pom.xml
index 3fdef6e..f09d622 100644
--- a/press-monster-test/demo/pom.xml
+++ b/press-monster-test/demo/pom.xml
@@ -32,6 +32,18 @@
press-monster-common
+
+
+ net.bytebuddy
+ byte-buddy
+ 1.14.9
+
+
+ net.bytebuddy
+ byte-buddy-agent
+ 1.14.9
+
+
org.springframework
@@ -59,4 +71,71 @@
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.3.0
+
+
+
+ true
+ io.github.kazusa.pressmonster.demo.DemoApplication
+
+
+
+ io.github.kazusa.pressmonster.demo.agent.SimpleAgent
+ io.github.kazusa.pressmonster.demo.agent.SimpleAgent
+ true
+ true
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.4.1
+
+
+ package
+
+ shade
+
+
+ false
+
+
+ io.github.kazusa.pressmonster.demo.DemoApplication
+
+ io.github.kazusa.pressmonster.demo.agent.SimpleAgent
+ io.github.kazusa.pressmonster.demo.agent.SimpleAgent
+ true
+ true
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/press-monster-test/demo/run-demo.sh b/press-monster-test/demo/run-demo.sh
new file mode 100755
index 0000000..249d427
--- /dev/null
+++ b/press-monster-test/demo/run-demo.sh
@@ -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"
\ No newline at end of file
diff --git a/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/DemoApplication.java b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/DemoApplication.java
index 625872e..d3b266b 100644
--- a/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/DemoApplication.java
+++ b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/DemoApplication.java
@@ -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 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 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();
+ }
}
\ No newline at end of file
diff --git a/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/DelegateMethodHandler.java b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/DelegateMethodHandler.java
new file mode 100644
index 0000000..b0cb141
--- /dev/null
+++ b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/DelegateMethodHandler.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/LoggingInterceptor.java b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/LoggingInterceptor.java
new file mode 100644
index 0000000..f69b489
--- /dev/null
+++ b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/LoggingInterceptor.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/MethodInterceptor.java b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/MethodInterceptor.java
new file mode 100644
index 0000000..b611815
--- /dev/null
+++ b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/MethodInterceptor.java
@@ -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;
+}
\ No newline at end of file
diff --git a/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/SimpleAgent.java b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/SimpleAgent.java
new file mode 100644
index 0000000..d02584d
--- /dev/null
+++ b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/agent/SimpleAgent.java
@@ -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 buildTypeMatchers() {
+ return
+ // 方式1:精确类名匹配
+ named("io.github.kazusa.pressmonster.demo.SampleController")
+
+ // 方式2:Spring @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";
+ }
+ }
+}
\ No newline at end of file
diff --git a/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/annotation/Monitored.java b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/annotation/Monitored.java
new file mode 100644
index 0000000..230b2c1
--- /dev/null
+++ b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/annotation/Monitored.java
@@ -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;
+}
\ No newline at end of file
diff --git a/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/service/OrderService.java b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/service/OrderService.java
new file mode 100644
index 0000000..d451a47
--- /dev/null
+++ b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/service/OrderService.java
@@ -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 findOrderById(String orderId) {
+ System.out.println("OrderService.findOrderById() 执行中...");
+
+ // 模拟数据库查询延时
+ try {
+ Thread.sleep(15);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ Map 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 getOrdersByUserId(String userId) {
+ System.out.println("OrderService.getOrdersByUserId() 执行中...");
+
+ // 模拟数据库查询延时
+ try {
+ Thread.sleep(25);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ Map 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 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 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;
+ }
+}
\ No newline at end of file
diff --git a/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/service/UserService.java b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/service/UserService.java
new file mode 100644
index 0000000..0524dfe
--- /dev/null
+++ b/press-monster-test/demo/src/main/java/io/github/kazusa/pressmonster/demo/service/UserService.java
@@ -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 findUserById(String userId) {
+ System.out.println("UserService.findUserById() 执行中...");
+
+ // 模拟数据库查询延时
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ Map 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 getAllUsers() {
+ System.out.println("UserService.getAllUsers() 执行中...");
+
+ // 模拟数据库查询延时
+ try {
+ Thread.sleep(20);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ Map 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 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 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 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;
+ }
+}
\ No newline at end of file