Java高級之基于Java Attach與Byte-Buddy實現SQL語句增強

目錄

一 Agent 模塊

1 HookAgent.java

2 FormatAdvice.java

3 配置文件

二 Attacher 模塊

1 AttachMain.java

三 測試模塊

1 DruidTest.java

四 驗證步驟

五 原理解析


筆者目標寫一款數據分析中間件,用來增強當前主流開源項目,前幾天寫了一票用Byte-Buddy對外部類庫進行增強的方案,但此方案需要在啟動java進程時指定javaagent參數,雖然方案更可靠,但依然覺得對源系統有侵入性。本文針對上一篇文章基礎上實現的完全無侵入增強技術方案。

在 Java 應用開發過程中,我們常常需要對第三方庫或框架的行為進行增強或定制,以滿足特定的業務需求。然而,由于對源代碼不可控或不希望對其進行永久修改,如何在運行時對已有類的方法進行非侵入式動態增強就成為一個重要課題。

本文將詳細介紹如何結合 Java Attach 機制與 Byte Buddy 庫,在不修改原始字節碼文件(非持久化修改)的前提下,對 com.alibaba.druid.sql.SQLUtils.format 方法進行增強,自動為所有符合條件的 SQL 語句追加 LIMIT 限制。整個示例包括三個模塊:

  1. agent:字節碼增強代理,實現增強邏輯。

  2. attacher:Attach 工具,通過 Attach API 將代理動態注入到目標 JVM。

  3. testapp:測試應用,演示增強前后輸出對比。

  • 項目結構

    父工程采用 Maven 聚合項目(packaging 為 pom),包含三個子模塊:

    • agent:構建可重定義/重轉字節碼的 shaded agent jar。

    • attacher:封裝 AttachMain 主程序,用于將 agent 注入到運行中的 JVM。

    • testapp:依賴 Druid,演示 SQL 格式化前后對比。

  • 父工程 POM

父工程 POM 定義了聚合構建與模塊列表:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion>
?<groupId>org.example</groupId><artifactId>code-gen</artifactId><version>1.0-SNAPSHOT</version><modules><module>agent</module><module>attacher</module><module>testapp</module></modules><packaging>pom</packaging>
?<name>code-gen</name><url>http://maven.apache.org</url>
?<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties>
?
</project>

此 POM 僅用于聚合,明確了三個模塊的相對路徑與構建順序。

一 Agent 模塊

Agent 模塊主要依賴 Byte Buddy,并通過 Maven Shade Plugin 打包為一個支持重定義和重轉換的 agent jar。核心配置如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>code-gen</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion>
?<artifactId>agent</artifactId><packaging>jar</packaging>
?<name>agent</name><url>http://maven.apache.org</url>
?<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties>
?<dependencies><!-- Byte Buddy 核心庫 --><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.14.12</version></dependency>
?<!-- 字節碼增強支持 --><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy-agent</artifactId><version>1.14.12</version></dependency>
?<!-- ASM 字節碼操作 --><dependency><groupId>org.ow2.asm</groupId><artifactId>asm</artifactId><version>9.6</version></dependency>
?</dependencies>
?
?<build><finalName>agent-shaded</finalName><plugins><!-- 構建 shaded agent jar --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.5.0</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals></execution></executions><configuration><createDependencyReducedPom>false</createDependencyReducedPom><transformers><transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>agent.HookAgent</mainClass><manifestEntries><Agent-Class>agent.HookAgent</Agent-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></transformer></transformers></configuration></plugin>
?<!-- 編譯插件 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.10.1</version><configuration><source>8</source><target>8</target></configuration></plugin></plugins></build>
</project>
?

1 HookAgent.java

代理入口實現了 agentmain 方法,用于 Attach 注入后的邏輯:

package agent;
?
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
?
import java.lang.instrument.Instrumentation;
?
public class HookAgent {public static void agentmain(String args, Instrumentation inst) {try{System.out.println("[Agent] Enhancing SQLUtils.format...");new AgentBuilder.Default().with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION).type(ElementMatchers.named("com.alibaba.druid.sql.SQLUtils")).transform((builder, td, cl, module, pd) -> builder.method(ElementMatchers.named("format").and(ElementMatchers.takesArguments(2)).and(ElementMatchers.takesArgument(0, String.class))).intercept(Advice.to(FormatAdvice.class))).installOn(inst);}catch (Throwable t){System.err.println("Agent init failed: ");t.printStackTrace();}}
}

該邏輯通過 Byte Buddy 定位到 SQLUtils.format(String, DbType) 方法,并將其攔截,委托給 FormatAdvice

2 FormatAdvice.java

增強邏輯的具體實現:

