SpringBoot之:SpringBoot中使用HATEOAS

2022-06-15 18:02:53

簡介

HATEOAS是實現REST規範的一種原則,通過遵循HATEOAS規範,可以解決我們實際程式碼實現的各種個問題。作為java最流行的框架Spring
當然也會不缺席HATEOAS的整合。

本文將會通過一個具體的例子來講解如何在SpringBoot中使用HATEOAS。

我們的目標

HATEOAS規則中,返回的資料會帶有連結。我們以熟悉的Book為例,來展示這次的HATEOAS,首先建立一個Book entity:

@Data
@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
}

我們希望能夠通過下面的連結來獲取到Book的詳細資料:

GET /book/1

返回的資料如下:


{
    "content": {
        "id": 1,
        "title": "The Hobbit"
    },
    "_links": {
        "self": {
            "href": "http://localhost:8080/book/1"
        }
    }
}

可以看到在返回的資料中除了content包含了book的資訊之外,還有一個_links屬性,表示和該Book相關的資源連結。

構建Entity和Repository

在做任何資料之前,我們都需要構建相應的資料,也就是entity和對應的資料操作,為了簡便起見,我們使用H2的記憶體資料庫。

我們需要在application.properties中設定如下:

spring.jpa.hibernate.ddl-auto=validate

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

然後設定對應的repository :

public interface BookRepository extends CrudRepository<Book, Long> {
    long deleteByTitle(String title);

    @Modifying
    @Query("delete from Book b where b.title=:title")
    void deleteBooks(@Param("title") String title);
}

同時,需要在resources中放置建立table的schema.sql和插入資料的data.sql。這樣在程式啟動的時候就可以自動建立相應的資料。

構建HATEOAS相關的RepresentationModel

如果要讓自己來實現,也可以實現新增連結的操作,但是這樣就太複雜了,還好我們有Spring。要在Spring中使用HATEOAS,需要進行如下設定:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-hateoas</artifactId>
        </dependency>

如果我們想要對Book進行HATEOAS的構建,那麼可以構建一個類,繼承RepresentationModel即可:

public class BookModel extends RepresentationModel<BookModel> {

    private final Book content;

    @JsonCreator
    public BookModel(@JsonProperty("content") Book content) {
        this.content = content;
    }

    public Book getContent() {
        return content;
    }
}

上面的例子中,我們用RepresentationModel封裝了一個Book物件,並將其設定為json的content屬性。

構建Controller

有了RepresentationModel,我們就可以使用它來構建HATEOAS的響應了。

我們看下面的例子:

	@RequestMapping("/book/{id}")
	public HttpEntity<Book> getBook(@PathVariable("id") Long id) {
		Book book= bookRepository.findById(id).get();
		BookModel bookModel = new BookModel(book);
		bookModel.add(linkTo(methodOn(BookController.class).getBook(id)).withSelfRel());
		return new ResponseEntity(bookModel, HttpStatus.OK);
	}

上面的例子中,我們使用@RequestMapping來構建了一個HTTP請求,通過傳入book的id來從資料庫中查詢相應的Book資料。

然後將其傳入BookModel中,構建好RepresentationModel。這時候可以直接返回這個物件。但是我們還需要向其新增一些links。

我們使用bookModel.add來新增相應的link。並且使用linkTo方法來生成相應的link。

最後將RepresentationModel返回。

當我們請求/book/1的時候,就會得到最前面我們想要得到的json值。使用HATEOAS是不是很簡單?

HATEOAS的意義

HATEOAS帶有相應的資源連結,通過一個資源就可以得到從這個資源可以存取的其他的資源,就像是一個存取到一個頁面,可以再通過這個頁面去存取其他的頁面一樣。

所以HATEOAS的意義就在於我們只需要存取一個資源就可以遍歷所有的資源。

我們通過測試來體驗一下資源的存取。

首先,我們直接存取/book/1這個資源,來確認下得到的結果:

    @Test
    void envEndpointNotHidden() throws Exception {
        mockMvc.perform(get("/book/1"))
                .andExpect(jsonPath("$.content.title").value("The Hobbit"));
    }

然後再通過Spring HATEOAS提供的Traverson類來進行連結的遍歷:

	@Test
	void envEndpointNotHidden() throws Exception {
		Traverson traverson = new Traverson(new URI("http://localhost:" + this.port + "/book/1"), MediaTypes.HAL_JSON);
		String bookTitle = traverson.follow("self").toObject("$.content.title");
		assertThat(bookTitle).isEqualTo("The Hobbit");
	}

總結

很好,我們已經可以使用基本的HATEOAS了,本文例子可以參考:

learn-springboot2