spring - Spring HttpHeaders 中可能存在的错误
问题描述
我发现我认为可能是 Spring 类HttpHeaders
和ReadOnlyHttpHeaders
. 在使用 Spring 提出 Jira 缺陷之前,我想确认这一点。这是我用来创建空HttpHeaders
对象的代码片段:
HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(HttpHeaders.EMPTY);
然后我使用以下方法将标题添加到我的新对象:
myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip")
在这HttpHeaders.EMPTY
不再是空的之后
HttpHeaders.EMPTY.size() == 1
HttpHeaders.EMPTY 的 javadoc 指出:
/**
* 空的 {@code HttpHeaders} 实例(不可变)。
*/
public static final HttpHeaders EMPTY
这里的问题是,当 'HttpHeaders.EMPTY' 在其他地方使用时,它会引入意外的标头。
考虑以下单元测试:
@Test
public void testUpdateEmptyHeaders() {
assertEquals(0, HttpHeaders.EMPTY.size()); // **Success**
HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(HttpHeaders.EMPTY);
myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip");
assertEquals(0, HttpHeaders.EMPTY.size()); // **Assert Fails**
}
@Test
// This test will fail if run after the test above, but will be successful if run by itself
public void testEmptyHeaders() {
assertEquals(0, HttpHeaders.EMPTY.size());
}
以下是单元测试的结果:
// testUpdateEmptyHeaders
08:39:28.450 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext@2e222612, testMethod = testUpdateEmptyHeaders@AuditContextTest, testException = java.lang.AssertionError: expected:<0> but was:<1>
java.lang.AssertionError:
Expected :0
Actual :1
// testEmptyHeaders
08:39:28.482 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext@2e222612, testMethod = testEmptyHeaders@AuditContextTest, testException = java.lang.AssertionError: expected:<0> but was:<1>
java.lang.AssertionError:
Expected :0
Actual :1
我觉得这是一个错误,因为HttpHeaders.EMPTY
应该是不可变的。我还可以通过在 SpringHttpHeaders.java
和ReadOnlyHttpHeaders.java
解决方案
Yup you are right this might be bug for spring framework HttpHeaders
public static final HttpHeaders EMPTY
HttpHeaders.EMPTY This will return empty HttpHeaders instance (immutable). (and it is singleton)
Case :1
Let's take a look at HttpHeaders.Empty
, which returns immutable object
HttpHeaders head = HttpHeaders.EMPTY;
System.out.println(System.identityHashCode(head)); //1338668845
System.out.println(head.size()); //0
HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(HttpHeaders.EMPTY);
System.out.println(System.identityHashCode(myHeaders)); //159413332
myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip");
head = HttpHeaders.EMPTY;
System.out.println(System.identityHashCode(head)); //1338668845
System.out.println(head.size()); //1
System.out.println(head); //{Accept-Encoding=[gzip]}
HttpHeaders head1 = HttpHeaders.EMPTY;
System.out.println(head1); //{Accept-Encoding=[gzip]}
System.out.println(System.identityHashCode(head1)); //1338668845
Conclusions :
1 :
HttpHeaders.EMPTY
is always returning the singleton object2: The problem is when
HttpHeaders.EMPTY
is passed towritableHttpHeaders
method internally the returned object is having relation withHttpHeaders.EMPTY
singleton object, look at case 2
Case :2 return object fromwritableHttpHeaders
reflects to HttpHeaders.EMPTY
singleton object (internally and indirectly)
HttpHeaders head = HttpHeaders.EMPTY;
System.out.println(System.identityHashCode(head)); //1338668845
System.out.println(head.size()); //0
HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(HttpHeaders.EMPTY);
System.out.println(System.identityHashCode(myHeaders)); //159413332
myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip");
myHeaders.add("hello", "value");
head = HttpHeaders.EMPTY;
System.out.println(System.identityHashCode(head)); //1338668845
System.out.println(head.size()); //2
System.out.println(head); //{Accept-Encoding=[gzip], hello=[value]}
HttpHeaders head1 = HttpHeaders.EMPTY;
System.out.println(head1); //{Accept-Encoding=[gzip], hello=[value]}
System.out.println(System.identityHashCode(head1)); //1338668845
myHeaders.remove("hello");
System.out.println(System.identityHashCode(head)); //1338668845
System.out.println(head.size()); //1
System.out.println(head); //{Accept-Encoding=[gzip]}
System.out.println(head1); //{Accept-Encoding=[gzip]}
System.out.println(System.identityHashCode(head1)); //1338668845
Conclusion :
1 :
add
andremove
operations performed onmyHeaders
object are reflecting toHttpHeaders.EMPTY
object
Case : 3 Suppose if we pass empty instance of the HttpHeaders object to writableHttpHeaders
using constructor then there is no issue everything works pretty clear
HttpHeaders head = HttpHeaders.EMPTY;
System.out.println(System.identityHashCode(head)); //1338668845
System.out.println(head.size()); //0
HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(new HttpHeaders());
System.out.println(System.identityHashCode(myHeaders)); //1323165413
myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip");
myHeaders.add("hello", "value");
head = HttpHeaders.EMPTY;
System.out.println(System.identityHashCode(head)); //1338668845
System.out.println(head.size()); //0
System.out.println(head); //{}
HttpHeaders head1 = HttpHeaders.EMPTY;
System.out.println(head1); //{}
System.out.println(System.identityHashCode(head1)); //1338668845
Case : 4 Even though indirectly immutable HttPHeaders.EMPTY
can be modified, but still it throws an error if you try modifying directly
HttpHeaders head = HttpHeaders.EMPTY;
System.out.println(System.identityHashCode(head));
System.out.println(head.size());
HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(HttpHeaders.EMPTY);
System.out.println(System.identityHashCode(myHeaders));
myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip");
head = HttpHeaders.EMPTY;
System.out.println(System.identityHashCode(head));
System.out.println(head.size());
System.out.println(head);
head.add("hello", "value");
Output :
1338668845
0
159413332
1338668845
1
{Accept-Encoding=[gzip]}
Exception in thread "main" java.lang.UnsupportedOperationException
at org.springframework.http.ReadOnlyHttpHeaders.add(ReadOnlyHttpHeaders.java:67)
at com.demo.NestedJsonParse.main(NestedJsonParse.java:40)
Final Conclusion : Yes it is bug you can raise a bug for spring projects spring-bug, immutable objects cannot change the state
推荐阅读
- python-3.x - 如何配置 Gunicorn - Heroku
- python - 无法弄清楚如何完成刽子手日期签名
- android - 如何在 android 上切片 AVC/H.264 帧?
- python-3.x - 如何从 lark-parser 树中获取变量名列表?
- java - 我可以使用 Java 捕获警告吗?
- swift - 如何在 AppDelegate OpenURLOptionsKey 中配置社交网络登录
- javascript - 将我的反应应用程序的布局存储在本地存储中不起作用
- jmeter - JMeter - Perfmon 用户
- node.js - 如何在 node.js(打字稿)中连接 socket.io?
- android - Room (266) 语句中止磁盘 I/O 错误