最近在阅读Spring实战第五版中文版,书中第6章关于Spring HATEOAS部分代码使用的是Spring HATEOAS 0.25的版本,而最新的Spring HATEOAS 1.0对旧版的API做了升级,导致在使用新版Spring Boot(截至文章发布日最新的Spring Boot版本为2.2.4)加载的Spring HATEOAS 1.0.3无法正常运行书中代码,所以我决定在此对书中的代码进行迁移升级。

在线阅读书中这一部分:https://potoyang.gitbook.io/spring-in-action-v5/di-6-zhang-chuang-jian-rest-fu-wu/6.2-qi-yong-chao-mei-ti

Spring HATEOAS 1.0 版本的变化

封装结构的最大变化是通过引入超媒体类型注册API来实现的,以支持Spring HATEOAS中的其他媒体类型。这导致客户端API和服务器API(分别命名的包)以及包中的媒体类型实现的明确分离 mediatype

最大的变化就是将原来的资源表示为模型,具体变化如下。

ResourceSupportResourceResourcesPagedResources组类从来没有真正感受到适当命名。毕竟,这些类型实际上并不表示资源,而是表示模型,可以通过超媒体信息和提供的内容来丰富它们。这是新名称映射到旧名称的方式:

  • ResourceSupport 就是现在 RepresentationModel

  • Resource 就是现在 EntityModel

  • Resources 就是现在 CollectionModel

  • PagedResources 就是现在 PagedModel

因此,ResourceAssembler已被重命名为RepresentationModelAssembler和及其方法toResource(…),并分别toResources(…)被重命名为toModel(…)toCollectionModel(…)。名称更改也反映在中包含的类中TypeReferences

  • RepresentationModel.getLinks()现在公开了一个Links实例(通过List<Link>),该实例公开了其他API,以Links使用各种策略来连接和合并不同的实例。同样,它已经变成了自绑定的泛型类型,以允许向实例添加链接的方法返回实例本身。

  • LinkDiscovererAPI已移动到client包。

  • LinkBuilderEntityLinksAPI已经被移到了server包。

  • ControllerLinkBuilder已移入server.mvc,不推荐使用替换WebMvcLinkBuilder

  • RelProvider已重命名为LinkRelationProvider并返回LinkRelation实例,而不是String

  • VndError已移至mediatype.vnderror套件。

另外注意 ResourceProcessor 接口被 RepresentationModelProcessor 取代

更多变化请参考Spring HATEOAS文档:https://spring.io/projects/spring-hateoas

代码迁移升级

书中程序清单6.4 为资源添加超链接

    @GetMapping("/recent")
public CollectionModel<EntityModel<Taco>> recentTacos() {
PageRequest page = PageRequest.of(
0, 12, Sort.by("createdAt").descending()); List<Taco> tacos = tacoRepo.findAll(page).getContent();
CollectionModel<EntityModel<Taco>> recentResources = CollectionModel.wrap(tacos); recentResources.add(
new Link("http://localhost:8080/design/recent", "recents"));
return recentResources;
}

消除URL硬编码

    @GetMapping("/recent")
public CollectionModel<EntityModel<Taco>> recentTacos() {
PageRequest page = PageRequest.of(
0, 12, Sort.by("createdAt").descending()); List<Taco> tacos = tacoRepo.findAll(page).getContent();
CollectionModel<EntityModel<Taco>> recentResources = CollectionModel.wrap(tacos); recentResources.add(
linkTo(methodOn(DesignTacoController.class).recentTacos()).withRel("recents"));
return recentResources;
}
程序清单6.5 能够携带领域数据和超链接列表taco资源
public class TacoResource extends RepresentationModel<TacoResource> {

