如何使用java agent修改字節(jié)碼并在springboot啟動(dòng)時(shí)自動(dòng)生效
在java開發(fā)的過(guò)程中,我們經(jīng)常遇到一些需要對(duì)某個(gè)方法做切面的需求,通常做法是使用spring提供的AOP功能,對(duì)方法做切面,例如方法出入?yún)⒋蛴?,接口mock等等。但很多時(shí)候,需要切面的方法是非spring容器管理的類,例如需要對(duì)okhttp、apacheHttpClient請(qǐng)求做mock,判斷http請(qǐng)求的參數(shù)、url為某個(gè)值時(shí),返回測(cè)試結(jié)果,例如請(qǐng)求參數(shù)中包含userName=tester,返回code=200。這種使用可以使用java Agent,通過(guò)java agent修改類的字節(jié)碼,實(shí)現(xiàn)對(duì)非spring容器管理對(duì)象的aop處理。但是使用javaAgent后,啟動(dòng)時(shí)需要添加參數(shù)-javaagent:xxx.jar,使用方還需要下載對(duì)象的jar到本地,使用起來(lái)略顯麻煩。proxy-sdk就是為了解決這個(gè)問(wèn)題,通過(guò)maven、gradle依賴工具直接引入jar依賴即可,然后添加你的切面邏輯,springboot服務(wù)啟動(dòng)即可生效。
GitHub - YingXinGuo95/proxy-agent: java agent with springboot
按照github上的README的指引我們引入jar包,從maven的中央倉(cāng)庫(kù)下載依賴。
<dependency> <groupId>io.github.yingxinguo95</groupId> <artifactId>proxy-sdk</artifactId> <version>0.0.1</version> <!-- 0.0.1拉取不到可以試試0.0.1-RELEASE --> <!--<version>0.0.1-RELEASE</version>--> </dependency>
配置需要代理方法配置和定義我們自己的需要重寫邏輯,例如我們需要apache httpclient的請(qǐng)求方法,實(shí)現(xiàn)接口mock,判斷當(dāng)請(qǐng)求某個(gè)地址時(shí)返回測(cè)試數(shù)據(jù),而不是真的請(qǐng)求對(duì)方。
例如以下代碼,前置重寫httpClient的execute方法,當(dāng)請(qǐng)求baidu.com時(shí)就返回測(cè)試的json數(shù)據(jù){\"code\":\"200\", \"msg\":\"mock data\"}
import io.github.proxy.annotation.ProxyRecodeCfg; import io.github.proxy.annotation.ReCodeType; import io.github.proxy.service.ProxyReCode; import lombok.SneakyThrows; import org.apache.http.*; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicStatusLine; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Locale; /** * 重寫apacheHttp請(qǐng)求處理邏輯,實(shí)現(xiàn)mock功能 */ @Component public class MockApacheHttpReCoder implements ProxyReCode { @SneakyThrows @ProxyRecodeCfg(proxyClassName="org.apache.http.impl.client.CloseableHttpClient", method="execute", type = ReCodeType.BEFORE) public static CloseableHttpResponse executeProxy1(HttpUriRequest request, HttpContext context) { String path = request.getURI().toString(); if (request instanceof HttpEntityEnclosingRequestBase) { //post請(qǐng)求讀取body HttpEntity entity = ((HttpEntityEnclosingRequestBase)request).getEntity(); if (entity == null) { return null; } String reqBody = EntityUtils.toString(entity, StandardCharsets.UTF_8); } if (path.startsWith("http://baidu.com")) { return buildMockResponse("{\"code\":\"200\", \"msg\":\"mock data\"}", null); } return null; } @SneakyThrows @ProxyRecodeCfg(proxyClassName="org.apache.http.impl.client.CloseableHttpClient", method="execute", type = ReCodeType.BEFORE) public static CloseableHttpResponse executeProxy2(HttpHost target, HttpRequest request, HttpContext context) { String path = request.getRequestLine().getUri(); if (request instanceof HttpEntityEnclosingRequestBase) { //post請(qǐng)求讀取body HttpEntity entity = ((HttpEntityEnclosingRequestBase)request).getEntity(); if (entity == null) { return null; } String reqBody = EntityUtils.toString(entity, StandardCharsets.UTF_8); } if (path.startsWith("http://baidu.com")) { return buildMockResponse("{\"code\":\"200\", \"msg\":\"mock返回\"}", null); } return null; } public static CloseableHttpResponse buildMockResponse(String mockValue, Header[] headers) { ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); String reasonPhrase = "OK"; StatusLine statusline = new BasicStatusLine(protocolVersion, HttpStatus.SC_OK, reasonPhrase); MockCloseableHttpResponse mockResponse = new MockCloseableHttpResponse(statusline); BasicHttpEntity entity = new BasicHttpEntity(); InputStream inputStream = new ByteArrayInputStream(mockValue.getBytes()); entity.setContent(inputStream); entity.setContentLength(mockValue.length()); entity.setChunked(false); mockResponse.setEntity(entity); if (headers != null) { mockResponse.setHeaders(headers); } return mockResponse; } public static class MockCloseableHttpResponse extends BasicHttpResponse implements CloseableHttpResponse { public MockCloseableHttpResponse(StatusLine statusline, ReasonPhraseCatalog catalog, Locale locale) { super(statusline, catalog, locale); } public MockCloseableHttpResponse(StatusLine statusline) { super(statusline); } public MockCloseableHttpResponse(ProtocolVersion ver, int code, String reason) { super(ver, code, reason); } @Override public void close() throws IOException { } } }
我們?cè)趕pringBoot啟動(dòng)后請(qǐng)求試試
@SpringBootApplication public class AppMain { @SneakyThrows public static void main(String[] args) { SpringApplication.run(AppMain.class, args); //創(chuàng)建HttpClient對(duì)象 CloseableHttpClient httpClient = HttpClients.createDefault(); //創(chuàng)建請(qǐng)求對(duì)象 HttpGet httpGet = new HttpGet("http://baidu.com"); //發(fā)送請(qǐng)求,請(qǐng)求響應(yīng)結(jié)果 CloseableHttpResponse response = httpClient.execute(httpGet); //獲取服務(wù)器返回的狀態(tài)碼 int statusCode = response.getStatusLine().getStatusCode(); System.out.println(">>>>>>>>>服務(wù)端返回成功的狀態(tài)碼為:"+statusCode); HttpEntity entity = response.getEntity(); String body = EntityUtils.toString(entity); System.out.println(">>>>>>>>>服務(wù)端返回的數(shù)據(jù)為:"+body); //關(guān)閉資源 response.close(); httpClient.close(); } }
啟動(dòng)服務(wù),在控制臺(tái)可以看到打印了對(duì)org.apache.http.impl.client.CloseableHttpClient類做字節(jié)碼重寫
[Attach Listener] INFO i.g.p.transformer.ReCoderTransformer -[proxy-agent] rewrite class:[org.apache.http.impl.client.CloseableHttpClient]
[Attach Listener] INFO io.github.proxy.AgentMain -[proxy-agent] redefine loaded class complete, cost:171ms
springboot啟動(dòng)后請(qǐng)求baidu.com,得到結(jié)果,順利得到了我們需要的測(cè)試數(shù)據(jù)
我們調(diào)整下代碼,new HttpGet("http://zhihu.com"),換一個(gè)請(qǐng)求地址,然后發(fā)起請(qǐng)求
執(zhí)行了httpClient的原始邏輯,請(qǐng)求zhihu.com拿到了響應(yīng)。
簡(jiǎn)單試驗(yàn)就到這里了,其他用法可以按照github上readme指引試驗(yàn)一下。覺(jué)的這個(gè)小工具不錯(cuò)小伙伴可以點(diǎn)個(gè)star~
到此這篇關(guān)于使用java agent修改字節(jié)碼,并在springboot啟動(dòng)時(shí)自動(dòng)生效的文章就介紹到這了,更多相關(guān)java agent修改字節(jié)碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何在Mac上安裝并配置JDK環(huán)境變量詳細(xì)步驟
這篇文章主要介紹了如何在Mac上安裝并配置JDK環(huán)境變量詳細(xì)步驟,包括下載JDK、安裝JDK、配置環(huán)境變量、驗(yàn)證JDK配置以及可選地設(shè)置PowerShell為默認(rèn)終端,需要的朋友可以參考下2025-04-04Java中的延遲隊(duì)列DelayQueue詳細(xì)解析
這篇文章主要介紹了Java中的延遲隊(duì)列DelayQueue詳細(xì)解析,JDK自身支持延遲隊(duì)列的數(shù)據(jù)結(jié)構(gòu),其實(shí)類:java.util.concurrent.DelayQueue,<BR>我們通過(guò)閱讀源碼的方式理解該延遲隊(duì)列類的實(shí)現(xiàn)過(guò)程,需要的朋友可以參考下2023-12-12springboot項(xiàng)目監(jiān)控開發(fā)小用例(實(shí)例分析)
這篇文章主要介紹了springboot項(xiàng)目監(jiān)控開發(fā)小用例,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09關(guān)于QueryWrapper,實(shí)現(xiàn)MybatisPlus多表關(guān)聯(lián)查詢方式
這篇文章主要介紹了關(guān)于QueryWrapper,實(shí)現(xiàn)MybatisPlus多表關(guān)聯(lián)查詢方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。2022-01-01