您现在的位置是:首页 >学无止境 >Spring:InvalidDefinitionException: Direct self-reference leading to cycle网站首页学无止境
Spring:InvalidDefinitionException: Direct self-reference leading to cycle
一、现象
一个列表接口报错,没有返回信息,异常堆栈如下:
11:52:05.096 [http-nio-8180-exec-36] ERROR c.u.s.f.w.e.GlobalExceptionHandler - [handleRuntimeException,65] - 请求地址'XXXXX',发生未知异常.
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class XXXX]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle (through reference chain: XXXX.page.TableDataInfo["rows"]->java.util.ArrayList[0]->XXXX["instance"])
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:462)
at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:104)
....
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle (through reference chain: XXXX.page.TableDataInfo["rows"]->java.util.ArrayList[0]->XXXX["instance"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1276)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter._handleSelfReference(BeanPropertyWriter.java:944)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:722)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1514)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:1006)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:456)
... 121 common frames omitted
二、解决办法
- 不要在可能序列化的Bean内写这种奇怪的方法(这是同事写的方法,至于他写这个方法的原因我下面讲)
public XXXX getInstance(){
return this;
}
或者
public XXXX getInstance(){
return new XXXX();
}
- 如果调用的地方不多可以修改方法名,不以get或is开头
- 在不需要(不能)序列化的getXXX方法上加@JsonIgnore注解
- 好好学习:-(
三、原因分析
spring在使用默认的Jackson对接口返回的数据序列化时出现了直接引用自循环的现象,遂抛出异常。
自循环出现的地方如下:
看了一下调用这个方法的地方只有一处:
那么写这个getInstance的原因也就清楚了,为了在toMap的时候将自身作为value。
至此我诚心得为您推荐:
Function.identity()
他的内容很简单:
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
扩展
我们看一下jackson是如何将 getInstance方法当做一个需要序列化的属性的。
1.启动时加载默认配置,认定’get’、‘set’、'is’开头的方法可能需要被序列化
在springboot启动时会使用
DefaultAccessorNamingStrategy$Provider
这个默认配置,设定以’get’、‘set’、'is’为开头的方法可能需要被序列化
启动进行默认配置时的堆栈:
<init>:286, DefaultAccessorNamingStrategy$Provider (com.fasterxml.jackson.databind.introspect)
<clinit>:370, ObjectMapper (com.fasterxml.jackson.databind)
build:672, Jackson2ObjectMapperBuilder (org.springframework.http.converter.json)
<init>:59, MappingJackson2HttpMessageConverter (org.springframework.http.converter.json)
<init>:91, AllEncompassingFormHttpMessageConverter (org.springframework.http.converter.support)
<init>:215, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
instantiateClass:211, BeanUtils (org.springframework.beans)
instantiate:87, SimpleInstantiationStrategy (org.springframework.beans.factory.support)
instantiateBean:1326, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1232, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:582, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:542, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:335, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 60187547 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$49)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:333, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:208, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:953, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:918, AbstractApplicationContext (org.springframework.context.support)
refresh:583, AbstractApplicationContext (org.springframework.context.support)
<init>:144, ClassPathXmlApplicationContext (org.springframework.context.support)
<init>:85, ClassPathXmlApplicationContext (org.springframework.context.support)
main:32, XXXApplication (com.XXX)
2.controller处理完请求后为返回的POJO查找需要序列化的属性
请求在DispatcherServlet doDispatch中交给controller进行处理,controller处理完后的返回结果经过RequestResponseBodyMethodProcessor的handleReturnValue方法:
后在AbstractJackson2HttpMessageConverter的writeInternal方法中交给默认的Jackson:ObjectWriter完成POJO的序列化
继续跟踪,能看到Jackson在实例化前的准备工作,看一下这个方法:POJOPropertiesCollector.getJsonValueAccessor()
_collected属性被用来控制对一个POJO的解析只进行一次。(这种工作当然只需要做一次就够了)
而在collectAll方法的最后则会缓存解析的结果,并关闭收集开关。
到这里我们已经离这一小节的目标很近了。看一下collectAll方法里的细节:
_addFields(props)和_addMethods(props)方法将类中符合序列化条件的属性和方法加入到结果中,随后再进行筛选转换。
在这两个方法中能看到经常用来忽略序列化的实现逻辑(transient关键字和@JsonIgnore注解)
_addMethods里通过方法参数个数大致将方法分为getter方法(0参数),setter方法(1参数),anySetter(2参数)
这里我们主要关注getter逻辑中调用的_addGetterMethod,它的前半部分主要是对方法进行初步筛选和注解处理
后半部分则是试图从方法中获取隐藏的属性名,也就是序列化后的key
其中,_accessorNaming.findNameForRegularGetter(m, m.getName())方法会将我们定义的getInstance方法作为属性进行切割,将instance作为其属性名加入到prop中
其中_getterPrefix就是第一小节中初始化的默认配置:“get”
解析属性的堆栈:
_addGetterMethod:782, POJOPropertiesCollector (com.fasterxml.jackson.databind.introspect)
_addMethods:687, POJOPropertiesCollector (com.fasterxml.jackson.databind.introspect)
collectAll:422, POJOPropertiesCollector (com.fasterxml.jackson.databind.introspect)
getJsonValueAccessor:270, POJOPropertiesCollector (com.fasterxml.jackson.databind.introspect)
findJsonValueAccessor:258, BasicBeanDescription (com.fasterxml.jackson.databind.introspect)
findSerializerByAnnotations:391, BasicSerializerFactory (com.fasterxml.jackson.databind.ser)
_createSerializer2:220, BeanSerializerFactory (com.fasterxml.jackson.databind.ser)
createSerializer:169, BeanSerializerFactory (com.fasterxml.jackson.databind.ser)
_createUntypedSerializer:1473, SerializerProvider (com.fasterxml.jackson.databind)
_createAndCacheUntypedSerializer:1421, SerializerProvider (com.fasterxml.jackson.databind)
findContentValueSerializer:753, SerializerProvider (com.fasterxml.jackson.databind)
findAndAddSecondarySerializer:90, PropertySerializerMap (com.fasterxml.jackson.databind.ser.impl)
_findAndAddDynamic:311, AsArraySerializerBase (com.fasterxml.jackson.databind.ser.std)
serializeContents:115, IndexedListSerializer (com.fasterxml.jackson.databind.ser.impl)
serialize:79, IndexedListSerializer (com.fasterxml.jackson.databind.ser.impl)
serialize:18, IndexedListSerializer (com.fasterxml.jackson.databind.ser.impl)
serializeFields:808, MapSerializer (com.fasterxml.jackson.databind.ser.std)
serializeWithoutTypeInfo:764, MapSerializer (com.fasterxml.jackson.databind.ser.std)
serialize:720, MapSerializer (com.fasterxml.jackson.databind.ser.std)
serialize:35, MapSerializer (com.fasterxml.jackson.databind.ser.std)
_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serializeValue:400, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serialize:1510, ObjectWriter$Prefetch (com.fasterxml.jackson.databind)
writeValue:1006, ObjectWriter (com.fasterxml.jackson.databind)
writeInternal:456, AbstractJackson2HttpMessageConverter (org.springframework.http.converter.json)
write:104, AbstractGenericHttpMessageConverter (org.springframework.http.converter)
writeWithMessageConverters:290, AbstractMessageConverterMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
handleReturnValue:183, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
handleReturnValue:78, HandlerMethodReturnValueHandlerComposite (org.springframework.web.method.support)
invokeAndHandle:135, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:895, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:808, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1067, DispatcherServlet (org.springframework.web.servlet)
doService:963, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:645, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:750, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
。。。。。。。。。
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1743, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
ps:偶然发现swagger在项目启动的时候也会使用POJOPropertiesCollector.collectAll方法获取类属性