    @Getter
private String name; @Getter
private Date createdAt; @Getter
private List<Ingredient> ingredients; public TacoResource(Taco taco) {
this.name = taco.getName();
this.createdAt = taco.getCreatedAt();
this.ingredients = taco.getIngredients();
}
}
程序清单6.6 装配taco资源的资源装配器
public class TacoResourceAssembler extends RepresentationModelAssemblerSupport<Taco, TacoResource> {
/**
* Creates a new {@link RepresentationModelAssemblerSupport} using the given controller class and resource type.
*
* @param controllerClass DesignTacoController {@literal DesignTacoController}.
* @param resourceType TacoResource {@literal TacoResource}.
*/
public TacoResourceAssembler(Class<?> controllerClass, Class<TacoResource> resourceType) {
super(controllerClass, resourceType);
} @Override
protected TacoResource instantiateModel(Taco taco) {
return new TacoResource(taco);
} @Override
public TacoResource toModel(Taco entity) {
return createModelWithId(entity.getId(), entity);
}
}

之后对recentTacos()的调整

@GetMapping("/recentNew")
public CollectionModel<TacoResource> recentTacos() {
PageRequest page = PageRequest.of(
0, 12, Sort.by("createdAt").descending());
List<Taco> tacos = tacoRepo.findAll(page).getContent(); CollectionModel<TacoResource> tacoResources =
new TacoResourceAssembler(DesignTacoController.class, TacoResource.class).toCollectionModel(tacos); tacoResources.add(linkTo(methodOn(DesignTacoController.class)
.recentTacos())
.withRel("recents"));
return tacoResources;
}

创建 IngredientResource 对象

@Data
public class IngredientResource extends RepresentationModel<IngredientResource> {
public IngredientResource(Ingredient ingredient) {
this.name = ingredient.getName();
this.type = ingredient.getType();
} private final String name;
private final Ingredient.Type type;
}
IngredientResourceAssembler 对象
public class IngredientResourceAssembler extends RepresentationModelAssemblerSupport<Ingredient, IngredientResource> {
/**
* Creates a new {@link RepresentationModelAssemblerSupport} using the given controller class and resource type.
*
* @param controllerClass IngredientController {@literal IngredientController}.
* @param resourceType IngredientResource {@literal IngredientResource}.
*/
public IngredientResourceAssembler(Class<?> controllerClass, Class<IngredientResource> resourceType) {
super(controllerClass, resourceType);
} @Override
protected IngredientResource instantiateModel(Ingredient entity) {
return new IngredientResource(entity);
} @Override
public IngredientResource toModel(Ingredient entity) {
return createModelWithId(entity.getId(), entity);
}
}

对 TacoResource 对象的修改

public class TacoResource extends RepresentationModel<TacoResource> {
private static final IngredientResourceAssembler
ingredientAssembler = new IngredientResourceAssembler(IngredientController.class, IngredientResource.class); @Getter
private String name; @Getter
private Date createdAt; @Getter
private CollectionModel<IngredientResource> ingredients; public TacoResource(Taco taco) {
this.name = taco.getName();
this.createdAt = taco.getCreatedAt();
this.ingredients = ingredientAssembler.toCollectionModel(taco.getIngredients()); }
}

程序清单6.7

@RepositoryRestController
public class RecentTacosController {
private TacoRepository tacoRepo; public RecentTacosController(TacoRepository tacoRepo) {
this.tacoRepo = tacoRepo;
} /**
* 虽然@GetMapping映射到了“/tacos/recent”路径,但是类级别的@Repository RestController注解会确保这个路径添加
* Spring Data REST的基础路径作为前缀。按照我们的配置,recentTacos()方法将会处理针对“/api/tacos/recent”的GET请求。
* */
@GetMapping(path="/tacos/recent", produces="application/hal+json")
public ResponseEntity<CollectionModel<TacoResource>> recentTacos() {
PageRequest page = PageRequest.of(
0, 12, Sort.by("createdAt").descending());
List<Taco> tacos = tacoRepo.findAll(page).getContent(); CollectionModel<TacoResource> tacoResources =
new TacoResourceAssembler(DesignTacoController.class, TacoResource.class).toCollectionModel(tacos); tacoResources.add(
linkTo(methodOn(RecentTacosController.class).recentTacos())
.withRel("recents"));
return new ResponseEntity<>(tacoResources, HttpStatus.OK);
} }
程序清单6.8 为Spring Data REST端点添加自定义的链接
    @Bean
