版本上线时发现
fastjson
的toString
方法的返回的字符串与与以前版本的toString
方法返回的字符串不相同,这致使依赖toString
进行md5
计算所获得的结果不相同,更进一步致使其余依赖该md5
值的插件发现和以前的md5
值不相等而重启,致使数据存在丢失状况。html
从项目中抽取出该模块代码,并进行了适当修改,但未改变整个处理逻辑,源码以下。java
package main; import com.alibaba.fastjson.JSONObject; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class Main { public static void main(String[] args) { JSONObject obj = new JSONObject(); obj.put("the_plugin_id", "the_plugin_id"); obj.put("the_plugin_name", "the_plugin_name"); obj.put("the_plugin_version", "the_plugin_version"); obj.put("the_plugin_md5", "the_plugin_md5"); obj.put("the_extend_info1", "the_extend_info1"); obj.put("the_extend_info2", "the_extend_info2"); obj.put("the_extend_info3", "the_extend_info3"); obj.put("the_extend_info4", "the_extend_info4"); System.out.println(obj.toString()); System.out.println("md5 ==> " + getMD5String(obj.toString())); } private static final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; static public String getMD5String(String source) { String retString = null; if (source == null) { return retString; } try { StringBuffer sb = new StringBuffer(); MessageDigest md = MessageDigest.getInstance("MD5"); md.update(source.getBytes(), 0, source.length()); byte[] retBytes = md.digest(); for (byte b : retBytes) { sb.append(hexDigits[(b >> 4) & 0x0f]); sb.append(hexDigits[b & 0x0f]); } retString = sb.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return retString; } }
fastjson
版本不一致的问题致使toString
方法返回的字符串不相同,待比对jar
后发现均依赖fastjson1.2.3
版本,排除因为fastjson
版本问题致使。JDK
从1.7
替换到1.8
致使,便是因为JDK
升级引发该问题,下面是验证过程。为验证是不是因为
JDK
升级致使该问题,分别使用不一样JDK
运行上述程序,获得结果以下。git
{"the_extend_info1":"the_extend_info1","the_plugin_version":"the_plugin_version","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4","the_plugin_name":"the_plugin_name","the_plugin_id":"the_plugin_id","the_plugin_md5":"the_plugin_md5"}
md5 ==> 87d74d87982fe1063a325c5aa97a9ef5json
格式化JSON
字符串以下app
{"the_extend_info1":"the_extend_info1","the_plugin_version":"the_plugin_version","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4","the_plugin_name":"the_plugin_name","the_plugin_id":"the_plugin_id","the_plugin_md5":"the_plugin_md5"}
{"the_plugin_md5":"the_plugin_md5","the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4","the_plugin_version":"the_plugin_version"}
md5 ==> fc8f7f526f5f37141f2fea3a03950f52函数
格式化JSON
字符串以下源码分析
{"the_plugin_md5":"the_plugin_md5","the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4","the_plugin_version":"the_plugin_version"}
对比
JDK1.7
和JDK1.8
下运行结果可知toString
方法返回的结果并不相同,这也就致使md5
计算的不相同,进一步致使其余依赖性的问题。优化
当使用
JSONObject obj = new JSONObject();
建立JSONObject
时,跟踪源码能够看到其会调用JSONObject(int, boolean)
型构造函数,而且会使用HashMap
维护插入的键值对,这是关键所在。插件
HashMap
在JDK1.7
和JDK1.8
中底层有不一样的逻辑,JDK1.8
的桶中会维护链表 + 红黑树
结构,该结果是对JDK1.7
的优化,JDK1.7
中维护链表
结构,在桶中元素较多而未达到再哈希的条件时查找效率会比较低下,而JDK1.8
当桶中元素个数达到必定数量时会将链表转化为红黑树,这样便能提升查询效率,有兴趣的读者可查阅JDK1.7
和JDK1.8
的源码,JDK1.8
源码分析传送门。code
由前面分析可知,直接使用
JSONObject obj = new JSONObject()
的方法生成JSONObject
对象时,其底层会使用HashMap
维护键值对,而HashMap
是和JDK
版本相关的,因此最好的解决方案应该是能和JDK
版本解耦的,而在JSONObject
的构造函数中,能够自定义传入Map
,这样就由指定Map
维护插入的键值对。可以使用LinkedHashMap
来维护插入键值对,而且还会维护插入的顺序。这样便能保证在不一样JDK
版本下使用toString
方法获得的字符串均相同。
使用
JSONObject obj = new JSONObject(new LinkedHashMap<String, Object>());
代替以前的JSONObject obj = new JSONObject();
便可。
{"the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_plugin_version":"the_plugin_version","the_plugin_md5":"the_plugin_md5","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4"}
md5 ==> 5c7725cd161d53f1e25a6a5c55b62c1f
格式化JSON
字符串以下
{"the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_plugin_version":"the_plugin_version","the_plugin_md5":"the_plugin_md5","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4"}
{"the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_plugin_version":"the_plugin_version","the_plugin_md5":"the_plugin_md5","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4"}
md5 ==> 5c7725cd161d53f1e25a6a5c55b62c1f
格式化JSON
字符串以下
{"the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_plugin_version":"the_plugin_version","the_plugin_md5":"the_plugin_md5","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4"}
对比在不一样
JDK
下运行的结果,能够发现toString
方法得到的字符串是彻底相同的,md5
值也是彻底相同的,即验证了方案的正确性。
在遇到问题时,特别是现网问题时,须要冷静分析,大胆猜测,当心求证,一点点找到突破口,此次的排坑过程大体如上所记录。