package agent;
?
import net.bytebuddy.asm.Advice;
?
public class FormatAdvice {
?@Advice.OnMethodEnterpublic static void onEnter(@Advice.Argument(value = 0, readOnly = false) String sql) {if (sql == null || sql.toLowerCase().contains("limit")) return;
?sql = sql.trim();if (sql.endsWith(";")) {sql = sql.substring(0, sql.length() - 1);}sql += " LIMIT 100";}
}

在方法執行入口,將不含 limit 的 SQL 自動追加 LIMIT 100

3 配置文件

resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Agent-Class: agent.HookAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

二 Attacher 模塊

Attacher 模塊依賴 JDK 自帶的 tools.jar,封裝 Attach API 調用:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>code-gen</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion>
?<artifactId>attacher</artifactId><packaging>jar</packaging>
?<name>attacher</name><url>http://maven.apache.org</url>
?<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties>
?<dependencies><dependency><groupId>com.sun</groupId><artifactId>tools</artifactId><version>1.8.0</version><scope>system</scope><!-- 替換成自己的 --><systemPath>/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/lib/tools.jar</systemPath></dependency></dependencies>
?
?<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.5.0</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration><transformers><transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>attacher.AttachMain</mainClass></transformer></transformers></configuration></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>8</source><target>8</target></configuration></plugin></plugins></build>
</project>

1 AttachMain.java

package attacher;
?
import com.sun.tools.attach.VirtualMachine;
?
public class AttachMain {public static void main(String[] args) throws Exception {if (args.length != 2) {System.out.println("Usage: java AttachMain <agent-path> <PID>");return;}VirtualMachine vm = VirtualMachine.attach(args[1]);vm.loadAgent(args[0]);vm.detach();}
}

通過傳入 agent JAR 路徑與目標 JVM PID,即可在運行時注入 HookAgent。無需重啟服務。


三 測試模塊

依賴 Druid,用于演示增強前后 SQL 格式化輸出:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>code-gen</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion>
?<artifactId>testapp</artifactId><packaging>jar</packaging>
?<name>testapp</name><url>http://maven.apache.org</url>
?<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties>
?<dependencies><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.18</version></dependency></dependencies>
?<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.5.0</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration><transformers><transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>app.DruidTest</mainClass></transformer></transformers></configuration></execution></executions></plugin></plugins></build>
?
</project>

1 DruidTest.java

package app;
?
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
?
/** 測試步驟*  1 啟動服務: java -cp testapp-1.0-SNAPSHOT.jar app.DruidTest*  2 查看服務PID: jps => 16498 DruidTest*  3 注入增強: java --add-modules jdk.attach -cp attacher-1.0-SNAPSHOT.jar attacher.AttachMain agent/target/agent-shaded.jar xxx*  執行結果:* ? ?  Before agent:* ? ?  SELECT id* ? ?  FROM ods__order* ? ?  [Agent] Enhancing SQLUtils.format...* ? ?  After agent:* ? ?  [Agent] Modified SQL: SELECT id FROM ods__order LIMIT 100* ? ?  SELECT id* ? ?  FROM ods__order* ? ?  LIMIT 100*/
public class DruidTest {public static void main(String[] args) throws Exception {String sql = "SELECT id FROM ods__order";System.out.println("Before agent:");System.out.println(SQLUtils.format(sql, DbType.mysql));
?Thread.sleep(60000); // 等待 Attach 注入
?System.out.println("After agent:");System.out.println(SQLUtils.format(sql, DbType.mysql));}
}

程序啟動后,先打印原始格式化結果,然后等待 60 秒,期間可通過 Attacher 工具注入 agent,最后再次打印格式化結果,以驗證增強效果。


四 驗證步驟

  1. 構建各模塊

    mvn clean package
  2. 啟動測試應用

    java -cp testapp-1.0-SNAPSHOT.jar app.DruidTest
  3. 查詢應用 PID

    jps
    # 例如輸出: 16498 DruidTest
  4. 注入增強 Agent

    java -cp attacher-1.0-SNAPSHOT.jar attacher.AttachMain agent/target/agent-shaded.jar 16498
  5. 觀察輸出

    • Before agent:

      SELECT id
      FROM ods__order
    • 注入日志:

      [Agent] Enhancing SQLUtils.format...
    • After agent:

      SELECT id
      FROM ods__order
      LIMIT 100

五 原理解析

  1. Java Attach 機制

    • Attach API 是 JDK 提供的工具,可動態向運行中的 JVM 進程注入 agent,而無需重啟。

    • 通過 VirtualMachine.attach(pid) 方法連接目標 JVM,再調用 loadAgent(path) 注入 agent jar。

  2. Byte Buddy

    • 高級字節碼操作庫,提供易用的 DSL 定義增強規則。

    • 支持重定義(redefinition)和重轉換(retransformation)兩種策略。本文使用重轉換,使已有類能夠在運行時更新方法實現。

  3. Advice 機制

    • Byte Buddy 的 Advice 注解可在方法入口或退出時插入自定義邏輯,類似 AOP 切面。

    • 通過 @Advice.OnMethodEnter 標注的方法可修改參數或執行前置邏輯。