public RepresentationModelProcessor<PagedModel<EntityModel<Taco>>> tacoProcessor(EntityLinks links) { return new RepresentationModelProcessor<PagedModel<EntityModel<Taco>>>() {
@Override
public PagedModel<EntityModel<Taco>> process(PagedModel<EntityModel<Taco>> resource) {
resource.add(
links.linkFor(Taco.class)
.slash("recent")
.withRel("recents"));
return resource;
}
};
}

另一种写法

如果你觉得写使用资源装配器有点麻烦,那么你还可以采用这种方法。

    @GetMapping("/employees")
public ResponseEntity<CollectionModel<EntityModel<Taco>>> findAll() {
PageRequest page = PageRequest.of(
0, 12, Sort.by("createdAt").descending());
List<EntityModel<Taco>> employees = StreamSupport.stream(tacoRepo.findAll(page).spliterator(), false)
.map(employee -> new EntityModel<>(employee,
linkTo(methodOn(DesignTacoController.class).findOne(employee.getId())).withSelfRel(),
linkTo(methodOn(DesignTacoController.class).findAll()).withRel("employees")))
.collect(Collectors.toList()); return ResponseEntity.ok(
new CollectionModel<>(employees,
linkTo(methodOn(DesignTacoController.class).findAll()).withSelfRel()));
}
@GetMapping("/employees/{id}")
public ResponseEntity<EntityModel<Taco>> findOne(@PathVariable long id) { return tacoRepo.findById(id)
.map(employee -> new EntityModel<>(employee,
linkTo(methodOn(DesignTacoController.class).findOne(employee.getId())).withSelfRel(), //
linkTo(methodOn(DesignTacoController.class).findAll()).withRel("employees"))) //
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}

参考来源:https://github.com/spring-projects/spring-hateoas-examples/tree/master/simplified

END

将Spring实战第5版中Spring HATEOAS部分代码迁移到Spring HATEOAS 1.0的更多相关文章

  1. 【spring实战第五版遇到的坑】第14章spring.cloud.config.uri和token配置项无效

    本文使用的Spring Boot版本为:2.1.4.RELEASE Spring Cloud版本为:Greenwich.SR1 按照书上的做法,在application.yml中配置配置服务器的地址和 ...

  2. Spring实战(第4版).pdf - 百度云资源

    http://www.supan.vip/spring%E5%AE%9E%E6%88%98 Spring实战(第4版).pdf 关于本书 Spring框架是以简化Java EE应用程序的开发为目标而创 ...

  3. Spring实战第4版PDF下载含源码

    下载链接 扫描右侧公告中二维码,回复[spring实战]即可获取所有链接. 读者评价 看了一半后在做评论,物流速度挺快,正版行货,只是运输过程有点印记,但是想必大家和你关注内容,spring 4必之3 ...

  4. Spring in action(Spring实战) 第四版中文翻译

    第一部分 Spring核心 Spring提供了非常多功能,可是全部这些功能的基础是是依赖注入(DI)和面向方面编程(AOP). 第一章 Springing into action 本章包含: Spri ...

  5. Spring 实战 第4版 读书笔记

    第一部分:Spring的核心 1.第一章:Spring之旅 1.1.简化Java开发 创建Spring的主要目的是用来替代更加重量级的企业级Java技术,尤其是EJB.相对EJB来说,Spring提供 ...

  6. 【spring实战第五版遇到的坑】4.2.3中LDAP内嵌服务器不启动的问题

    按照4.2.3中的指导一步一步的去做,在登录界面进行登录时,报错了,报错信息是LDAP服务器连接不上. 后来查了一些资源发现还需要加入一些其他的依赖,如下: <dependency> &l ...

  7. 【spring实战第五版遇到的坑】3.2中配置关系映射时,表名和3.1中不一样

    3.2章中按照书中的步骤写好相应类的映射关系,发现启动时,之前在3.1章中建的表全部被删重新建立了,并且Ingredient表的数据没了,由于使用了JPA,默认使用的是hibernate,在启动时会删 ...

  8. 【spring实战第五版遇到的坑】3.1中的例子报错

    按照书中的例子,一直做到第3.1章使用JDBC读写数据时,在提交设计的taco表单时,报了如下的异常信息: Failed to convert property value of type java. ...

  9. Spring实战 (第3版)——AOP

    在软件开发中,分布于应用中多处的功能被称为横切关注点.通常,这些横切关注点从概念上是与应用的 业务逻辑相分离的(但是往往直接嵌入到应用的业务逻辑之中).将这些横切关注点与业务逻辑相分离正是 面向切面编 ...

