如何使用java agent修改字節(jié)碼并在springboot啟動時自動生效
在java開發(fā)的過程中,我們經(jīng)常遇到一些需要對某個方法做切面的需求,通常做法是使用spring提供的AOP功能,對方法做切面,例如方法出入?yún)⒋蛴。涌趍ock等等。但很多時候,需要切面的方法是非spring容器管理的類,例如需要對okhttp、apacheHttpClient請求做mock,判斷http請求的參數(shù)、url為某個值時,返回測試結(jié)果,例如請求參數(shù)中包含userName=tester,返回code=200。這種使用可以使用java Agent,通過java agent修改類的字節(jié)碼,實現(xiàn)對非spring容器管理對象的aop處理。但是使用javaAgent后,啟動時需要添加參數(shù)-javaagent:xxx.jar,使用方還需要下載對象的jar到本地,使用起來略顯麻煩。proxy-sdk就是為了解決這個問題,通過maven、gradle依賴工具直接引入jar依賴即可,然后添加你的切面邏輯,springboot服務(wù)啟動即可生效。
GitHub - YingXinGuo95/proxy-agent: java agent with springboot
按照github上的README的指引我們引入jar包,從maven的中央倉庫下載依賴。
<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的請求方法,實現(xiàn)接口mock,判斷當請求某個地址時返回測試數(shù)據(jù),而不是真的請求對方。
例如以下代碼,前置重寫httpClient的execute方法,當請求baidu.com時就返回測試的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請求處理邏輯,實現(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請求讀取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請求讀取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 {
}
}
}我們在springBoot啟動后請求試試
@SpringBootApplication
public class AppMain {
@SneakyThrows
public static void main(String[] args) {
SpringApplication.run(AppMain.class, args);
//創(chuàng)建HttpClient對象
CloseableHttpClient httpClient = HttpClients.createDefault();
//創(chuàng)建請求對象
HttpGet httpGet = new HttpGet("http://baidu.com");
//發(fā)送請求,請求響應(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();
}
}啟動服務(wù),在控制臺可以看到打印了對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啟動后請求baidu.com,得到結(jié)果,順利得到了我們需要的測試數(shù)據(jù)

我們調(diào)整下代碼,new HttpGet("http://zhihu.com"),換一個請求地址,然后發(fā)起請求

執(zhí)行了httpClient的原始邏輯,請求zhihu.com拿到了響應(yīng)。
簡單試驗就到這里了,其他用法可以按照github上readme指引試驗一下。覺的這個小工具不錯小伙伴可以點個star~
到此這篇關(guān)于使用java agent修改字節(jié)碼,并在springboot啟動時自動生效的文章就介紹到這了,更多相關(guān)java agent修改字節(jié)碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot項目監(jiān)控開發(fā)小用例(實例分析)
這篇文章主要介紹了springboot項目監(jiān)控開發(fā)小用例,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09
關(guān)于QueryWrapper,實現(xiàn)MybatisPlus多表關(guān)聯(lián)查詢方式
這篇文章主要介紹了關(guān)于QueryWrapper,實現(xiàn)MybatisPlus多表關(guān)聯(lián)查詢方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教。2022-01-01

