6.2 启用超媒体
6.2 启用超媒体
到目前为止,创建的/design/recent
接口发出/design
接口中,以获得特定
使用硬编码的
超媒体作为应用程序状态的引擎(HATEOAS
例如,假设一个客户端请求一个最近设计的
[
{
"id": 4,
"name": "Veg-Out",
"createdAt": "2018-01-31T20:15:53.219+0000",
"ingredients": [
{"id": "FLTO", "name": "Flour Tortilla", "type": "WRAP"},
{"id": "COTO", "name": "Corn Tortilla", "type": "WRAP"},
{"id": "TMTO", "name": "Diced Tomatoes", "type": "VEGGIES"},
{"id": "LETC", "name": "Lettuce", "type": "VEGGIES"},
{"id": "SLSA", "name": "Salsa", "type": "SAUCE"}
]
},
...
]
如果客户端希望在/design
的/ingredients
的http://
或 https://
和
相反,如果使用超媒体启用了
{
"_embedded": {
"tacoResourceList": [
{
"name": "Veg-Out",
"createdAt": "2018-01-31T20:15:53.219+0000",
"ingredients": [
{
"name": "Flour Tortilla", "type": "WRAP",
"_links": {
"self": { "href": "http://localhost:8080/ingredients/FLTO" }
}
},
{
"name": "Corn Tortilla", "type": "WRAP",
"_links": {
"self": { "href": "http://localhost:8080/ingredients/COTO" }
}
},
{
"name": "Diced Tomatoes", "type": "VEGGIES",
"_links": {
"self": { "href": "http://localhost:8080/ingredients/TMTO" }
}
},
{
"name": "Lettuce", "type": "VEGGIES",
"_links": {
"self": { "href": "http://localhost:8080/ingredients/LETC" }
}
},
{
"name": "Salsa", "type": "SAUCE",
"_links": {
"self": { "href": "http://localhost:8080/ingredients/SLSA" }
}
}
],
"_links": {
"self": { "href": "http://localhost:8080/design/4" }
}
},
...
]
},
"_links": {
"recents": {
"href": "http://localhost:8080/design/recent"
}
}
}
这种特殊风格的
虽然这个列表不像以前那样简洁,但它确实提供了一些有用的信息。这个新的
如果客户端应用程序需要对列表中的
要在
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
这个启动程序不仅将/design/recent
的
6.2.1 添加超链接
要将超链接添加到最近创建的
@GetMapping("/recent")
public Resources<Resource<Taco>> recentTacos() {
PageRequest page = PageRequest.of(
0, 12, Sort.by("createdAt").descending());
List<Taco> tacos = tacoRepo.findAll(page).getContent();
Resources<Resource<Taco>> recentResources = Resources.wrap(tacos);
recentResources.add(
new Link("http://localhost:8080/design/recent", "recents"));
return recentResources;
}
在这个新版本的
"_links": {
"recents": {
"href": "http://localhost:8080/design/recent"
}
}
这是一个好的开始,但你仍有一些工作要做。此时,添加的惟一链接就是整个列表;没有链接添加到
像这样硬编码一个
使用
Resources<Resource<Taco>> recentResources = Resources.wrap(tacos);
recentResources.add(
ControllerLinkBuilder.linkTo(DesignTacoController.class)
.slash("recent")
.withRel("recents"));
不仅不再需要硬编码主机名,还不必指定 /design
路径。相反,需要一个指向/design
。
接下来是对任何/
和给定的值,因此,/design/recent
。
最后,为链接指定一个关系名。在本例中,关系被命名为
尽管我非常喜欢
Resources<Resource<Taco>> recentResources = Resources.wrap(tacos);
recentResources.add(
linkTo(methodOn(DesignTacoController.class).recentTacos())
.withRel("recents"));
这里我决定静态地引用
6.2.2 创建资源装配器
现在需要向列表中包含的
我们需要一个不同的策略。
将定义一个实用工具类,将
package tacos.web.api;
import java.util.Date;
import java.util.List;
import org.springframework.hateoas.ResourceSupport;
import lombok.Getter;
import tacos.Ingredient;
import tacos.Taco;
public class TacoResource extends ResourceSupport {
@Getter
private final String name;
@Getter
private final Date createdAt;
@Getter
private final List<Ingredient> ingredients;
public TacoResource(Taco taco) {
this.name = taco.getName();
this.createdAt = taco.getCreatedAt();
this.ingredients = taco.getIngredients();
}
}
在很多方面,
另外,
注意:
域和资源:分开还是放一起?一些
Spring 开发人员可能会选择通过扩展他们的域类型ResourceSupport ,来将他们的域类型和资源类型组合成单个类型,正确的方法没有对错之分。我选择创建一个单独的资源类型,这样Taco 就不会在不需要链接的情况下不必要地与资源链接混杂在一起。另外,通过创建一个单独的资源类型,我可以很容易地去掉id 属性,这样就不会在API 中暴露它。
为了帮助将
package tacos.web.api;
import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
import tacos.Taco;
public class TacoResourceAssembler extends ResourceAssemblerSupport<Taco, TacoResource> {
public TacoResourceAssembler() {
super(DesignTacoController.class, TacoResource.class);
}
@Override
protected TacoResource instantiateResource(Taco taco) {
return new TacoResource(taco);
}
@Override
public TacoResource toResource(Taco taco) {
return createResourceWithId(taco.getId(), taco);
}
}
重写
最后,
从表面上看,
现在调整
@GetMapping("/recent")
public Resources<TacoResource> recentTacos() {
PageRequest page = PageRequest.of(
0, 12, Sort.by("createdAt").descending());
List<Taco> tacos = tacoRepo.findAll(page).getContent();
List<TacoResource> tacoResources = new TacoResourceAssembler().toResources(tacos);
Resources<TacoResource> recentResources = new Resources<TacoResource>(tacoResources);
recentResources.add(
linkTo(methodOn(DesignTacoController.class).recentTacos())
.withRel("recents"));
return recentResources;
}
通过
此时,对 /design/recent
接口的
package tacos.web.api;
import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
import tacos.Ingredient;
class IngredientResourceAssembler extends
ResourceAssemblerSupport<Ingredient, IngredientResource> {
public IngredientResourceAssembler() {
super(IngredientController2.class, IngredientResource.class);
}
@Override
public IngredientResource toResource(Ingredient ingredient) {
return createResourceWithId(ingredient.getId(), ingredient);
}
@Override
protected IngredientResource instantiateResource(Ingredient ingredient) {
return new IngredientResource(ingredient);
}
}
如你所见,
说到
package tacos.web.api;
import org.springframework.hateoas.ResourceSupport;
import lombok.Getter;
import tacos.Ingredient;
import tacos.Ingredient.Type;
public class IngredientResource extends ResourceSupport {
@Getter
private String name;
@Getter
private Type type;
public IngredientResource(Ingredient ingredient) {
this.name = ingredient.getName();
this.type = ingredient.getType();
}
}
与
剩下要做的就是对
package tacos.web.api;
import java.util.Date;
import java.util.List;
import org.springframework.hateoas.ResourceSupport;
import lombok.Getter;
import tacos.Taco;
public class TacoResource extends ResourceSupport {
private static final IngredientResourceAssembler
ingredientAssembler = new IngredientResourceAssembler();
@Getter
private final String name;
@Getter
private final Date createdAt;
@Getter
private final List<IngredientResource> ingredients;
public TacoResource(Taco taco) {
this.name = taco.getName();
this.createdAt = taco.getCreatedAt();
this.ingredients = ingredientAssembler.toResources(taco.getIngredients());
}
}
这个新版本的
最近的
6.2.3 嵌套命名关系
如果仔细看看程序清单
{
"_embedded": {
"tacoResourceList": [
...
]
}
}
最值得注意的是
@Relation(value="taco", collectionRelation="tacos")
public class TacoResources extends ResourcesSupport {
...
}
在这里,已经指定当资源对象中使用
因此,从 /design/recent
返回的
{
"_embedded": {
"tacos": [
...
]
}
}
如果在存储库中使用