您现在的位置是:首页 >学无止境 >Spring:InvalidDefinitionException: Direct self-reference leading to cycle网站首页学无止境

Spring:InvalidDefinitionException: Direct self-reference leading to cycle

小林子你真猛! 2024-10-24 12:01:04
简介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

二、解决办法

  1. 不要在可能序列化的Bean内写这种奇怪的方法(这是同事写的方法,至于他写这个方法的原因我下面讲)
	public XXXX getInstance(){
        return this;
    }

或者

	public XXXX getInstance(){
        return new XXXX();
    }
  1. 如果调用的地方不多可以修改方法名,不以get或is开头
  2. 在不需要(不能)序列化的getXXX方法上加@JsonIgnore注解
  3. 好好学习:-(

三、原因分析

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方法获取类属性

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。