本文示例展示了如何結合 Java Attach 與 Byte Buddy,在運行時對第三方庫(Druid)的 SQLUtils.format 方法進行無感知增強,無需修改原始代碼,且對生產環境影響極小。此思路同樣適用于其他需要動態增強的場景,如監控、埋點、執行權限控制等。希望本文能幫助讀者快速上手該技術并在項目中靈活應用。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/916279.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/916279.shtml
英文地址,請注明出處:http://en.pswp.cn/news/916279.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

2025第五屆生物發酵營養源高峰論壇

一、會議時間會議時間:2025年8月8日二、會議地點上海新國際博覽中心–W4館現場2號會議室三、組織單位主辦單位:中國生物發酵產業協會承辦單位:浙江工業大學樂斯福集團Procelys 樂斯福發酵營養元參會福利&#xff0c;助力高效交流為提升參會體驗&#xff0c;組委會特別推出多項福…

Kubernetes 配置管理

這里寫目錄標題什么是 ConfigMap創建 ConfigMap基于目錄創建 ConfigMap創建 conf 目錄&#xff0c;里面放置兩個文件基于目錄下的所有文件創建 ConfigMap查看當前創建的 ConfigMap基于文件創建 ConfigMap創建測試文件 game-cfg基于單個文件創建 ConfigMap查看當前創建的 Config…

ESP32+MicroPython:用Python玩轉物聯網開發

什么是ESP32&#xff1f; ESP32作為當下最熱門的物聯網開發板&#xff0c;常被比作"嵌入式世界的瑞士軍刀"。但很多初學者會混淆芯片、模組和開發板的概念&#xff0c;其實它們的關系很簡單&#xff1a; 芯片(Soc)&#xff1a;核心處理器&#xff0c;如ESP32-D0WD模…

opencv學習(圖像金字塔)

1.什么是圖像金字塔圖像金字塔是一種多尺度圖像表示方法&#xff0c;通過對原始圖像進行下采樣&#xff08;縮小&#xff09;和上采樣&#xff08;放大&#xff09;&#xff0c;生成一系列不同分辨率的圖像集合&#xff0c;形似 “金字塔”&#xff08;底部是高分辨率原始圖像&…

從 C# 到 Python:項目實戰第五天的飛躍

在前面三天的學習中&#xff0c;我們已經掌握了 Python 的基礎語法、數據結構以及一些核心庫的使用。今天&#xff0c;我們將通過三個實戰項目&#xff0c;深入對比 C# 和 Python 在命令行工具開發、Web 應用開發以及數據處理方面的差異&#xff0c;感受 Python 在實際項目中的…

rabbitmq 03

一、mq的作用和使用場景 MQ的基本作用 MQ&#xff08;Message Queue&#xff0c;消息隊列&#xff09;是一種應用程序對應用程序的通信方法&#xff0c;主要作用包括&#xff1a; 異步處理&#xff1a;解耦生產者和消費者&#xff0c;允許生產者發送消息后立即返回&#xff0…

Ubuntu 24.04 顯示中文+使用中文鍵盤

ubuntu 24.04 中文顯示中文鍵盤Ubuntu中文輸入重啟iBus服務Ubuntu中文輸入 安裝的Ubuntu24.04&#xff0c;一般默認是英文的&#xff0c;要使用中文的話&#xff0c;可以通過命令行設置&#xff0c;也可以使用‘設置’&#xff0c;在圖形化界面中操作。 下面是在‘設置’的圖形…

Docker實戰:Tomcat容器從部署到自定義網頁的完整操作

Docker實戰&#xff1a;Tomcat容器從部署到自定義網頁的完整操作 繼Nginx容器部署后&#xff0c;我們再來實操Tomcat容器的使用——從拉取鏡像、啟動容器&#xff0c;到端口映射、網頁掛載&#xff0c;全程通過實際命令演示&#xff0c;帶你掌握Tomcat在Docker中的核心用法。 一…

使用cherry studio離線搭建私人知識庫流程記錄

本篇文章記錄近期嘗試在個人筆記本上、全離線狀態下搭建知識庫的流程。用到的工具包括&#xff1a;Cherry Studio、ollama。主要過程是&#xff1a;首先下載ollama用于管理大模型&#xff1b;然后&#xff0c;從魔塔社區下載需要的deepseek、千問大模型和bge-m3嵌入模型&#x…

