Problem Discovery
Today this topic is still relatively easy, and many of you may have encountered this problem.
The @RestController
, @ResponseBody
and other annotations are the ones we deal with most when writing web applications, and we often have the need to return an object to the client, which SpringMVC helps us serialize into JSON objects. And today I want to share the topic is not something profound, it is the return of an object when there is a circular reference to explore the problem.
The problem is very simple and easy to reproduce, so let’s go straight to the code.
Prepare two objects with circular references.
Return objects with circular references directly in the SpringMVC controller.
|
|
Requesting curl localhost:8080/hello
was found to throw a StackOverFlowError
directly.
Problem Analysis
It is not difficult to understand what happened in between, from the stack and common sense should understand the fact that SpringMVC uses jackson as HttpMessageConverter by default, so that when we return the object, it will be serialized into json string by jackson’s serializer, and another fact is that jackson is unable to resolve circular references in java, nesting type of parsing, which eventually led to StackOverFlowError.
Some people may say, “Why do you have circular references? God knows how odd the business scenario, since Java does not limit the existence of circular references, there must be a reasonable scenario for the existence of the possibility, if you have an interface on the line has been running smoothly until one day, encountered an object containing circular references, you look at the StackOverFlowError printed out of the stack, and begin to doubt life, is which What kind of idiot did this!
We first assume the existence of circular references to the reasonableness of how to solve the problem? The simplest solution: maintain the association in one direction, referring to the idea of one-way mapping in the OneToMany association in Hibernate, which requires killing the Person member variable in IdCard. Or, with the help of the annotations provided by jackson, specify the fields that ignore circular references, e.g., like this
Of course, I also looked through some sources to try to find a more elegant solution to jackson, such as these two annotations.
But in my opinion, they don’t seem to be of much use.
Of course, you can also choose to use FastJsonHttpMessageConverter
to replace the default implementation of jackson if you don’t mind the frequent security vulnerabilities of fastjson, like the following.
|
|
You can customize some features for json conversion, but today I’m mainly concerned with the SerializerFeature.DisableCircularReferenceDetect
property, which allows fastjson to handle circular references by default, as long as the feature is not shown to be turned on.
After configuring as above, let’s see the result.
|
|
has been returned normally, fastjson uses "$ref":"..."
This identifier solves the circular reference problem, and if you continue to use fastjson deserialization, you can still resolve to the same object.
Using FastJsonHttpMessageConverter completely circumvents the circular reference problem, which is very helpful in scenarios where the return type is not fixed, whereas @JsonIgnore
only works on objects with fixed structure of circular references.
Questions to ponder
It’s worth mentioning why the standard JSON library doesn’t focus so much on circular references. fastjson seems to be a special case, but I think the main reason is that the JSON serialization format is meant to be universal, and contract information like $ref
is not defined by the JSON specification. fastjson can ensure that $ref
can be parsed properly when serializing and deserializing. ref` can be resolved properly when serializing and deserializing, but if it is a cross-framework, cross-system, cross-language scenario, this is all an unknown. In the end, it’s a gap between the Java language’s circular references and the JSON general specification’s lack of this concept (maybe the JSON specification describes this feature, but I haven’t found it, so please correct me if there’s a problem).
Should I go with @JsonIgnore
or use FastJsonHttpMessageConverter
? After thinking about the above, I think you should be able to choose the right solution according to your own scenario.
To summarize, if you choose FastJsonHttpMessageConverter
, the changes are larger, and if there are more stock interfaces, it is recommended to do a good regression to make sure that the circular reference problem is solved while not introducing other incompatible changes. And, you need to evaluate the solution based on your usage scenario, if there is a circular reference, fastjson will use $ref
to record the reference information, please make sure your front-end or interface side can recognize the information, because this may not be the standard JSON specification. You can also choose @JsonIgnore
for minimal changes, but be aware that if you deserialize again based on the serialization result, the reference information will not be automatically restored.
Reference
https://mp.weixin.qq.com/s/CnyGY4PSkJKZ0hRTAH_saQ