You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 2.3.x 自定义@RestController如何应用投影及返回HAL格式数据

自定义控制器集成Spring Data REST投影功能无限递归问题解决方案

问题场景

我尝试在标注了@RestController的自定义控制器返回的实体上应用投影功能,代码如下:

@RequestMapping(method = GET, value = "customer-api/students/viewProfile")
public @ResponseBody
ResponseEntity<?> fetchProfile(PersistentEntityResourceAssembler resourceAssembler) {

    Student student = studentRepo.findByCreatedBy(accessToken.getSubject());

    if (student != null) {
        return new ResponseEntity<>(resourceAssembler.toModel(student), HttpStatus.OK);
    } else {
        return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
    }

}

运行时抛出无限递归异常,报错信息如下:

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.jpa.mapping.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping.JpaPersistentPropertyImpl["owner"]->org.springframework.data.jpa.mapping.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping.JpaPersistentPropertyImpl["owner"]

该代码在Spring Boot 1.5.17版本中运行正常,可以正常应用投影并返回HAL格式的JSON,但升级到Spring 2.3.x版本后就报错。需要实现Spring Data REST导出控制器的相关能力,包括投影功能、返回HAL格式JSON。

补充说明

Student实体不存在双向关联关系,同时使用了PersistentEntityResourceAssembler来组装Student实体的响应体,按照规范说明:

PersistentEntityResourceAssembler - 通常注入到控制器方法中,它会按照Spring Data REST的规范渲染单个实体,指向托管类型的关联会被渲染为链接等形式。
@ManyToOne关联会被渲染为链接,不符合递归问题出现的常规触发条件。

根因分析

Spring Boot 2.3+版本对Spring Data REST的序列化逻辑做了调整:

  • 1.5.x版本中自定义控制器返回PersistentEntityResource时,Spring会自动应用Spring Data REST内置的Jackson序列化器处理
  • 2.3.x版本后该序列化器默认仅作用于Spring Data REST自动生成的接口,自定义控制器返回的PersistentEntityResource会被默认Jackson序列化器处理,直接序列化对象内部的JPA元数据属性JpaPersistentEntityImpl和JpaPersistentPropertyImpl,两者互相持有对方引用触发无限递归。

解决方案

方案1:使用PersistentEntityResource.Builder手动构建返回资源(推荐)

主动指定投影参数、关联链接,保证序列化逻辑符合Spring Data REST规范:

// 所需依赖可以直接从Spring上下文注入
private final ProjectionFactory projectionFactory;
private final PersistentEntity<Student, ?> persistentEntity;
private final EntityLinks entityLinks;

@RequestMapping(method = GET, value = "customer-api/students/viewProfile")
public ResponseEntity<?> fetchProfile() {
    Student student = studentRepo.findByCreatedBy(accessToken.getSubject());
    if (student == null) {
        return ResponseEntity.notFound().build();
    }
    // 应用指定投影,替换为你自定义的投影接口即可
    Object projectedStudent = projectionFactory.createProjection(StudentProfileProjection.class, student);
    // 构建符合Spring Data REST规范的资源对象
    PersistentEntityResource resource = PersistentEntityResource.build(projectedStudent, persistentEntity)
            .withLink(entityLinks.linkToItemResource(Student.class, student.getId()).withSelfRel())
            .build();
    return ResponseEntity.ok(resource);
}

方案2:复用Spring Data REST内置控制器逻辑

自定义控制器继承RepositoryEntityController,直接复用框架自带的序列化、投影、关联链接生成能力,无需手动处理资源组装逻辑。

方案3:手动注册全局序列化器

在配置类中注册Spring Data REST专属的Jackson模块,让自定义控制器返回的资源也能被正确序列化:

@Configuration
public class CustomRestConfig {
    @Bean
    public Module persistentEntityJackson2Module(Associations associations,
                                                  EntityLinks entityLinks,
                                                  RelProvider relProvider,
                                                  RepositoryInvokerFactory repositoryInvokerFactory,
                                                  ProjectionFactory projectionFactory) {
        return new PersistentEntityJackson2Module(associations, entityLinks, relProvider, repositoryInvokerFactory, projectionFactory);
    }
}

以上所有方案都可以在自定义控制器中实现投影功能、返回标准HAL格式JSON的需求。


内容的提问来源于stack exchange,提问作者Charlie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 04:09:03