“This is my first day of the Novembermore Challenge.The final text challenge in 2021”
Hello everyone, I am Yihang!
Here’s the thing! One day after lunch at noon, was starting the afternoon brick moving task, suddenly the group inside lively, because of busy, also did not go to see, after a while, suddenly a partner in the group @ me, went to climb the building to look at everyone’s chat record, the result is found a very interesting Bug; This seems to be a very basic problem, but for those who don’t have a very solid foundation, if they really encounter it, they may be fooled. This guy in the group was fooled all afternoon, and I would like to share it with you here.
The discussion started when one of the boys asked this question:
The interface returns a Map. The key is of type Long. The Map contains data, but no value can be retrieved.
Because the Key of the underlying data type was converted to String when it was returned as Json, some partners quickly proposed to confirm whether the Key was converted to String, but the result was denied. But I’m skeptical of this denial, so I’ll have to check it for myself;
Problem of comb
In order to understand the situation, it is necessary to comb through briefly;
- The business scenario looks like this:
- The A service provides an interface that returns A Map
,> - Service B invokes the corresponding interface of service A through the RestTemplate, and the input parameter is A Long
- Service B obtains the Object by receiving the Map
,>
response and using the Long value as the Key
- The A service provides an interface that returns A Map
- Problem:Whether this interface design method is reasonable or not is discussed at the end of this articleService B can receive Map<Long, Object> objects normally
log.info("map:{}",map)
Can output the corresponding key and Object normally; But bymap.get(sourceId)
Take the Object,Sometimes normal, sometimes null is pulled out; This suddenly becomes interesting; Programmers encounter a Bug, as long as it is necessary or baidu to, it is not a Bug, easily won; Only the bug that sometimes appears sometimes is normal, is the most headache, may make you doubt life for a time;
Repetition Bug
In order to clarify this problem, I simulated his business logic as he wrote it, and wrote a simple code to reproduce normal and abnormal conditions:
-
The key can be Long l = 123456789000L. , the code is as follows:
@slf4j public class Main {public static void Main (String[] args) throws Exception {//A Service data Map<Long,String> mp = new HashMap<>(); Long l = 123456789000L; mp.put(l,"123"); The info (" key: {} ", l); String s1 = json.tojsonString (mp); String s1 = json.tojsonString (mp); Log.info (" JSON text: {}",s1); Map<Long,String> mp2 = json.parseObject (s1, map.class); Log.info (" JSON text converted Map object: {}",mp2); / / by the key values of the info (" by key: {} get value: {} ", l, mp2. Get (l)); }}Copy the code
The results
-
L = 123456789L; l = 123456789L;
Public class Main {public static void Main (String[] args) throws Exception {//A Service data Map<Long,String> mp = new HashMap<>(); Long l = 123456789L; mp.put(l,"123"); String s1 = json.tojsonString (mp); String s1 = json.tojsonString (mp); Log.info (" JSON text: {}",s1); Map<Long,String> mp2 = json.parseObject (s1, map.class); Log.info (" JSON text converted Map object: {}",mp2); / / by the key values of the info (" by key: {} get value: {} ", l, mp2. Get (l)); }}Copy the code
The results
Results analysis
Found no! Two pieces of code, except the key is different, there is no difference in the logic part, no error, and both work correctly, then why the normal part and the other part of the result is null?
Get (l) = mp2.get(l) = mp2.get(l) = mp2
Boy! Abnormal things will be demon;
If the key is long l = 123456789L, mp2 will not get the value. Because mp2 doesn’t have a Long key at all, it has an Integer key, right? When the Key is Long, the value is retrieved normally. When the Key is Integer, the value is retrieved null
Why is it Integer
I saved a Long as a key. When I transferred the Json text to MP2, I used Map<Long,String> to receive the text. It seems that everything is valid.
After all, the core code is only five simple lines, and a little analysis shows that the problem is in that line of code
Map<Long,String> mp2 = JSON.parseObject(s1,Map.class);
Copy the code
The object passed by the conversion is just a map.class; It does not specify what types of keys and values are in the Map; Because of generic erasure, fastJson cannot determine the specific type of the underlying numeric key. Instead, it uses the length to match the most appropriate data type. Since 123456789 can be received using Integer, it is converted to Integer; 123456789000 can only be received through Long, which is converted to Long;
Here is the fastJson source code for number type judgment; To determine what type the current number needs to be converted to:
if (negative) { if (i > this.np + 1) { if (result >= -2147483648L && type ! = 76) { if (type == 83) { return (short)((int)result); } else if (type == 66) { return (byte)((int)result); } else { return (int)result; } } else { return result; } } else { throw new NumberFormatException(this.numberString()); } } else { result = -result; if (result <= 2147483647L && type ! = 76) { if (type == 83) { return (short)((int)result); } else if (type == 66) { return (byte)((int)result); } else { return (int)result; } } else { return result; }}Copy the code
This explains exactly why the bug appears;
How to solve it?
fastJson
If you use fastJson to convert Json text into objects, you can easily specify the type of key and value in the Map
Map<Long,String> mp2 = JSON.parseObject(s1,new TypeReference<Map<Long,String>>(){});
Copy the code
The value can be retrieved even if the key is 123456789
RestTemplate
This article is caused by requesting another service from RestTemplate without specifying a generic object, so it needs to be specified as well.
-
The sample interface
@RestController @RequestMapping("/a") public class TestController { @GetMapping("/b") public Map<Long, String> b() { Map<Long, String> mp = new HashMap<>(); mp.put(1L,"123"); mp.put(123456789L,"456"); mp.put(123456789000L,"789"); return mp; }}Copy the code
-
RestTemplate request
@Autowired RestTemplate restTemplate; @Test public void restTemplate() throws Exception { ParameterizedTypeReference<Map<Long, String>> typeRef = new ParameterizedTypeReference<Map<Long, String>>() {}; Map < Long, String > mp = restTemplate. Exchange (" http://127.0.0.1:8080/a/b ", HttpMethod. GET new HttpEntity < > (null). typeRef).getBody(); log.info("mp:{}", mp); Log.info (" get key :{} ",1L,mp.get(1L)); Log.info (" get key :{} :{} ",123456789L,mp.get(123456789L)); Log.info (" get key :{} :{} ",123456789000L,mp.get(123456789000L)); }Copy the code
thinking
At this point, the whole problem is solved!
But there is another point that I have to mention; The kid’s Map as an object of message interaction is very not recommended for use, through the Map, seemingly improves the flexibility, after all, what objects can be thrown in, but it is a lot to the readability of the code, and maintenance of barriers, because I have no way to see what the Map into the data, also do not know when put the data into it; If I’m just a caller and I want to see what you’re returning, I can’t see it clearly just by looking at the interface definition, but I have to go through the detailed code and see what values you’re putting in the Map and what they mean.
These problems may eventually dig themselves into a deep hole
In order to improve the flexibility, readability and extensibility of the interface, abstract interface packet data based on generics is an important method. The Json format of the message is divided into the common part and the business data part, so that the whole data structure becomes more flexible, but without losing the overall standard. Through the response object, you can determine the data you want to return at a glance. Please refer to the following simple example:
{/ / public part "code" : 0, "MSG" : "success", "data" : {/ / business data}}Copy the code
Corresponding code:
@Data
public class BaseBean<T> {
private Integer code;
private String msg;
private T data;
}
Copy the code
With generics, you have the flexibility to express any response
-
The user
@getMapping ("/user") public BaseBean< user > user() {GetMapping("/user") public BaseBean< user > user = new BaseBean<>(); return user; }Copy the code
-
goods
@getMapping (" goods") public BaseBean< goods > goods() {GetMapping(" goods") public BaseBean< goods > goods = new BaseBean<> goods(); return user; }Copy the code
.
Well, today to share here, would like to see the friends of this article, in the future, no Bug!!