【工具類】Linux 環境利用 uv 安裝多版本 python

文章目錄前置工作環境說明如果kali無法訪問網絡pypi 換源安裝 uvuv 寫入環境變量臨時寫入永久寫入無法打開 github 解決方案&#xff08;注意此方法可能也會失效&#xff09;安裝多版本 python查看已安裝的pythonuv python install到 uv 的 github 主頁&#xff0c;找安裝文件下…

求職招聘小程序源碼招聘小程序開發定制

身份&#xff1a;求職者、企業求職者&#xff1a;完善簡歷&#xff0c;簡歷投遞企業&#xff1a;企業入駐&#xff0c;查看簡歷企業會員&#xff1a;半年 、年度 權益&#xff1a;每日發布條數、刷新條數&#xff0c;簡歷下載數量聊天&#xff1a;求職者可以和企業聊天招聘會…

Git 使用全指南:從配置到免密登錄

Git 使用全指南&#xff1a;從配置到免密登錄一、Git 基礎配置二、Git 代碼提交流程2.1 克隆遠程倉庫2.2 創建并切換分支2.3 暫存文件2.4 提交到本地倉庫2.5 拉取遠程最新代碼2.6 推送本地分支到遠程三、VSCode 服務器免密登錄配置3.1 生成 Windows SSH 密鑰3.2 復制公鑰到服務…

組合期權:領式策略

文章目錄0.簡介1.多頭領式策略&#xff08;Long Collar&#xff09;?1.1 策略構成1.2 適用場景?1.3 損益分析1.4 案例示范2.空頭領式策略&#xff08;Short Collar&#xff09;?2.1 策略構成2.2 適用場景2.3 損益分析2.4 案例示范參考文獻0.簡介 領式策略&#xff08;Colla…

ECSPI控制器

目錄 SPI協議簡介 極性與相位 SPI框圖 單字節收發 發送數據流程 接收數據流程 ECSPI控制器 關鍵特性 時鐘源 主機模式 等待狀態 片選控制 單突發傳輸 多突發傳輸 相位控制 ECSPI Memory Map ECSPI寄存器 ECSPIx_RXDATA ECSPIx_TXDATA ?編輯 ECSPIx_CONREG …

HTTP 與 SpringBoot 參數提交與接收協議方式

HTTP 協議支持多種參數提交方式&#xff0c;主要取決于請求方法(Method)和內容類型(Content-Type)。以下是主要的參數提交協議&#xff1a;1. URL 查詢參數 (Query Parameters)請求方法: GET (也可用于其他方法)格式: ?key1value1&key2value2示例: GET /users?id123&…

Lua(數組)

Lua 數組基礎概念Lua 中的數組實際上是用整數索引的 table&#xff0c;是一種特殊形式的表。數組索引通常從 1 開始&#xff08;Lua 慣例&#xff09;&#xff0c;但也可以從其他值開始。創建數組通過表構造器初始化數組&#xff1a;-- 索引從 1 開始的數組 local arr {10, …

【Docker項目實戰】在Docker環境下部署go-file文件分享工具

【Docker項目實戰】在Docker環境下部署go-file文件分享工具一、go-file介紹1.1 go-file簡介1.2 go-file特點1.3 go-file使用場景二、本地環境介紹2.1 本地環境規劃2.2 本次實踐介紹三、本地環境檢查3.1 檢查Docker服務狀態3.2 檢查Docker版本3.3 檢查docker compose 版本四、下…

C++基礎學習——文件操作詳解

一、文件流類概述 C 標準庫提供了三個主要的文件流類&#xff1a; ifstream (輸入文件流)&#xff1a;用于從文件讀取數據ofstream (輸出文件流)&#xff1a;用于向文件寫入數據fstream (文件流)&#xff1a;既可讀又可寫 這些類都繼承自 iostream 類&#xff0c;因此可以使用 …

Android補全計劃 DrawerLayout使用

DrawerLayout其實用了很久了&#xff0c;甚至封裝了一些代碼方便不同項目使用&#xff0c;但重構代碼的時候突然意識到這塊內容很不成體系&#xff0c;因此又參考了些文檔&#xff0c;組建了自己的一個文檔。 toolbardrawerlayout能寫的效果很多&#xff0c;在此我也只是截取了…

人工智能之數學基礎:概率論之韋恩圖的應用

韋恩圖的應用由于事件的計算有時候太過于抽象了&#xff0c;此時我們可以使用韋恩圖的方式來進行驗證&#xff0c;我們下面來舉一個例子&#xff0c;A∪B&#xff09;-CA∪(B-C)是否成立&#xff1f;我們可以通過韋恩圖來完成這個任務&#xff1a;我們通過這種方式來一點一點的…