如何使用java agent修改字节码并在springboot启动时自动生效
作者:gyx_ruarua
在java开发的过程中,我们经常遇到一些需要对某个方法做切面的需求,通常做法是使用spring提供的AOP功能,对方法做切面,例如方法出入参打印,接口mock等等。但很多时候,需要切面的方法是非spring容器管理的类,例如需要对okhttp、apacheHttpClient请求做mock,判断http请求的参数、url为某个值时,返回测试结果,例如请求参数中包含userName=tester,返回code=200。这种使用可以使用java Agent,通过java agent修改类的字节码,实现对非spring容器管理对象的aop处理。但是使用javaAgent后,启动时需要添加参数-javaagent:xxx.jar,使用方还需要下载对象的jar到本地,使用起来略显麻烦。proxy-sdk就是为了解决这个问题,通过maven、gradle依赖工具直接引入jar依赖即可,然后添加你的切面逻辑,springboot服务启动即可生效。
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的请求方法,实现接口mock,判断当请求某个地址时返回测试数据,而不是真的请求对方。
例如以下代码,前置重写httpClient的execute方法,当请求baidu.com时就返回测试的json数据{\"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请求处理逻辑,实现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); //创建HttpClient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); //创建请求对象 HttpGet httpGet = new HttpGet("http://baidu.com"); //发送请求,请求响应结果 CloseableHttpResponse response = httpClient.execute(httpGet); //获取服务器返回的状态码 int statusCode = response.getStatusLine().getStatusCode(); System.out.println(">>>>>>>>>服务端返回成功的状态码为:"+statusCode); HttpEntity entity = response.getEntity(); String body = EntityUtils.toString(entity); System.out.println(">>>>>>>>>服务端返回的数据为:"+body); //关闭资源 response.close(); httpClient.close(); } }
启动服务,在控制台可以看到打印了对org.apache.http.impl.client.CloseableHttpClient类做字节码重写
[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,得到结果,顺利得到了我们需要的测试数据
我们调整下代码,new HttpGet("http://zhihu.com"),换一个请求地址,然后发起请求
执行了httpClient的原始逻辑,请求zhihu.com拿到了响应。
简单试验就到这里了,其他用法可以按照github上readme指引试验一下。觉的这个小工具不错小伙伴可以点个star~
到此这篇关于使用java agent修改字节码,并在springboot启动时自动生效的文章就介绍到这了,更多相关java agent修改字节码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!