Bytebuddy委托拦截样例
This commit is contained in:
parent
9d36ad9121
commit
27adffa183
|
|
@ -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编程的参考示例。*
|
||||
|
|
@ -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>
|
||||
|
|
@ -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"
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
||||
// 方式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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue