1. 问题描述
项目中遇到的一个问题,简要描述下过程:有两个系统A和B,用户请求A系统的接口a,a接口获取到用户请求参数对象后,再结合A系统中缓存的用户信息构建新的对象参数obj,然后a接口传入参数obj请求B系统的下载接口B。就是这样一个A系统请求B系统接口的事,问题是:开发环境操作时,一切正常,但是测试人员进行测试的时候,显示的却是空白页面,无法下载。通过查阅发现,是测试人员在启动项目的时候,使用jacocoagent.jar进行代码覆盖率的统计。下面使用一个简单的来说明一下吧。
2.Demo演示
2.1 首先创建一个实体对象User
public class User {
private String username;
private String sex;
private int age;
public User() {
}
public User(String username, String sex, int age) {
this.username = username;
this.sex = sex;
this.age = age;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
2.2 创建一个用户请求接口/test/call
@PostMapping(value = "/call")
public String call(HttpServletResponse response) {
User user = new User();
user.setUsername("duan");
user.setAge(18);
user.setSex("lady");
String url = getGetUrl(URL, user, User.class);
httpUtil.doGetForDownload(response, url);
return url;
}
因为get请求是将参数通过&符号拼接在url后面的,所以这里专门写了一个方法getGetUrl用来将一个对象拼接到给定的URL后面,这里URL定义如下:
private static final String URL = "http://localhost:8080/user/createUser";
getGetUrl方法如下:
/**
* 拼接Get请求的URL
* @param url
* @param obj
* @return
*/
private static String getGetUrl(String url, Object obj, Class classType) {
StringBuilder sb = new StringBuilder().append(url).append("?1=1");
try {
Field[] fields = classType.getDeclaredFields();
for(Field field : fields) {
field.setAccessible(true);
String name = field.getName();
Object value = field.get(obj);
if (value == null) {
sb.append("&").append(name).append("=");
} else if (value instanceof List){
for (Object var : ((List) value).toArray()) {
sb.append("&").append(name).append("=").append(var);
}
} else {
sb.append("&").append(name).append("=").append(value);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}
在httpUtil.doGetForDownload方法中打印出请求的URL。
现在,我们来启动项目,第一次启动项目时先不添加启动参数,请求call接口没有问题。第二次启动时,添加启动参数:
-javaagent:E:/temp/jacoco-0.8.6/lib/jacocoagent.jar=destfile=E:/temp/jacoco.txt
在进行这一步操作时,需要下载jacocoagent.jar,将其放在指定的目录下,我这里临时放在了E:/temp/jacoco-0.8.6/lib下面,jacocoagent.jar的下载地址https://www.jacoco.org/jacoco/index.html。
再次启动项目,请求call接口,日志打印情况如下:
2020-09-17 16:08:24.778 INFO 42460 --- [nio-8080-exec-1] duan.example.jacoco.util.HttpUtil : 请求的URL:http://localhost:8080/user/createUser?1=1&username=duan&sex=lady&age=18&$jacocoData=[Z@646d0c35
2020-09-17 16:08:25.791 INFO 42460 --- [nio-8080-exec-2] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header
Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level.
java.lang.IllegalArgumentException: Invalid character found in the request target [/user/createUser?1=1&username=duan&sex=lady&age=18&$jacocoData=[Z@646d0c35]. The valid characters are defined in RFC 7230 and RFC 3986
at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:491) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:260) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589) [tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.37.jar:9.0.37]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_221]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_221]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.37.jar:9.0.37]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_221]
可以看到,在上图第一行输出了请求的URL,在URL的后面多了一部分&$jacocoData=[Z@646d0c35,也就是这个原因,导致的我在项目中使用时调用B系统的接口出错。
那既然知道出现这个问题的原因,又不能让测试取消这个启动参数,该怎么解决呢?有以下几种办法:
(1)不使用反射函数,手动拼接URL
(2)在反射方法中,使用了是否为复合字段的field方法来判断,代码如下:
private static String getGetUrl(String url, Object obj, Class classType) {
StringBuilder sb = new StringBuilder().append(url).append("?1=1");
try {
Field[] fields = classType.getDeclaredFields();
for(Field field : fields) {
field.setAccessible(true);
if(field.isSynthetic()) {
continue;
}
String name = field.getName();
Object value = field.get(obj);
if (value == null) {
sb.append("&").append(name).append("=");
} else if (value instanceof List){
for (Object var : ((List) value).toArray()) {
sb.append("&").append(name).append("=").append(var);
}
} else {
sb.append("&").append(name).append("=").append(value);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}