不要讨厌HATEOAS Part Deux:HATEOAS的春天
在我关于HATEOAS的系列文章的最后结论中,我们将深入探讨如何使用Spring-Data-REST和Spring-HATEOAS实现HATEOAS。 HATEOAS的春天到了!
我整理了一个有效的项目,该项目将演示下面的代码示例以及其他一些功能。 该项目可以在这里找到: https : //github.com/in-the-keyhole/hateoas-demo-II 。 需要JDK 8和Maven,否则不需要外部依赖项即可运行项目。
服务资源
通过其资源与Web服务交互是REST的核心设计约束之一。 使用Spring-Data和Spring-MVC ,开始提供资源并不是很困难。 您需要为要提供服务的实体添加一个Repository
,并实现一个控制器来为其提供服务。 但是, Spring-Data-REST使此过程变得更加容易,并在此过程中提供了更丰富的资源(即添加超媒体标记)。
@RepositoryRestResource
public interface ItemRepo extends CrudRepository<Item, Long> {
}
就这么简单。 如果您启动您的弹簧启动应用程序并导航到http://localhost:8080/items
(并已做了一些其他必要的配置 为 好 ),你应该得到的回报JSON看起来是这样的:
{"_embedded" : {"items" : [ {"name" : "Independence Day","description" : "Best. Movie. Speech. Ever!","price" : 10.0,"type" : "Movies","_links" : {"self" : {"href" : "http://localhost:8080/api/items/21"},"item" : {"href" : "http://localhost:8080/api/items/21"}}},...]},"_links" : {"self" : {"href" : "http://localhost:8080/items/"},"profile" : {"href" : "http://localhost:8080/profile/items"},"search" : {"href" : "http://localhost:8080/items/search"}}
}
除了易于演示的GET
功能外,Spring-Data-REST还增加了PUT
(出于某种原因决定将PUT
用于创建和更新的原因,Spring-Data-REST)的功能,并DELETE
资源以及检索资源的ID。 仅两行代码就具有很多功能!
分页和排序
资源通常会有很多记录。 通常,由于各个级别的资源成本很高,因此您不希望应要求返回所有这些记录。 分页是解决此问题的常用解决方案,而Spring-Data-REST使其易于实现。
另一个常见的需求是允许客户端对来自资源的返回进行排序的能力,在这里Spring-Data-REST还是可以解决的。 为了使用Item
资源实现此功能,我们需要从将CrudRepository
扩展为CrudRepository
, PagingAndSortingRepository
所示:
@RepositoryRestResource
public interface ItemRepo extends PagingAndSortingRepository<Item, Long> {
}
重新启动应用程序并返回http://localhost:8080/items
,返回的内容最初看起来是相同的,但是在页面底部附近,我们看到了一些新的JSON对象:
{... "_links" : {"first" : {"href" : "http://localhost:8080/items?page=0&size=20"},"self" : {"href" : "http://localhost:8080/items"},"next" : {"href" : "http://localhost:8080/items?page=1&size=20"},"last" : {"href" : "http://localhost:8080/items?page=1&size=20"},"profile" : {"href" : "http://localhost:8080/profile/items"},"search" : {"href" : "http://localhost:8080/items/search"}},"page" : {"size" : 20,"totalElements" : 23,"totalPages" : 2,"number" : 0}
}
Spring-Data-REST提供了用于浏览资源返回页面的超媒体控件。 last,next,prev和first(如果适用)(注意:Spring-Data-REST使用基于0的数组进行分页)。 如果仔细观察,您还将注意到Spring-Data-REST如何允许客户端操纵每页的返回数( .../items?size=x
)。 最后,还添加了排序,并可以使用URL参数: .../items?sort=name&name.dir=desc
。
搜索资源
因此,我们正在提供资源,对退货进行分页,并允许客户对这些退货进行排序。 这些都是非常有用的,但是客户端通常会希望搜索资源的特定子集。 这是Spring-Data-REST使极其简单的另一项任务。
@RepositoryRestResource
public interface ItemRepo extends PagingAndSortingRepository<Item, Long> {List<Item> findByType(@Param("type") String type);@RestResource(path = "byMaxPrice")@Query("SELECT i FROM Item i WHERE i.price <= :maxPrice")List<Item> findItemsLessThan(@Param("maxPrice") double maxPrice);@RestResource(path = "byMaxPriceAndType")@Query("SELECT i FROM Item i WHERE i.price <= :maxPrice AND i.type = :type")List<Item> findItemsLessThanAndType(@Param("maxPrice") double maxPrice, @Param("type") String type);
}
上面是一些用户可能希望通过以下方式搜索商品的查询:商品的类型,商品的最高价格,然后将这两个参数组合在一起。 导航到http://localhost:8080/items/search
,Spring-Data-REST呈现所有可用的搜索选项以及如何与它们交互。 与搜索端点交互时,也会启用根资源端点处可用的分页和排序功能!
..."findItemsLessThan" : {"href" : "http://localhost:8080/items/search/byMaxPrice{?maxPrice}","templated" : true},"findByType" : {"href" : "http://localhost:8080/items/search/findByType{?type}","templated" : true},"findItemsLessThanAndType" : {"href" : "http://localhost:8080/items/search/byMaxPriceAndType{?maxPrice,type}","templated" : true},
...
改变资源的形状
有时候改变端点服务的实体的形状是有益的。 您可能需要展平对象树,隐藏字段或更改字段名称以维护合同。 Spring-Data-REST提供了使用投影来操纵形状的功能。
首先,我们需要创建一个接口并使用@Projection
对其进行注释:
@Projection(name = "itemSummary", types = { Item.class })
public interface ItemSummary {String getName();String getPrice();
}
这将允许Spring-Data-REST根据请求以ItemSummary
形状提供我们的Item实体: http://localhost:8080/api/items/1?projection=itemSummary
。 如果我们想使ItemSummary
默认的形状,我们击中时返回/items
端点可以通过添加来完成excerptProjectio
n向@RepositoryRestResource
上标注ItemRepo
。
@RepositoryRestResource(excerptProjection = ItemSummary.class)
public interface ItemRepo extends PagingAndSortingRepository<Item, Long> {
现在,当我们点击../items
,我们的回报看起来像这样:
...
{"name" : "Sony 55 TV","price" : "1350.0","_links" : {"self" : {"href" : "http://localhost:8080/api/items/2"},"item" : {"href" : "http://localhost:8080/api/items/2{?projection}","templated" : true}}
}
...
自定义资源的端点
实体的名称可能不一定总是作为资源端点的名称。 它可能不符合遗留需求,您可能需要在资源的终结点之前加上前缀,或者只是想要一个不同的名称。 Spring-Data-REST提供了满足所有这些需求的钩子。
更改资源名称:
@RepositoryRestResource(collectionResourceRel = "merchandise", path = "merchandise")
public interface ItemRepo extends PagingAndSortingRepository<Item, Long> {
}
并添加基本路径:
@Configuration
public class RestConfiguration extends RepositoryRestConfigurerAdapter {@Overridepublic void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {config.setBasePath("api");}}
现在,将不再是通过../api/merchandise
来提供Item实体,而是通过../items
来提供../api/merchandise
。
保护资源
安全是一个非常重要和复杂的主题。 即使是整个谈话也几乎没有触及表面。 因此,将此部分视为对这个主题的轻微磨损。
隐藏场
如上一节所述,投影是隐藏字段的一种方法。 另一种更安全的方法是在如下所示的字段上使用@JsonIgnore
以防止其返回:
public class Item implements Serializable, Identifiable<Long> {@JsonIgnore@Column(name = "secret_field")private String secretField;
}
限制通过HTTP的访问
在某些情况下,无论您是谁,都根本无法通过HTTP访问功能。 这可以通过@RestResource(exported = false)
来实现,它告诉Spring-Data-REST完全不将该资源或资源的一部分发布到Web上。 可以在“类型”和“方法”级别上进行设置。 如果要广泛拒绝,然后显式定义应访问的内容,则在方法级别也可以覆盖类型级别。
方法级别:
public interface OrderRepo extends CrudRepository<Order, Long> {@Override@RestResource(exported = false)<S extends Order> S save(S entity);
}
类型级别,具有方法级别覆盖:
@RestResource(exported = false)
public interface OrderRepo extends CrudRepository<Order, Long> {@Override@RestResource(exported = true)<S extends Order> S save(S entity);
}
另一种方法(如果您愿意)是扩展Repository接口,而仅定义您希望客户端可以访问的方法。
public interface PaymentRepo extends Repository<Payment, Long> {Payment findOne(Long id);<S extends Payment> S save(S entity);
}
通过角色限制访问
您可能还希望将功能限制为仅某些类型的用户。
@RepositoryRestResource(collectionResourceRel = "merchandise", path = "merchandise")
public interface ItemRepo extends PagingAndSortingRepository<Item, Long> {@PreAuthorize("hasRole('ADMIN')")<S extends Item> S save(S entity);@PreAuthorize("hasRole('ADMIN')")<S extends Item> Iterable<S> save(Iterable<S> entities);
}
虽然我认为并不是严格要求的,但是由于可能与Spring-MVC过滤器进行了一些时髦的交互,因此需要一些其他的URL配置才能使基于角色的安全性发挥作用。 (我花了很多时间研究这个问题。)但是,无论如何,实现多层安全性通常是一个好习惯,因此,这也不一定是错误的:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {@Override@Autowiredpublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN")//.and().withUser("user").password("password").roles("USER");}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().antMatcher("/merchandise").authorizeRequests().antMatchers(HttpMethod.POST).hasAnyRole("ADMIN")//.and().antMatcher("/merchandise").authorizeRequests().antMatchers(HttpMethod.PUT).hasAnyRole("ADMIN")//.and().antMatcher("/**").authorizeRequests().antMatchers(HttpMethod.DELETE).denyAll()//.and().antMatcher("/merchandise").authorizeRequests().antMatchers(HttpMethod.GET).permitAll()//.and().antMatcher("/**").authorizeRequests().anyRequest().authenticated().and().httpBasic();}
}
像@RestResource
一样, @PreAuthorize
也可以放在类型级别,并在方法级别覆盖。
@PreAuthorize("hasRole('USER')")
public interface OrderRepo extends CrudRepository<Order, Long> {
}
Spring-HATEOAS的附加定制
到目前为止,我已经演示了Spring-Data-REST的所有功能,以及如何使HATEOAS服务的实现变得轻而易举。 las,使用Spring-Data-REST可以做的事情有局限性。 幸运的是,还有另一个Spring项目Spring-HATEOAS可以从那里开始。
Spring-HATEOAS简化了向资源添加超媒体标记的过程,对于处理资源之间的自定义交互非常有用。 例如,将商品添加到订单中:
@RequestMapping("/{id}")
public ResponseEntity<Resource<Item>> viewItem(@PathVariable String id) {Item item = itemRepo.findOne(Long.valueOf(id));Resource<Item> resource = new Resource<Item>(item);if (hasExistingOrder()) {// Provide a link to an existing Orderresource.add(entityLinks.linkToSingleResource(retrieveExistingOrder()).withRel("addToCart"));} else {// Provide a link to create a new Orderresource.add(entityLinks.linkFor(Order.class).withRel("addToCart"));}resource.add(entityLinks.linkToSingleResource(item).withSelfRel());return ResponseEntity.ok(resource);
}
这样,我们就覆盖了Spring-Data-REST提供的默认/merchandise/(id)
功能,现在将返回以下结果:
{"name" : "Samsung 55 TV","description" : "Samsung 55 LCD HD TV","price" : 1500.0,"type" : "Electronics","_links" : {"addToCart" : {"href" : "http://localhost:8080/api/orders"},"self" : {"href" : "http://localhost:8080/api/merchandise/1{?projection}","templated" : true}}
}
因此,我们的客户代码现在可以呈现一个链接,使用户可以轻松地将商品添加到他们的购物车或创建新的购物车并向其中添加商品。
结论
HATEOAS是REST规范中经常被忽略的部分,主要是因为实现和维护它会非常耗时。 Spring-Data-REST和Spring-HATEOAS大大减少了实现时间和维护时间,这使得HATEOAS在RESTful服务中实现更加实用。
我只能接触到Spring-Data-REST和Spring-HATEOAS必须提供的某些功能。 有关它们各自功能集的完整说明,建议您查看下面链接的参考文档。 如果您有任何疑问或需要进一步说明,请随时在下面的评论部分中提问。
其他资源
- http://docs.spring.io/spring-data/rest/docs/2.5.1.RELEASE/reference/html/
- http://docs.spring.io/spring-hateoas/docs/0.19.0.RELEASE/reference/html/
翻译自: https://www.javacodegeeks.com/2016/05/dont-hate-hateoas-part-deux-springtime-hateoas.html
不要讨厌HATEOAS Part Deux:HATEOAS的春天相关推荐
- RESTful服务的第三部分:HATEOAS和Richardson成熟度模型
by Sanchit Gera 通过Sanchit Gera RESTful服务的第三部分:HATEOAS和Richardson成熟度模型 (RESTful Services Part III : H ...
- 架构之:REST和HATEOAS
文章目录 简介 HATEOAS简介 HATEOAS的格式 HATEOAS的Spring支持 总结 简介 我们知道REST是一种架构方式,它只是指定了六种需要遵循的基本原则,但是它指定的原则都比较宽泛, ...
- Spring Boot+HATEOAS快速介绍与示例
HATEOAS 是什么 HATEOAS , 全称是Hypermedia as the engine of application state , 翻译一下是:超媒体作为应用程序状态引擎, 也翻译成超媒 ...
- SpringBoot之:SpringBoot中使用HATEOAS
HATEOAS是实现REST规范的一种原则,通过遵循HATEOAS规范,可以解决我们实际代码实现的各种个问题.作为java最流行的框架Spring 当然也会不缺席HATEOAS的集成. 本文将会通过一 ...
- SpringBoot 中使用 HATEOAS
简介 HATEOAS 是实现 REST 规范的一种原则,通过遵循 HATEOAS 规范,可以解决我们实际代码实现的各种个问题.作为 java 最流行的框架 Spring 当然也会不缺席 HATEOAS ...
- 理工男生资源达人的梦想,树莓派低成本创造私人专属互联网的构想及实现
title: 理工男生资源达人的梦想,树莓派低成本创造私人专属互联网 tags: 离线互联网 资源达人 categories: 树莓派 前两天看到一个名为Internet in a box(盒子里的离 ...
- SpringBoot:常用属性汇总
2019独角兽企业重金招聘Python工程师标准>>> 可以在application.properties/application.yml文件中或作为命令行开关指定各种属性.本节提供 ...
- springboot的自动配置原理
SpringBoot中的默认配置 通过刚才的学习,我们知道@EnableAutoConfiguration会开启SpringBoot的自动配置,并且根据你引入的依赖来生效对应的默认配置.那么问题来了: ...
- spring boot application.properties 属性详解
2019年3月21日17:09:59 英文原版: https://docs.spring.io/spring-boot/docs/current/reference/html/common-appli ...
最新文章
- python dlib实现面部标志识别
- Mob之社会化分享集成ShareSDK
- relink 11gR2 数据库
- linux配置nginx虚拟目录
- python应该怎么自学-新手该如何学python怎么学好python?
- 树莓派-语音聊天机器人+语音控制引脚电平高低
- cesium three性能比较_mapboxgl + three 动画 — 网格热图
- 学习方法书籍 很好推荐
- python如何调用阿里云接口_Python调用aliyun API设置阿里云负载均衡虚拟服务器组权重...
- web html介绍笔记,WEB 之 HTML 系列笔记
- 手机如何访问电脑局域网文件共享服务器,数据共享 手机怎么访问电脑文件?多个设备之间数据共享...
- bar图设置距离 python_Python可视化分析:学会Matplotlib这几点就够了
- vue-cli history 本地开发刷新页面丢失_react 开发入门
- centos时间同步
- python气象绘图速成_气象数据可视化——利用Python绘制温度平流
- linux zip压缩比1000,linux下压缩工具总结与使用(参考私房菜)
- 73种网页常用js代码
- 吉林大学线性代数知识点及解题方法
- EXCEL基本操作技巧
- R语言进行Box-Cox变换
热门文章
- Java 调用EXE
- Mybatis+MySQL动态分页查询数据经典案例
- 使用JDBCTemplate实现与Spring结合,方法公用 ——接口(BaseDao)
- java实现动态验证码源代码——jsp页面
- 用赫夫曼树进行文件的压缩
- 集合打印出来的信息不是输入的信息
- Android 获取屏幕宽度和高度直接转换为DP
- JS (intermediate value).Format is not a function问题解决
- thinking-in-java(20)注解
- kafka mirror_SSL Kafka经纪人从Kafka Mirror Maker迁移到Brooklin的挑战