HATEOAS for a Spring REST Service – 用于Spring REST服务的HATEOAS

最后修改: 2011年 11月 13日


1. Overview


This article will focus on the implementation of discoverability in a Spring REST Service and on satisfying the HATEOAS constraint.

本文将重点讨论在Spring REST服务中实现可发现性以及满足HATEOAS约束的问题。

This article focuses on Spring MVC. Our article An Intro to Spring HATEOAS describes how to use HATEOAS in Spring Boot.

本文重点介绍Spring MVC。我们的文章An Intro to Spring HATEOAS介绍了如何在Spring Boot中使用HATEOAS。

2. Decoupling Discoverability Through Events


Discoverability as a separate aspect or concern of the web layer should be decoupled from the controller handling the HTTP request. For this purpose, the Controller will fire off events for all the actions that require additional manipulation of the response.


First, let’s create the events:


public class SingleResourceRetrieved extends ApplicationEvent {
    private HttpServletResponse response;

    public SingleResourceRetrieved(Object source, HttpServletResponse response) {

        this.response = response;

    public HttpServletResponse getResponse() {
        return response;
public class ResourceCreated extends ApplicationEvent {
    private HttpServletResponse response;
    private long idOfNewResource;

    public ResourceCreated(Object source, 
      HttpServletResponse response, long idOfNewResource) {

        this.response = response;
        this.idOfNewResource = idOfNewResource;

    public HttpServletResponse getResponse() {
        return response;
    public long getIdOfNewResource() {
        return idOfNewResource;

Then, the Controller, with 2 simple operations – find by id and create:


@RequestMapping(value = "/foos")
public class FooController {

    private ApplicationEventPublisher eventPublisher;

    private IFooService service;

    @GetMapping(value = "foos/{id}")
    public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) {
        Foo resourceById = Preconditions.checkNotNull(service.findOne(id));

        eventPublisher.publishEvent(new SingleResourceRetrieved(this, response));
        return resourceById;

    public void create(@RequestBody Foo resource, HttpServletResponse response) {
        Long newId = service.create(resource).getId();

        eventPublisher.publishEvent(new ResourceCreated(this, response, newId));

We can then handle these events with any number of decoupled listeners. Each of these can focus on its own particular case and help towards satisfying the overall HATEOAS constraint.


The listeners should be the last objects in the call stack and no direct access to them is necessary; as such they are not public.


3. Making the URI of a Newly Created Resource Discoverable


As discussed in the previous post on HATEOAS, the operation of creating a new Resource should return the URI of that resource in the Location HTTP header of the response.

正如在HATEOAS 上一篇文章中所讨论的那样,创建新资源的操作应在响应的Location HTTP 头中返回该资源的 URI

We’ll handle this using a listener:


class ResourceCreatedDiscoverabilityListener
  implements ApplicationListener<ResourceCreated>{

    public void onApplicationEvent(ResourceCreated resourceCreatedEvent){

       HttpServletResponse response = resourceCreatedEvent.getResponse();
       long idOfNewResource = resourceCreatedEvent.getIdOfNewResource();

       addLinkHeaderOnResourceCreation(response, idOfNewResource);
   void addLinkHeaderOnResourceCreation
     (HttpServletResponse response, long idOfNewResource){
       URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri().
       response.setHeader("Location", uri.toASCIIString());

In this example, we’re making use of the ServletUriComponentsBuilder – which helps with using the current Request. This way, we don’t need to pass anything around and we can simply access this statically.


If the API would return ResponseEntity – we could also use the Location support.

如果API将返回ResponseEntity – 我们也可以使用Location支持

4. Getting a Single Resource


On retrieving a single Resource, the client should be able to discover the URI to get all Resources of that type:


class SingleResourceRetrievedDiscoverabilityListener
 implements ApplicationListener<SingleResourceRetrieved>{

    public void onApplicationEvent(SingleResourceRetrieved resourceRetrievedEvent){

        HttpServletResponse response = resourceRetrievedEvent.getResponse();
        addLinkHeaderOnSingleResourceRetrieval(request, response);
    void addLinkHeaderOnSingleResourceRetrieval(HttpServletResponse response){
        String requestURL = ServletUriComponentsBuilder.fromCurrentRequestUri().
        int positionOfLastSlash = requestURL.lastIndexOf("/");
        String uriForResourceCreation = requestURL.substring(0, positionOfLastSlash);

        String linkHeaderValue = LinkUtil
          .createLinkHeader(uriForResourceCreation, "collection");
        response.addHeader(LINK_HEADER, linkHeaderValue);

Note that the semantics of the link relation make use of the “collection” relation type, specified and used in several microformats, but not yet standardized.


The Link header is one of the most used HTTP headers for the purposes of discoverability. The utility to create this header is simple enough:


public class LinkUtil {
    public static String createLinkHeader(String uri, String rel) {
        return "<" + uri + ">; rel=\"" + rel + "\"";

5. Discoverability at the Root


The root is the entry point in the entire service – it’s what the client comes into contact with when consuming the API for the first time.


If the HATEOAS constraint is to be considered and implemented throughout, then this is the place to start. Therefore all the main URIs of the system have to be discoverable from the root.


Let’s now look at the controller for this:


@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) {
    String rootUri = request.getRequestURL().toString();

    URI fooUri = new UriTemplate("{rootUri}{resource}").expand(rootUri, "foos");
    String linkToFoos = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection");
    response.addHeader("Link", linkToFoos);

This is, of course, an illustration of the concept, focusing on a single, sample URI, for Foo Resources. A real implementation should add, similarly, URIs for all the Resources published to the client.


5.1. Discoverability Is Not About Changing URIs


This can be a controversial point – on the one hand, the purpose of HATEOAS is to have the client discover the URIs of the API and not rely on hardcoded values. On the other hand – this is not how the web works: yes, URIs are discovered, but they are also bookmarked.


A subtle but important distinction is the evolution of the API – the old URIs should still work, but any client that will discover the API should discover the new URIs – which allows the API to change dynamically, and good clients to work well even when the API changes.


In conclusion – just because all URIs of the RESTful web service should be considered cool URIs (and cool URIs don’t change) – that doesn’t mean that adhering to the HATEOAS constraint isn’t extremely useful when evolving the API.

总之–只是因为RESTful网络服务的所有URI应该被认为是cool。w3.org/TR/cooluris/” rel=”nofollow noopener noreferrer” target=”_blank” title=”酷的URI规范”>URI(而酷的URI 不改变)- 但这并不意味着在发展API时遵守HATEOAS的约束就不那么有用了。

6. Caveats of Discoverability


As some of the discussions around the previous articles state, the first goal of discoverability is to make minimal or no use of documentation and have the client learn and understand how to use the API via the responses it gets.


In fact, this shouldn’t be regarded as such a far fetched ideal – it’s how we consume every new web page – without any documentation. So, if the concept is more problematic in the context of REST, then it must be a matter of technical implementation, not of a question of whether or not it’s possible.


That being said, technically, we are still far from a fully working solution – the specification and framework support are still evolving, and because of that, we have to make some compromises.


7. Conclusion


This article covered the implementation of some of the traits of discoverability in the context of a RESTful Service with Spring MVC and touched on the concept of discoverability at the root.

这篇文章介绍了在使用Spring MVC的RESTful服务的背景下实现可发现性的一些特征,并从根本上触及了可发现性的概念。

The implementation of all these examples and code snippets can be found over on GitHub – this is a Maven-based project, so it should be easy to import and run as it is.

所有这些例子和代码片段的实现都可以在GitHub上找到over – 这是一个基于Maven的项目,因此应该很容易导入并按原样运行。