|
| 1 | +# Java Hook 方法整理 |
| 2 | + |
| 3 | +在 Java 中,Hook 技术主要用于在不修改原始代码的情况下,对程序的执行流程进行拦截和增强。根据 Hook 生效的时机,可以将其分为 **静态 Hook (编译期/加载期)** 和 **动态 Hook (运行期)**。 |
| 4 | + |
| 5 | +## 1. 静态 Hook (Static Hook) |
| 6 | + |
| 7 | +静态 Hook 指在程序运行之前(编译阶段)或类加载阶段(Load Time)修改字节码。 |
| 8 | + |
| 9 | +### 1.1 AspectJ (编译时织入 - Compile-Time Weaving) |
| 10 | + |
| 11 | +AspectJ 是最成熟的 AOP 框架,它可以在编译阶段将切面代码直接织入到目标类的 `.class` 文件中。 |
| 12 | + |
| 13 | +* **原理**:使用 `ajc` 编译器代替 `javac`,在编译时修改字节码。 |
| 14 | +* **优点**: |
| 15 | + * 运行效率高(无运行时代理开销)。 |
| 16 | + * 功能最强(可 Hook 构造函数、静态方法、final 方法、私有方法)。 |
| 17 | +* **缺点**:需要特定的编译工具链支持,配置相对复杂。 |
| 18 | +* **示例**: |
| 19 | + ```java |
| 20 | + public aspect LogAspect { |
| 21 | + // 定义切点:所有 Service 结尾类的所有方法 |
| 22 | + pointcut serviceMethods(): execution(* *..*Service.*(..)); |
| 23 | + |
| 24 | + // 前置通知 |
| 25 | + before(): serviceMethods() { |
| 26 | + System.out.println("Before method execution"); |
| 27 | + } |
| 28 | + } |
| 29 | + ``` |
| 30 | + |
| 31 | +### 1.2 Java Agent (Premain - 加载时织入) |
| 32 | + |
| 33 | +利用 JVM 的 `Instrumentation` API,在类加载(Class Loading)阶段修改字节码。 |
| 34 | + |
| 35 | +* **原理**: |
| 36 | + 1. 编写一个包含 `premain` 方法的 Agent Jar。 |
| 37 | + 2. 启动应用时添加参数 `-javaagent:myagent.jar`。 |
| 38 | + 3. JVM 启动时加载 Agent,调用 `premain`。 |
| 39 | + 4. Agent 注册 `ClassFileTransformer`。 |
| 40 | + 5. 当类被加载时,Transformer 拦截字节码并进行修改(使用 ASM, Javassist, ByteBuddy 等库)。 |
| 41 | +* **示例**: |
| 42 | + ```java |
| 43 | + public static void premain(String agentArgs, Instrumentation inst) { |
| 44 | + inst.addTransformer(new MyClassFileTransformer()); |
| 45 | + } |
| 46 | + ``` |
| 47 | +* **场景**:全链路监控(SkyWalking, Pinpoint)、全局日志埋点。 |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +## 2. 动态 Hook (Runtime Hook) |
| 52 | + |
| 53 | +动态 Hook 指在程序运行过程中,动态地创建代理对象或修改已加载的类。 |
| 54 | + |
| 55 | +### 2.1 动态代理 (Dynamic Proxy) |
| 56 | + |
| 57 | +JDK 自带的动态代理机制,基于接口实现。 |
| 58 | + |
| 59 | +* **原理**:利用 `java.lang.reflect.Proxy` 在内存中生成一个实现了目标接口的新类。 |
| 60 | +* **限制**:**只能代理接口**,无法代理未实现接口的类。 |
| 61 | +* **示例**: |
| 62 | + ```java |
| 63 | + Service proxy = (Service) Proxy.newProxyInstance(loader, interfaces, handler); |
| 64 | + ``` |
| 65 | + |
| 66 | +### 2.2 CGLIB / ByteBuddy (子类代理) |
| 67 | + |
| 68 | +通过生成目标类的子类来实现代理。 |
| 69 | + |
| 70 | +* **原理**:在运行时动态生成目标类的子类,并重写非 `final` 方法,在子类中插入拦截逻辑。 |
| 71 | +* **优点**:无需接口,可代理普通类。 |
| 72 | +* **限制**:**无法代理 final 类或 final 方法**。 |
| 73 | +* **场景**:Spring AOP (无接口时默认使用 CGLIB)。 |
| 74 | +* **示例 (CGLIB)**: |
| 75 | + ```java |
| 76 | + Enhancer enhancer = new Enhancer(); |
| 77 | + enhancer.setSuperclass(TargetClass.class); |
| 78 | + enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { |
| 79 | + System.out.println("Before " + method.getName()); |
| 80 | + Object result = proxy.invokeSuper(obj, args); // 注意调用 invokeSuper |
| 81 | + System.out.println("After " + method.getName()); |
| 82 | + return result; |
| 83 | + }); |
| 84 | + TargetClass proxy = (TargetClass) enhancer.create(); |
| 85 | + ``` |
| 86 | + |
| 87 | +### 2.3 Java Agent (Agentmain - 运行时重定义) |
| 88 | + |
| 89 | +利用 JVM 的 Attach 机制,在 JVM 运行时动态注入 Agent。 |
| 90 | + |
| 91 | +* **原理**: |
| 92 | + 1. 通过 `VirtualMachine.attach(pid)` 连接到目标 JVM 进程。 |
| 93 | + 2. 加载 Agent Jar,触发 `agentmain` 方法。 |
| 94 | + 3. 获取 `Instrumentation` 实例。 |
| 95 | + 4. 调用 `inst.retransformClasses(targetClass)` 触发类的重定义。 |
| 96 | +* **能力**:可以在不重启应用的情况下修改类逻辑(热部署)。 |
| 97 | +* **限制**:运行时修改字节码有严格限制(如不能新增/删除字段或方法,只能修改方法体)。 |
| 98 | +* **场景**:Arthas (在线诊断), JRebel (热部署)。 |
| 99 | +* **示例**: |
| 100 | + ```java |
| 101 | + // Agentmain 入口 |
| 102 | + public static void agentmain(String agentArgs, Instrumentation inst) { |
| 103 | + inst.addTransformer(new MyClassFileTransformer(), true); |
| 104 | + try { |
| 105 | + // 触发已加载类的重转换 |
| 106 | + inst.retransformClasses(TargetClass.class); |
| 107 | + } catch (UnmodifiableClassException e) { |
| 108 | + e.printStackTrace(); |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + // Attach 客户端代码 (通常在另一个进程运行) |
| 113 | + public static void main(String[] args) throws Exception { |
| 114 | + String pid = "12345"; // 目标 JVM 进程 ID |
| 115 | + VirtualMachine vm = VirtualMachine.attach(pid); |
| 116 | + vm.loadAgent("/path/to/agent.jar"); |
| 117 | + vm.detach(); |
| 118 | + } |
| 119 | + ``` |
| 120 | + |
| 121 | +### 2.4 Native Hook (JNI / JVMTI) |
| 122 | + |
| 123 | +跳出 JVM 层面,直接在操作系统或 Native 层面进行 Hook。 |
| 124 | + |
| 125 | +* **原理**:使用 JNI 调用 C/C++ 代码,利用操作系统的 Hook 技术(如 PLT Hook, Inline Hook)或 JVMTI (JVM Tool Interface) 事件回调。 |
| 126 | +* **场景**: |
| 127 | + * JVM 自身性能分析(Profiler)。 |
| 128 | + * 深度调试。 |
| 129 | + * 系统级调用监控。 |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +### 总结对比 |
| 134 | + |
| 135 | +| 类别 | 技术方案 | 生效时机 | 核心特点 | 适用场景 | |
| 136 | +| :--- | :--- | :--- | :--- | :--- | |
| 137 | +| **静态 Hook** | **AspectJ (CTW)** | 编译期 | 修改 .class 文件,性能最高,无限制 | 复杂切面,高性能要求 | |
| 138 | +| **静态 Hook** | **Java Agent (Premain)** | 类加载期 | 修改字节码,无侵入 | APM 监控,字节码增强 | |
| 139 | +| **动态 Hook** | **JDK 动态代理** | 运行时 | 基于接口,生成代理对象 | RPC, 简单 AOP | |
| 140 | +| **动态 Hook** | **CGLIB/ByteBuddy** | 运行时 | 基于子类,生成代理对象 | Spring AOP | |
| 141 | +| **动态 Hook** | **Java Agent (Attach)** | 运行时 | 重定义已加载的类 (Retransform) | 在线诊断 (Arthas),热修复 | |
0 commit comments