在java开发的过程中,我们经常遇到一些需要对某个方法做切面的需求,通常做法是使用spring提供的AOP功能,对方法做切面,例如方法出入参打印,接口mock等等。但很多时候,需要切面的方法是非spring容器管理的类,例如需要对okhttp、apacheHttpClient请求做mock,判断http请求的参数、url为某个值时,返回测试结果,例如请求参数中包含userName=tester,返回code=200。这种使用可以使用java Agent,通过java agent修改类的字节码,实现对非spring容器管理对象的aop处理。但是使用javaAgent后,启动时需要添加参数-javaagent:xxx.jar,使用方还需要下载agent 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.2</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~