一尘不染

在Spring Boot中为自定义控制器方法启用HAL序列化

spring

我正在尝试使用spring-boot-starter-data-rest用Spring Boot构建RESTful API。有一些实体:帐户,交易,类别和用户-只是通常的东西。

当我通过默认生成的API 在http:// localhost:8080 / transactions上检索对象时,一切进展顺利,我得到了一个列表,其中所有事务都像JSON对象那样:

{
  "amount": -4.81,
  "date": "2014-06-17T21:18:00.000+0000",
  "description": "Pizza",
  "_links": {
    "self": {
      "href": "http://localhost:8080/transactions/5"
    },
    "category": {
      "href": "http://localhost:8080/transactions/5/category"
    },
    "account": {
      "href": "http://localhost:8080/transactions/5/account"
    }
  }
}

但是现在的目标是仅检索该URL下的最新事务,因为我不想序列化整个数据库表。所以我写了一个控制器:

@Controller
public class TransactionController {
    private final TransactionRepository transactionRepository;

    @Autowired
    public TransactionController(TransactionRepository transactionRepository) {
        this.transactionRepository = transactionRepository;
    }

    // return the 5 latest transactions
    @RequestMapping(value = "/transactions", method = RequestMethod.GET)
    public @ResponseBody List<Transaction> getLastTransactions() {
        return  transactionRepository.findAll(new PageRequest(0, 5, new Sort(new Sort.Order(Sort.Direction.DESC, "date")))).getContent();
    }
}

现在,当我尝试访问http:// localhost:8080 / transactions时,

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed

因为用户和帐户之间存在循环引用。通过在用户的帐户列表中添加@JsonBackReference批注解决此问题时,我可以检索交易列表,但只能使用这种“经典”格式:

{
  "id": 5,
  "amount": -4.5,
  "date": "2014-06-17T21:18:00.000+0000",
  "description": "Pizza",
  "account": {
    "id": 2,
    "name": "Account Tilman",
    "owner": {
      "id": 1,
      "name": "Tilman"
    },
    "categories": [
      {
        "id": 1,
        "name": "Groceries"
      },
      {
        "id": 2,
        "name": "Restaurant"
      }
    ],
    "users": [
      {
        "id": 1,
        "name": "Tilman"
      }
    ]
  },
  "category": {
    "id": 2,
    "name": "Restaurant"
  }
}

不再有HAL链接,所有东西都由jackson直接序列化。我尝试添加

@EnableHypermediaSupport(type = HypermediaType.HAL)

到实体类,但是那没让我走到哪里。我只希望我的控制器使用HAL _links返回与生成的API相同的对象,而不是序列化每个引用。有什么想法吗?

编辑: 好的,三思后,我意识到必须将@EnableHypermediaSupport批注添加到配置中。这解决了循环引用的问题,我可以从User中删除@JsonBackReference。但是只有对象本身的属性被序列化,没有_links部分:

{
    "amount": -4.81,
    "date": "2014-06-17T21:18:00.000+0000",
    "description": "Pizza"
}

我知道我可以编写包装类来为所有实体扩展ResourceSupport,但这似乎毫无意义。由于spring-hateoas可以使用_link节为自动创建的REST接口神奇地生成表示形式,因此应该有一种从自定义控制器返回相同表示形式的方法,对吗?


阅读 386

收藏
2020-04-14

共1个答案

一尘不染

这里有很多方面:

  1. 我怀疑收集资源是否/transactions真的返回了你所描述的单个事务。这些表示形式将返回用于物料资源。

  2. 如果TransactionRepository已经是PageableAndSortingRepository集合资源,则可以通过展开在API根目录中公开的URI模板(名为)来调整集合资源transactions。默认情况下,这是一个pagesizesort参数。这意味着客户可以请求你想要公开的内容。

  3. 如果要默认使用分页和排序选项,则实现控制器是正确的方法。但是,要实现类似Spring Data REST公开的表示形式,你至少需要返回的实例,ResourceSupport因为这是HAL映射所注册的类型。

如果你考虑一下,这里没有神奇的事物。一个普通的实体没有任何链接,ResourcesSupport像和这样的类型Resource<T>允许你包装该实体并在你认为合适的情况下添加链接。Spring Data REST基本上是使用大量隐式可用的有关域和存储库结构的知识来为你完成的。你可以重复使用很多,如下所示。

你需要在此处了解一些帮助程序:

  • PersistentEntityResourceAssembler-通常注入到控制器方法中。它以Spring Data REST方式呈现单个实体,这意味着指向托管类型的关联将呈现为链接等。
  • PagedResourcesAssembler-通常注入控制器实例中。负责准备页面中包含的项目,可以选择使用专用的ResourceAssembler。
    Spring Data REST对页面的基本作用如下:
PersistentEntityResourceAssembler entityAssembler = …;
Resources<?> … = pagedResourcesAssembler.toResources(page, entityAssembler);

基本上是使用PagedResourcesAssemblerPersistentEntityResourceAssembler一起呈现项目。

返回该Resources实例将为你提供所需的表示形式设计。

2020-04-14