本文介绍了在 Spring Boot 2.3 中重现 Spring Data Rest 搜索控制器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 Spring Boot 2.0 之前,我可以重现为 mongodb 存储库公开的查询方法生成的控制器.这是一个代码示例:

域实体

@Document(collection = "foos")公共类 Foo {@Id私人字符串ID;私人字符串名称;//省略 getter/setter}

Mongo 存储库

public interface FooRepository extends MongoRepository{公共页面findByName(@Param("name") String name, Pageable pageable);}

Spring Boot 通过/foos/search/findByName?name=... 自动暴露搜索方法,结果类似于:

{_嵌入":{福": [ {姓名":qc",_links":{自我":{参考":http://localhost:8080/foos/56a8a8d5daffd28c9c907974"},富":{参考":http://localhost:8080/foos/56a8a8d5daffd28c9c907974"}}}]},_links":{自我":{参考":http://localhost:8080/foos/search/findByName?name=qc&page=0&size=20"}},页面":{尺寸": 20,总元素": 1,总页数": 1,数量": 0}}

我可以使用以下自定义控制器和配置重现它

@RestController@RequestMapping("foos")@RequiredArgsConstructor//龙目岛公共类 FooQueryController {私人最终 FooRepository 存储库;私有最终 PagedResourcesAssembler pagedResourcesAssembler;@GetMapping(value = "搜索/查询",产生 = MediaType.APPLICATION_JSON_UT8_VALUE)public ResponseEntity custom(@RequestParam("name") 字符串名称,可分页可分页,PersistentEntityResourceAssembler resourceAssembler) {var page = repository.findByName(name, pageable);var model = pagedResourcesAssembler.toResource(page, resourceAssembler);返回 ResponseEntity.ok(model);}}//启用在 RestController 中注入 PersistentEntityResourceAssembler//详见 https://jira.spring.io/browse/DATAREST-657@配置@Order(Ordered.HIGHEST_PRECEDENCE)@RequiredArgsConstructor公共类 MvcConfiguration 实现 WebMvcConfigurer {//警告:不要更改此成员的名称 - 它注入了//RequestMappingHandlerAdapter$repositoryExporterHandlerAdapter().私有最终 RequestMappingHandlerAdapter repositoryExporterHandlerAdapter;@覆盖public void addArgumentResolvers(List参数解析器) {ListcustomArgumentResolvers =repositoryExporterHandlerAdapter.getCustomArgumentResolvers();argumentResolvers.addAll(customArgumentResolvers);}}

有了这个,我可以向 /foos/search/query?name=... 发送请求并得到预期的响应:

{_嵌入":{福": [ {姓名":qc",_links":{自我":{参考":http://localhost:8080/foos/56a8a8d5daffd28c9c907974"},富":{参考":http://localhost:8080/foos/56a8a8d5daffd28c9c907974"}}}]},_links":{自我":{参考":http://localhost:8080/foos/search/query?name=qc&page=0&size=20"}},页面":{尺寸": 20,总元素": 1,总页数": 1,数量": 0}}

切换到 Spring Boot 2.3,在控制器中使用 Spring HATEOAS 1.0 API

@RestController@RequestMapping("foos")@RequiredArgsConstructor公共类 FooQueryController {私人最终 FooRepository 存储库;私有最终 PagedResourcesAssembler pagedResourcesAssembler;@GetMapping(value = "搜索/查询",产生 = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity custom(@RequestParam("name") 字符串名称,可分页可分页,PersistentEntityResourceAssembler resourceAssembler) {var page = repository.findByName(name, pageable);var model = pagedResourcesAssembler.toModel(page, resourceAssembler);返回 ResponseEntity.ok(model);}}

我现在得到以下结果:

{"_embedded":{"foos":[{"id":"56a8a8d5daffd28c9c907974","name":"qc","embeddeds":{}嵌套:false,"persistentEntity":{idProperty":{name":id",rawType":java.lang.String",association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty";:{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name";:"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id",";rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name";:"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String",关联":假,所有者":{idProperty":

Spring 的日志中有以下错误:

2020-08-06 18:11:20.968 WARN 9932 --- [nio-8080-exec-1] .wsmsDefaultHandlerExceptionResolver:尝试解决异常 [org.springframework.http.converter.HttpMessageNotWritableException 时失败]java.lang.IllegalStateException:提交响应后无法调用 sendError()在 org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:472) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.sendServerError(DefaultHandlerExceptionResolver.java:550) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.handleHttpMessageNotWritable(DefaultHandlerExceptionResolver.java:440) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.doResolveException(DefaultHandlerExceptionResolver.java:210) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:141) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.servlet.handler.HandlerExceptionResolverComposite.resolveException(HandlerExceptionResolverComposite.java:80) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1300) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1111) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1057) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 javax.servlet.http.HttpServlet.service(HttpServlet.java:626) ~[tomcat-embed-core-9.0.37.jar:4.0.FR]在 org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.37.jar:4.0.FR]在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.37.jar:9.0.37]在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]在 java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]在 org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.37.jar:9.0.37]在 java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]2020-08-06 18:11:20.979 错误 9932 --- [nio-8080-exec-1] oaccC[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] 在上下文中路径[]抛出异常[请求处理失败;嵌套异常是 org.springframework.http.converter.HttpMessageNotWritableException:无法写入 JSON:无限递归(StackOverflowError);嵌套异常是 com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError)(通过参考链:org.springframework.data.mongodb.core.mapping.CachingMongoPersistentProperty[owner"]->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity[idProperty"]->org.springframework.data.mongodb.core.mapping.CachingMongoPersistentProperty[owner"]->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity[idProperty"]->org.springframework.data.mongodb.core.mapping.CachingMongoPersistentProperty[owner"]->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity[Property";]->org.springframework.data.mongodb.core.mapping.CachingMongoPersistentProperty[owner"]->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity[idProperty"]->org.springframework.data.mongodb.core.mapping.CachingMongoPersistentProperty[owner"]->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity[idProperty"]->......它像这样继续...... ->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity[idProperty"])]与根本原因java.lang.StackOverflowError: null在 java.base/java.lang.ClassLoader.defineClass1(Native Method) ~[na:na]在 java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016) ~[na:na]在 java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174) ~[na:na]在 java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:800) ~[na:na]在 java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:698) ~[na:na]在 java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:621) ~[na:na]在 java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:579) ~[na:na]在 java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) ~[na:na]在 java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]在 com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:773) ~[jackson-databind-2.11.1.jar:2.11.1]在 com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.11.1.jar:2.11.1]在 com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.11.1.jar:2.11.1]在 com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755) ~[jackson-databind-2.11.1.jar:2.11.1]在 com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.11.1.jar:2.11.1]... 几十条线路都是这样在 com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.11.1.jar:2.11.1]在 com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755) ~[jackson-databind-2.11.1.jar:2.11.1]2020-08-06 18:11:21.553 错误 9932 --- [nio-8080-exec-1] seErrorMvcAutoConfiguration$StaticView:无法为请求 [/foos/search/query] 和异常 [] 呈现错误页面作为响应已经承诺.因此,响应可能具有错误的状态代码.

如果返回var page = repository.findByName(name, pageable);的内容而不是var model = pagedResourcesAssembler.toModel(page, resourceAssembler);,我得到以下结果:

{"content":[{"id":"56a8a8d5daffd28c9c907974","name":"qc"}],"pageable":{"sort":{";sorted":false,"unsorted":true,"empty":true},"offset":0,"pageNumber":0,"pageSize":20,"paged":true,"unpaged":false},"last":true,"totalPages":1,"totalElements":1,"size":20,"number":0,"sort":{"sorted";:false,"unsorted":true,"empty":true},"numberOfElements":1,"first":true,"empty":false}

所以循环混乱来自序列化 pagedResourcesAssembler.toModel(page, resourceAssembler)(可能还有 WebMvcConfigurer 覆盖).

解决方案

当我调用 PersistentEntityResourceAssembler.toModel(Object) 时,我遇到了相同的 StackOverflowError: null.我的代码返回一个 EntityModel 而不是 PagedModel.我的问题是通过切换到 PersistentEntityResourceAssembler.toFullResource(Object) 来解决的.

toModel(Object) 使用摘录投影,而 toFullResource(Object) 不使用.我没有时间深入代码来检查是什么产生了无限循环从而导致堆栈溢出.

查看PagedResourcesAssembler的源码,PagedResourcesAssembler.toModel(Page, RepresentationModelAssembler)调用RepresentationModelAssember.toModel(Object).>

所以解决方法 1 是复制 PagedResourcesAssembler.toModel(Page, RepresentationModelAssembler) 的源代码并使用 RepresentationModelAssember.toFullResource(Object).

解决方法 2 是扩展 PagedResourcesAssembler 并覆盖 createModel(...) 方法.

在解决此问题之前,我有一个问题.为什么要将 PersistentEntityResourceAssembler 传递给 PagedResourcesAssembler?当我的自定义控制器返回单个资源时,我的代码使用 PersistentEntityResourceAssembler 添加 HTTP 标头 ETagLast-Modified.使用 PersistentEntityResourceAssembler 构建集合资源(分页资源)的响应有什么好处?

Until Spring Boot 2.0, I could reproduce the controllers generated for query methods exposed by a mongodb repository.Here is a code sample:

Domain Entity

@Document(collection = "foos")
public class Foo {
    @Id
    private String id;
    private String name;

    // getters/setters omitted
}

Mongo repository

public interface FooRepository extends MongoRepository<Foo, String> {

    public Page<Foo> findByName(@Param("name") String name, Pageable pageable);

}

Spring Boot automatically exposes the search method through /foos/search/findByName?name=... with a result similar to this:

{
  "_embedded" : {
    "foos" : [ {
      "name" : "qc",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/foos/56a8a8d5daffd28c9c907974"
        },
        "foo" : {
          "href" : "http://localhost:8080/foos/56a8a8d5daffd28c9c907974"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/foos/search/findByName?name=qc&page=0&size=20"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

I could reproduce it with the following custom controller and configuration

@RestController
@RequestMapping("foos")
@RequiredArgsConstructor // lombok
public class FooQueryController {

    private final FooRepository repository;
    private final PagedResourcesAssembler pagedResourcesAssembler;

    @GetMapping(value = "search/query",
                produces = MediaType.APPLICATION_JSON_UT8_VALUE)
    public ResponseEntity custom(@RequestParam("name") String name,
                                      Pageable pageable,
                                      PersistentEntityResourceAssembler resourceAssembler) {
        var page = repository.findByName(name, pageable);
        var model = pagedResourcesAssembler.toResource(page, resourceAssembler);

        return ResponseEntity.ok(model);
    }

}

// Enables injecting a PersistentEntityResourceAssembler  in a RestController
// see https://jira.spring.io/browse/DATAREST-657 for details
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
@RequiredArgsConstructor
public class MvcConfiguration implements WebMvcConfigurer {

    // WARNING: do NOT change the name of this member - it is injected with the
    //          RequestMappingHandlerAdapter$repositoryExporterHandlerAdapter().
    private final RequestMappingHandlerAdapter repositoryExporterHandlerAdapter;

    @Override
    public void addArgumentResolvers(
            List<HandlerMethodArgumentResolver> argumentResolvers) {
        List<HandlerMethodArgumentResolver> customArgumentResolvers =
                repositoryExporterHandlerAdapter.getCustomArgumentResolvers();
        argumentResolvers.addAll(customArgumentResolvers);
    }

}

With this I can send a request to /foos/search/query?name=... and get the expected response:

{
  "_embedded" : {
    "foos" : [ {
      "name" : "qc",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/foos/56a8a8d5daffd28c9c907974"
        },
        "foo" : {
          "href" : "http://localhost:8080/foos/56a8a8d5daffd28c9c907974"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/foos/search/query?name=qc&page=0&size=20"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

Switching to Spring Boot 2.3, using the Spring HATEOAS 1.0 API in the controller

@RestController
@RequestMapping("foos")
@RequiredArgsConstructor
public class FooQueryController {

    private final FooRepository repository;
    private final PagedResourcesAssembler pagedResourcesAssembler;

    @GetMapping(value = "search/query",
                produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity custom(@RequestParam("name") String name,
                                      Pageable pageable,
                                      PersistentEntityResourceAssembler resourceAssembler) {
        var page = repository.findByName(name, pageable);
        var model = pagedResourcesAssembler.toModel(page, resourceAssembler);

        return ResponseEntity.ok(model);
    }

}

I now get the following result:

{"_embedded":{"foos":[{"id":"56a8a8d5daffd28c9c907974","name":"qc","embeddeds":{},"nested":false,"persistentEntity":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":{"name":"id","rawType":"java.lang.String","association":false,"owner":{"idProperty":

With the following errors in Spring's log:

2020-08-06 18:11:20.968  WARN 9932 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Failure while trying to resolve exception [org.springframework.http.converter.HttpMessageNotWritableException]

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
    at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:472) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.sendServerError(DefaultHandlerExceptionResolver.java:550) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.handleHttpMessageNotWritable(DefaultHandlerExceptionResolver.java:440) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.doResolveException(DefaultHandlerExceptionResolver.java:210) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:141) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.servlet.handler.HandlerExceptionResolverComposite.resolveException(HandlerExceptionResolverComposite.java:80) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1300) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1111) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1057) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:626) ~[tomcat-embed-core-9.0.37.jar:4.0.FR]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.37.jar:4.0.FR]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.37.jar:9.0.37]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
    at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

2020-08-06 18:11:20.979 ERROR 9932 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: org.springframework.data.mongodb.core.mapping.CachingMongoPersistentProperty["owner"]->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity["idProperty"]->org.springframework.data.mongodb.core.mapping.CachingMongoPersistentProperty["owner"]->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity["idProperty"]->org.springframework.data.mongodb.core.mapping.CachingMongoPersistentProperty["owner"]->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity["idProperty"]->org.springframework.data.mongodb.core.mapping.CachingMongoPersistentProperty["owner"]->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity["idProperty"]->org.springframework.data.mongodb.core.mapping.CachingMongoPersistentProperty["owner"]->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity["idProperty"]-> ... IT GOES ON AND ON LIKE THIS ... ->org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity["idProperty"])] with root cause

java.lang.StackOverflowError: null
    at java.base/java.lang.ClassLoader.defineClass1(Native Method) ~[na:na]
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016) ~[na:na]
    at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:800) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:698) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:621) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:579) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:773) ~[jackson-databind-2.11.1.jar:2.11.1]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.11.1.jar:2.11.1]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.11.1.jar:2.11.1]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755) ~[jackson-databind-2.11.1.jar:2.11.1]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.11.1.jar:2.11.1]

    ... IT GOES ON LIKE THIS FOR DOZENS AND DOZENS OF LINE

    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.11.1.jar:2.11.1]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755) ~[jackson-databind-2.11.1.jar:2.11.1]