随机推荐

  1. springmvc接收json数据的常见方式

    经常使用Ajax异步请求来进行数据传输,传的数据是json数据,json数据又有对象,数组.所有总结下springmvc获取前端传来的json数据方式:1.以RequestParam接收前端传来的是j ...

  2. C# 把文件夹下所有文件添加到集合中

    private List<string> FindFile(string Path) { List<string> list=new List<string>(); ...

  3. 正则表达式grep命令

    grep命令 作用:文本搜索工具,根据用户指定的“模式”对目标文本逐行进行匹配检查:打印匹配到的行. 模式::由正则表达式字符及文本字符所编写的过滤条件 语法:grep [OPTIONS] PATTE ...

  4. @Controller和@RestController

    @RestController=@Controller+@ResponseBody 1.使用RestController时,返回到前端的内容是Return里的内容,无法返回jsp/html等页面, 此 ...

  5. (树形DP)Strategic game POJ - 1463

    题意: 给你一棵树,树的每一个节点可以守护与其相连的所有边,问你最少用多少个节点可以守护这整棵树 思路: 仔细思考不难发现,要想守护一条边,边的两个端点必须有一个可以被选(两个都选也可以),然后这个问 ...

  6. (分块)楼房重建 HYSBZ - 2957

    题意 长度为n的坐标轴上,从1-n上的每一点都有一栋楼房,楼房的初识高度都为0,每一天都有一栋楼房的高度被修改(也可以不变),一栋楼房能被看见当且仅当其最高点与远点的连线不会与其他之前连线相交,问你每 ...

  7. php变量中两种特殊类型

    第一种----资源 资源(resource):资源是由专门的函数来建立和使用的,例如打开文件.数据连接.图形画布.我们可以对资源进行操作(创建.使用和释放).任何资源,在不需要的时候应该被及时释放.如 ...

  8. Windows Terminal入门

    目录 0.引言 1.简易安装 2.初识WT 3.初识Settings 3.1全局配置 3.2每一个终端配置 3.3配色方案 3.4键位绑定 4.连接云服务器 5.连接WSL 6.玩转Emoji 0.引 ...

  9. 【Java并发基础】Java线程的生命周期

    前言 线程是操作系统中的一个概念,支持多线程的语言都是对OS中的线程进行了封装.要学好线程,就要搞清除它的生命周期,也就是生命周期各个节点的状态转换机制.不同的开发语言对操作系统中的线程进行了不同的封 ...

  10. Python学习,第三课 - 数据类型

    前言. 本次针对Python中的数据类型,做详细的总结 1.数字 2 是一个整数的例子. 长整数 不过是大一些的整数. 3.23和52.3E-4是浮点数的例子.E标记表示10的幂.在这里,52.3E- ...