作者:小傅哥
部落格:https://bugstack.cn
沉澱、分享、成長,讓自己和他人都能有所收穫!?
一、前言
片面了!
一月三舟,托爾斯泰說:“多麼偉大的作家,也不過就是在書寫自己的片面而已”。何況是我,何況是我們!
雖然我們不書寫文章,但我們寫需求、寫程式碼、寫註釋,當我們遇到了需要被討論的問題點時,往往變成了爭論點。這個好、那個差、你用的都是啥啥啥!
當你把路走窄了,你所能接受到的新的思路、新的想法、新的視野,以及非常重要的收入,也都會隨之減少。只有橫向對比、參考借鑑、查漏補缺,才能讓你的頭腦中會有更多的思路,無論是在寫程式碼上、還是在理財上、還是在生活上。
二、需求目的
你是否有在使用 IntelliJ IDEA 做開發的過程,需要拿到執行 SQL 語句,複製出來做驗證的時候,總是這樣的語句:SELECT * FROM USER WHERE id = ? AND name = ?
又需要自己把 ? 號
替換成入參值呢?
當然這個需求其實並不大,甚至你還可以使用其他方式解決。那麼在本章節會給你提供一個新的思路,可能你幾乎是沒過的方式進行處理。
那麼在這個章節的案例中我們用到基於 IDEA Plugin 開發能力,把位元組碼插樁探針,基於 Javaagent 的能力,注入到程式碼中。再通過增強後的位元組碼,獲取到 com.mysql.jdbc.PreparedStatement
-> executeInternal
執行時的物件,從而拿到可以直接測試的 SQL 語句。
三、案例開發
1. 工程結構
guide-idea-plugin-probe
├── .gradle
├── probe-agent
│ ├── src
│ │ └── main
│ │ └── java
│ │ └── cn.bugstack.guide.idea.plugin
│ │ ├── MonitorMethod.java
│ │ └── PreAgent.java
│ └── build.gradle
└── probe-plugin
│ └── src
│ │ └── main
│ │ ├── java
│ │ │ └── cn.bugstack.guide.idea.plugin
│ │ │ └── utils
│ │ │ │ └── PluginUtil.java
│ │ │ └── PerRun.java
│ │ └── resources
│ │ └── META-INF
│ │ └── plugin.xml
│ └── build.gradle
├── build.gradle
└── gradle.properties
原始碼獲取:#公眾號:bugstack蟲洞棧
回覆:idea
即可下載全部 IDEA 外掛開發原始碼
在此 IDEA 外掛工程中,工程結構分為2塊:
- probe-agent:探針模組,用於編譯打包提供位元組碼增強服務,給 probe-plugin 模組使用
- probe-plugin:外掛模組,通過
java.programPatcher
載入位元組碼增強包,獲取並列印執行資料庫操作的 SQL 語句。
2. 位元組碼增強獲取 SQL
此處的位元組碼增強方式,採用的 Byte-Buddy 位元組碼框架,它的使用方式更加簡單,在使用的過程中有些像使用 AOP 的攔截方式一樣,獲取到你需要的資訊。
此外在 gradle 打包構建的時候,需要新增 shadowJar
模組,把 Premain-Class
打包進去。這部分程式碼中可以檢視
2.1 探針入口
cn.bugstack.guide.idea.plugin.PreAgent
//JVM 首先嚐試在代理類上呼叫以下方法
public static void premain(String agentArgs, Instrumentation inst) {
AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
return builder
.method(ElementMatchers.named("executeInternal")) // 攔截任意方法
.intercept(MethodDelegation.to(MonitorMethod.class)); // 委託
};
new AgentBuilder
.Default()
.type(ElementMatchers.nameStartsWith("com.mysql.jdbc.PreparedStatement"))
.transform(transformer)
.installOn(inst);
}
- 通過 Byte-buddy 配置,攔截匹配的類和方法,因為這個類和方法下,可以獲取到完整吃執行 SQL 語句。
2.2 攔截 SQL
cn.bugstack.guide.idea.plugin.MonitorMethod
@RuntimeType
public static Object intercept(@This Object obj, @Origin Method method, @SuperCall Callable<?> callable, @AllArguments Object... args) throws Exception {
try {
return callable.call();
} finally {
String originalSql = (String) BeanUtil.getFieldValue(obj, "originalSql");
String replaceSql = ReflectUtil.invoke(obj, "asSql");
System.out.println("資料庫名稱:Mysql");
System.out.println("執行緒ID:" + Thread.currentThread().getId());
System.out.println("時間:" + new Date());
System.out.println("原始SQL:\r\n" + originalSql);
System.out.println("替換SQL:\r\n" + replaceSql);
}
}
- 攔截方法入參是一種可配置操作,比如
@This Object obj
是為了獲取當前類的執行物件,@Origin Method method
是為了獲取執行方法。 - 在 finally 塊中,我們可以通過反射拿到當前類的屬性資訊,以及反射拿到執行的 SQL,並做列印輸出。
2.3 編譯打包
在測試和開發 IDEA Plugin 外掛之前,我們需要先進行一個打包操作,這個打包就是把位元組碼增強的程式碼打包整一個 Jar 包。在 build.gradle -> shadowJar
- 打包編譯後,就可以在 build -> libs 下看到 Jar:
probe-agent-1.0-SNAPSHOT-all.jar
這個 Jar 就是用來做位元組碼增強處理的。
2.4 測試驗證
這裡在把寫好的位元組碼增強元件給外掛使用之前,可以做一個測試驗證,避免每次都需要啟動外掛才能做測試。
單元測試
public class ApiTest {
public static void main(String[] args) throws Exception {
String URL = "jdbc:mysql://127.0.0.1:3306/itstack?characterEncoding=utf-8";
String USER = "root";
String PASSWORD = "123456";
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
String sql="SELECT * FROM USER WHERE id = ? AND name = ?";
PreparedStatement statement = conn.prepareStatement(sql);
statement.setLong(1,1L);
statement.setString(2,"謝飛機");
ResultSet rs = statement.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("name") + " " + rs.getString("address"));
}
}
}
- VM options:
-javaagent:你的路徑\libs\probe-agent-1.0-SNAPSHOT-all.jar
- 注意在測試執行的時候,你要給 ApiTest 配置 VM options 才能列印攔截 SQL 資訊
測試結果
原始SQL:
SELECT * FROM USER WHERE id = ? AND name = ?
替換SQL:
SELECT * FROM USER WHERE id = 1 AND name = '謝飛機'
謝飛機 北京.大興區.通明湖公園
- 好啦,這樣我們就可以攔截可以複製執行的 SQL 語句了,接下來我們再做下 IDEA Plugin 的處理。
3. 通過外掛開發引入探針 Jar
接下來我們要把開發好的位元組碼增強 Jar 包,複製到 IDEA Plugin 外掛開發模組中的 libs(可自己建立) 下,之後在 plugin.xml 配置載入 implementation fileTree(dir: 'libs', includes: ['*jar'])
這樣就可以程式中,找到這個 jar 包並配置到程式中。
3.1 複製 jar 到 libs 下
3.2 build.gradle 配置載入
dependencies {
implementation fileTree(dir: 'libs', includes: ['*jar'])
}
- 通過
implementation fileTree
引入載入檔案樹的方式,把我們配置好的 Jar 載入到程式執行中。
3.3 程式中引入 javaagent
cn.bugstack.guide.idea.plugin.PerRun
public class PerRun extends JavaProgramPatcher {
@Override
public void patchJavaParameters(Executor executor, RunProfile configuration, JavaParameters javaParameters) {
RunConfiguration runConfiguration = (RunConfiguration) configuration;
ParametersList vmParametersList = javaParameters.getVMParametersList();
vmParametersList.addParametersString("-javaagent:" + agentCoreJarPath);
vmParametersList.addNotEmptyProperty("guide-idea-plugin-probe.projectId", runConfiguration.getProject().getLocationHash());
}
}
- 通過繼承
JavaProgramPatcher
類,實現patchJavaParameters
方法,通過 configuration 屬性來配置我們自己需要被載入的-javaagent
包。 - 這樣在通過 IDEA 已經安裝此外掛,執行程式碼的時候,就會執行到這個攔截和列印 SQL 的功能。
3.4 plugin.xml 新增配置
<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
<java.programPatcher implementation="cn.bugstack.guide.idea.plugin.PerRun"/>
</extensions>
- 之後你還需要把開發好的載入類,配置到
java.programPatcher
這樣就可以程式執行的時候,被載入到了。
四、測試驗證
- 準備好一個有資料庫操作的工程,需要的是 JDBC,如果是其他的,你需要自己擴充套件
- 啟動外掛後,開啟你的工程,執行單元測試,檢視列印區
啟動外掛
- 如果你是新下載程式碼,那麼可以在 probe-plugin -> Tasks -> intellij -> runIde 中進行執行啟動。
單元測試
@Test
public void test_update(){
User user = new User();
user.setId(1L);
user.setName("謝飛機");
user.setAge(18);
user.setAddress("北京.大興區.亦莊經濟開發區");
userDao.update(user);
}
測試結果
22:30:55.593 [main] DEBUG cn.bugstack.test.demo.infrastructure.dao.UserDao.update[143] - ==> Preparing: UPDATE user SET name=?,age=?,address=? WHERE id=?
22:30:55.625 [main] DEBUG cn.bugstack.test.demo.infrastructure.dao.UserDao.update[143] - ==> Parameters: 謝飛機(String), 18(Integer), 北京.大興區.亦莊經濟開發區(String), 1(Long)
資料庫名稱:Mysql
執行緒ID:1
原始SQL:
UPDATE user SET name=?,age=?,address=?
WHERE id=?
替換SQL:
UPDATE user SET name='謝飛機',age=18,address='北京.大興區.亦莊經濟開發區'
WHERE id=1
- 通過測試結果可以看到,我們可以獲取到直接拿去測試驗證的 SQL 語句了,就不用在複製帶問號的 SQL 還得修改後測試了。
五、總結
- 首先我們是在本章節初步嘗試使用多模組的方式來建立工程,這樣的方式可以更加好維護各類一個工程下所需要的程式碼模組。你也可以嘗試使用 gradle 建立多模組工程
- 對於位元組碼插樁增強的使用方式,本篇只是一個介紹,這項技術還可以運用到更多的場景,開發出各種提升研發效率的工具。
- 瞭解額外的 Jar 包是怎麼載入到工程的,以及如何通過配置的方式讓
javaagent
引入自己開發好的探針元件。