2020-08-06 18:11:21.553 ERROR 9932 --- [nio-8080-exec-1] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/foos/search/query] and exception [] as the response has already been committed. As a result, the response may have the wrong status code.

If if return the contents of var page = repository.findByName(name, pageable); instead of var model = pagedResourcesAssembler.toModel(page, resourceAssembler);, I get the following result:

{"content":[{"id":"56a8a8d5daffd28c9c907974","name":"qc"}],"pageable":{"sort":{"sorted":false,"unsorted":true,"empty":true},"offset":0,"pageNumber":0,"pageSize":20,"paged":true,"unpaged":false},"last":true,"totalPages":1,"totalElements":1,"size":20,"number":0,"sort":{"sorted":false,"unsorted":true,"empty":true},"numberOfElements":1,"first":true,"empty":false}

So the circular mess comes from the serialization pagedResourcesAssembler.toModel(page, resourceAssembler) (and probably the WebMvcConfigurer override).

解决方案

I had the same StackOverflowError: null when I call PersistentEntityResourceAssembler.toModel(Object). My code returns a single EntityModel instead of a PagedModel. My problem is solved by switching to PersistentEntityResourceAssembler.toFullResource(Object).

The toModel(Object) uses excerpt projection while toFullResource(Object) does not. I don't have time to dig into the code to check what produces an infinite loop to cause stack overflow.

Look at the source code of PagedResourcesAssembler, PagedResourcesAssembler.toModel(Page, RepresentationModelAssembler) calls RepresentationModelAssember.toModel(Object).

So workaround 1 is copying the source code of PagedResourcesAssembler.toModel(Page, RepresentationModelAssembler) and use RepresentationModelAssember.toFullResource(Object).

Workaround 2 is extending the PagedResourcesAssembler and overriding the createModel(...) method.

Before making the workaround, I have one question. Why do you pass a PersistentEntityResourceAssembler to PagedResourcesAssembler? My code uses PersistentEntityResourceAssembler to add HTTP headers ETag and Last-Modified when my custom controller returns a single resource. What good does it have to use PersistentEntityResourceAssembler to build the response of a collection resource (paged resource)?

这篇关于在 Spring Boot 2.3 中重现 Spring Data Rest 搜索控制器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

05-27 03:53
查看更多