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



我正在尝试使用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:

@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

,因为用户和帐户之间的循环引用。当我通过向User中的帐户列表添加@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)

的实体类,但这并没有得到我任何地方。我只是希望我的控制器返回与生成的API相同的对象,使用HAL _links而不是每个引用都被序列化。任何想法吗?

编辑:

好吧,经过两次思考,我意识到@EnableHypermediaSupport注释必须添加到配置,当然。这解决了循环引用的问题,我可以从用户中删除@JsonBackReference。但是只有对象本身的属性被序列化,没有_links部分:

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

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

这里有很多方面:

  1. 我怀疑/transactions的集合资源是否真的像您描述的那样返回单个事务。

  2. 如果TransactionRepository已经是PageableAndSortingRepository,收集资源可以通过扩展API根中暴露的URI模板来调整命名为transactions的链接。默认情况下是pagesizesort参数。这意味着客户端可以请求您想要公开的内容。

  3. 如果你想默认分页和排序选项,实现一个控制器是正确的方法。然而,要实现像Spring Data REST公开这样的表示,您需要至少返回ResourceSupport的实例,因为这是HAL映射注册的类型。

    如果你仔细想想,这里没有什么神奇的。普通实体没有任何链接,ResourcesSupportResource<T>之类的类型允许您包装实体并使用您认为合适的链接来丰富它。Spring Data REST基本上使用了大量关于隐式可用的域和存储库结构的知识来为您完成这些工作。您可以重用很多,如下所示。

    这里有几个你需要注意的帮手:

    • PersistentEntityResourceAssembler——通常注入到控制器方法中。它以Spring Data REST方式呈现单个实体,这意味着指向托管类型的关联将呈现为链接等。
    • PagedResourcesAssembler——通常注入控制器实例。负责准备页面中包含的项目,可选择使用专用的ResourceAssembler

    Spring Data REST对页面的基本作用如下:

    PersistentEntityResourceAssembler entityAssembler = …;
    Resources<?> … = pagedResourcesAssembler.toResources(page, entityAssembler);
    

    这基本上是使用PagedResourcesAssemblerPersistentEntityResourceAssembler来渲染项目。

    返回Resources实例应该会给你期望的表示设计。

您不需要创建自己的控制器来限制查询结果或对结果进行排序。只需在存储库中创建一个查询方法:

public interface TransactionRepository extends MongoRepository<Transaction, String> {
    List<Transaction> findFirst10ByOrderByDateDesc();
}

Spring Data REST将自动将其导出为/transactions/search/findFirst10ByOrderByDateDesc的方法资源。

要在控制器中使用PersistentEntityResourceAssembler,我们应该将其标记为@RepositoryRestController

@RestController
@RequestMapping("/categories")
@RepositoryRestController
public class CategoryController implements ValidableController {
// dependencies
@RequestMapping(method = POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PersistentEntityResource> create(@Valid @RequestBody CategoryForm category,
                                                       BindingResult validation,
                                                       PersistentEntityResourceAssembler resourceAssembler)
{
    validate(validation);
    Category entity = categoryConverter.convert(category);
    entity = categoryService.save(entity);
    return ResponseEntity.ok(resourceAssembler.toFullResource(entity));
}

它构建了非常漂亮的HAL样式的响应

{
"createdTime": "2018-07-24T00:55:32.854",
"updatedTime": "2018-07-24T00:55:32.855",
"name": "cfvfcdfgdfdfdfs32",
"options": [
    "aaa",
    "bbb"
],
"_links": {
    "self": {
        "href": "http://localhost:8080/shop/categories/34"
    },
    "category": {
        "href": "http://localhost:8080/shop/categories/34{?projection}",
        "templated": true
    },
    "products": {
        "href": "http://localhost:8080/shop/categories/34/products"
    },
    "categories": {
        "href": "http://localhost:8080/shop/categories/34/categories{?projection}",
        "templated": true
    },
    "parent": {
        "href": "http://localhost:8080/shop/categories/34/parent{?projection}",
        "templated": true
    }
}

}

最新更新