JAX-RS和OpenAPI对Hypermedia API的支持:任重而道远

或早或晚,大多数积极使用REST(ful) Web服务和API的开发人员都偶然发现了这种真正的外星事物,即HATEOAS : 超文本作为应用程序状态的引擎 。 对HATEOAS是什么以及它与REST的关系的好奇最终将导致发现Richardson成熟度模型 ,该模型使REST和RESTful的行业定义神秘化。 后者是一个启发,但提出了一个问题:这些年来,我们是否一直在错误地进行REST ?

让我们尝试从不同的角度回答这个问题。 HATEOAS是REST核心架构约束之一。 从这个角度来看,答案是“是”,为了声称符合REST ,Web服务或API应该支持它。 但是,如果您四处看看(甚至参考您过去或现在的经验),您可能会发现大多数Web服务和API只是域模型周围的CRUD包装器,而没有HATEOAS支持。 这是为什么? 可能有多个原因,但是从开发人员的工具箱角度来看, HATEOAS的支持不是那么好。

在今天的帖子中,我们将讨论有关HATEOAS的 JAX-RS 2.x必须提供的内容 ,如何从服务器和客户端的角度使用它以及如何增强OpenAPI v3.0.x规范以暴露超媒体。作为合同的一部分。 如果您很兴奋,请让我们开始吧。

因此,我们的JAX-RS Web API将围绕管理公司及其员工而构建。 基础是Spring Boot和Apache CXF ,其中Swagger是OpenAPI规范的实现。 AppConfig是我们需要定义的唯一配置,以启动和运行应用程序(这要归功于Spring Boot的自动配置功能)。

 @SpringBootConfiguration  public class AppConfig { @Bean OpenApiFeature createOpenApiFeature() { final OpenApiFeature openApiFeature = new OpenApiFeature(); openApiFeature.setSwaggerUiConfig( new SwaggerUiConfig().url( "/api/openapi.json" )); return openApiFeature; }     @Bean JacksonJsonProvider jacksonJsonProvider() { return new JacksonJsonProvider(); }  } 

CompanyPerson这个模型非常简单(请注意,这两个类之间没有直接关系)。

 public class Company { private String id; private String name;  }  public class Person { private String id; private String email; private String firstName; private String lastName;  } 

该模型通过CompanyResource公开, CompanyResource是典型的JAX-RS资源类,带有@Path注释,此外还带有OpenAPI的@Tag注释。

 @Component  @Path ( "/companies" )  @Tag (name = "companies" )  public class CompanyResource { @Autowired private CompanyService service;  } 

很好,资源类尚未定义端点,因此让我们加强一下。 我们的第一个端点将通过标识符查找公司,并以JSON格式返回其表示形式。 但是,由于我们没有包含任何与员工相关的细节,因此提示消费者(Web UI或任何其他客户端)在哪里查找真是太棒了。 有多种方法可以执行此操作,但是由于我们坚持使用JAX-RS ,因此可以使用开箱即用的Web链接 ( RFC-5988 )。 该代码段包含数千个单词。

 @Produces (MediaType.APPLICATION_JSON)  @GET  @Path ( "{id}" )  public Response getCompanyById( @Context UriInfo uriInfo, @PathParam ( "id" ) String id) { return service .findCompanyById(id) .map(company -> Response .ok(company) .links( Link.fromUriBuilder(uriInfo .getRequestUriBuilder()) .rel( "self" ) .build(), Link.fromUriBuilder(uriInfo .getBaseUriBuilder() .path(CompanyResource. class )) .rel( "collection" ) .build(), Link.fromUriBuilder(uriInfo .getBaseUriBuilder() .path(CompanyResource. class ) .path(CompanyResource. class , "getStaff" )) .rel( "staff" ) .build(id) ) .build()) .orElseThrow(() -> new NotFoundException( "The company with id '" + id + "' does not exists" ));  } 

这里几乎没有发生任何事情。 我们关心的是使用ResponseBuilder :: links方法,其中提供了三个链接。 第一个是self ,它本质上是链接上下文(定义为RFC-5988的一部分)。 第二个是collection ,它指向CompanyResource端点,该端点返回公司列表(也包含在标准关系注册表中)。 最后,第三个是我们自己的员工关系,我们通过一个名为getStaff的方法实现的另一CompanyResource端点组装(我们将看到它不久)。 这些链接将在“ 链接”响应标头中传递,并指导客户端下一步去向。 让我们通过运行该应用程序来实际查看它。

 $ mvn clean package  $ java -jar target/jax-rs- 2.1 -hateaos- 0.0 . 1 -SNAPSHOT.jar 

然后使用curl检查来自此资源端点的响应(不必要的详细信息已被滤除)。

 $ curl -v http: //localhost:8080/api/companies/1  > GET /api/companies/ 1 HTTP/ 1.1  > Host: localhost: 8080  > User-Agent: curl/ 7.47 . 1  > Accept: */*  >  < HTTP/ 1.1 200  < Link: <http: //localhost:8080/api/companies/1>;rel="self"  < Link: <http: //localhost:8080/api/companies/1/staff>;rel="staff"  < Link: <http: //localhost:8080/api/companies>;rel="collection"  < Content-Type: application/json  < Transfer-Encoding: chunked  <  { "id" : "1" , "name" : "HATEOAS, Inc."  } 

链接头在那里,指的是其他感兴趣的端点。 从客户的角度来看,事情看起来也很简单。 Response类提供专用的getLinks方法来包装对Link响应标头的访问,例如:

 final Client client = ClientBuilder.newClient();  try ( final Response response = client .target( " http://localhost:8080/api/companies/ {id}" ) .resolveTemplate( "id" , "1" ) .request() .accept(MediaType.APPLICATION_JSON) .get()) {             final Optional staff = response .getLinks() .stream() .filter(link -> Objects.equals(link.getRel(), "staff" )) .findFirst();             staff.ifPresent(link -> { // follow the link here });  } finally { client.close();  } 

到目前为止,一切都很好。 展望未来,由于HATEOAS本质上是Web API合同的一部分,因此让我们在桌上找出OpenAPI规范所具有的内容。 不幸的是, 到目前为止 尚不支持 HATEOAS ,但是从好的方面来说,存在链接的概念(尽管不应将它们与Web链接混淆,它们有些相似,但并不相同)。 为了说明作为OpenAPI规范一部分的链接的用法,让我们用Swagger注释装饰端点。

 @Operation ( description = "Find Company by Id" , responses = { @ApiResponse ( content = @Content (schema = @Schema (implementation = Company. class )), links = { @io .swagger.v3.oas.annotations.links.Link( name = "self" , operationRef = "#/paths/~1companies~1{id}/get" , description = "Find Company" , parameters = @LinkParameter (name = "id" , expression = "$response.body#/id" ) ), @io .swagger.v3.oas.annotations.links.Link( name = "staff" , operationRef = "#/paths/~1companies~1{id}~1staff/get" , description = "Get Company Staff" , parameters = @LinkParameter (name = "id" , expression = "$response.body#/id" ) ), @io .swagger.v3.oas.annotations.links.Link( name = "collection" , operationRef = "#/paths/~1companies/get" , description = "List Companies" ) }, description = "Company details" , responseCode = "200" ), @ApiResponse ( description = "Company does not exist" , responseCode = "404" ) }  )  @Produces (MediaType.APPLICATION_JSON)  @GET  @Path ( "{id}" )  public Response getCompanyById( @Context UriInfo uriInfo, @PathParam ( "id" ) String id) { // ...  } 

如果我们运行该应用程序并浏览到浏览器中的http:// localhost:8080 / api / api-docs (这是Swagger UI的托管位置),我们将能够看到每个响应中的链接部分。

超媒体API

但是除此之外……您可以使用那里的链接做很多事情(如果您对该主题感兴趣,请注意此问题 )。 吸引公司员工的资源终点看起来非常相似。

 @Operation ( description = "Get Company Staff" , responses = { @ApiResponse ( content = @Content (array = @ArraySchema (schema = @Schema (implementation = Person. class ))), links = { @io .swagger.v3.oas.annotations.links.Link( name = "self" , operationRef = "#/paths/~1companies~1{id}~1staff/get" , description = "Staff" , parameters = @LinkParameter (name = "id" , expression = "$response.body#/id" ) ), @io .swagger.v3.oas.annotations.links.Link( name = "company" , operationRef = "#/paths/~1companies~1{id}/get" , description = "Company" , parameters = @LinkParameter (name = "id" , expression = "$response.body#/id" ) ) }, description = "The Staff of the Company" , responseCode = "200" ), @ApiResponse ( description = "Company does not exist" , responseCode = "404" ) }  )  @Produces (MediaType.APPLICATION_JSON)  @GET  @Path ( "{id}/staff" )  public Response getStaff( @Context UriInfo uriInfo, @PathParam ( "id" ) String id) { return service .findCompanyById(id) .map(c -> service.getStaff(c)) .map(staff -> Response .ok(staff) .links( Link.fromUriBuilder(uriInfo .getRequestUriBuilder()) .rel( "self" ) .build(), Link.fromUriBuilder(uriInfo .getBaseUriBuilder() .path(CompanyResource. class ) .path(id)) .rel( "company" ) .build() ) .build()) .orElseThrow(() -> new NotFoundException( "The company with id '" + id + "' does not exists" ));  } 

如您所料,除了指向self的链接之外,它还包括指向公司的链接。 当我们使用curl尝试时,预期的响应标头将返回。

 $ curl -v http: //localhost:8080/api/companies/1/staff  > GET /api/companies/ 1 /staff HTTP/ 1.1  > Host: localhost: 8080  > User-Agent: curl/ 7.47 . 1  > Accept: */*  >  < HTTP/ 1.1 200  < Link: <http: //localhost:8080/api/companies/1/staff>;rel="self"  < Link: <http: //localhost:8080/api/companies/1>;rel="company"  < Content-Type: application/json  < Transfer-Encoding: chunked  <  [ { "id" : "1" , "email" : "john@smith.com" , "firstName" : "John" , "lastName" : "Smith" }, { "id" : "2" , "email" : "bob@smith.com" , "firstName" : "Bob" , "lastName" : "Smith" }  ] 

那么我们可以得出什么样的结论呢? HATEOAS实际上通过动态地驱动对话来统一Web API提供者和使用者之间的交互模型。 这非常强大,但是其中的大多数框架和工具要么都对HATEOAS提供了相当基本的支持(例如Web Linking ),要么根本没有。

在很多情况下,只要使用Web链接就足够了(到目前为止,我们已经看到了示例,例如分页,导航等),但是假设创建,编辑或修补现有资源又如何呢? 如何用超媒体丰富集合中返回的各个元素(在RFC-6537中进行描述)? HATEOAS是否值得所有这些努力?

与往常一样,答案是“取决于”,也许我们应该超越JAX-RS ? 在下一篇文章中(s_,我们将继续解决问题。

完整的源代码可在Github上找到 。

翻译自: https://www.javacodegeeks.com/2019/02/hypermedia-apis-support-jax-rs-openapi.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/343565.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【渝粤教育】21秋期末考试大学英语210262k2

1、He has_______since three days ago.&#xff08;2 分&#xff09; A&#xff0e;gone away B&#xff0e;gone C&#xff0e;left D&#xff0e;been away 2、What an interesting book! I don’t want to .&#xff08;2 分&#xff09; A&#xff0e;give to it B&#x…

【渝粤教育】21秋期末考试建筑工程计量与计价10517k1

1、施工排水降水中成井的计量单位是&#xff08; &#xff09;&#xff08;1 分&#xff09; A&#xff0e;天 B&#xff0e;m C&#xff0e;台次 D&#xff0e;昼夜 2、按广东省18定额规定&#xff0c;天棚装饰高度超过&#xff08;&#xff09;&#xff0c;可计算满堂脚手架。…

工业以太网交换机的光口和电口有什么区别?

对于从事安防传输设备行业的朋友们来说&#xff0c;相信大家对工业以太网交换机应该都不陌生。客户购买工业以太网交换机时&#xff0c;很多客户都会要几光几电的交换机&#xff0c;最近也接到不少客户的电话&#xff0c;都会询问工业以太网交换机的电口和光口有什么区别&#…

【渝粤教育】21秋期末考试混凝土结构10515k1

21秋期末考试混凝土结构10515k1 1、钢筋混凝土轴心受压构件&#xff0c;稳定系数是为了考虑&#xff1a; &#xff08; &#xff09;。&#xff08;3 分&#xff09; A&#xff0e;附加偏心距的影响 B&#xff0e;附加弯矩的影响 C&#xff0e;剪力的影响 D&#xff0e;承载力的…

工业以太网交换机与网络交换机的区别

工业以太网交换机与商用交换机在数据交换功能上基本一致&#xff0c;但在设计上以及在元器件的选用上&#xff0c;产品的强度和适用性方面更能满足工业现场的需要。此外在模块扩展方面也表现的比商用交换机更为灵活&#xff1a;有多种光口和电口可供选配。在材质的选用、产品的…

【渝粤教育】国家开放大学2018年春季 0004-21T有机合成单元反应 参考试题

科目编号&#xff1a;0004 座位号&#xff1a; 2017-2018学年度第二学期期末考试 有机合成单元过程 2018年6月 一、选择题&#xff08;每小题3分&#xff0c;共45分) 1、分子结构中具有&#xff08; )官能团的有机化合物&#xff0c;通常称之为羧酸。 —NO2 B.—COOH C.—CH…

【渝粤教育】国家开放大学2018年春季 0014-22T秘书学(一) 参考试题

试卷编号&#xff1a;0014 座位号 2017—2018学年度第二学期期末考试 秘书学&#xff08;一&#xff09; 试 题 2018年7月 1&#xff0e;秘书环境 2. 公共关系3. 辅助决策4. 协调关系1&#xff0e;秘书事务的内容有&#xff08; &#xff09; A&#xff0e;接打电话 B.车辆管…

mysql 内存溢出_mysql - MySQL在非常大的表上计算性能 - 堆栈内存溢出

我在Innodb中有一个表格&#xff0c;其中有超过1亿行。我必须知道外键 1时是否有超过5000行。我不需要确切的数字。我做了一些测试&#xff1a;> 16 seconds > 16秒中> 16 seconds > 16秒中> 0.6 seconds > 0.6秒我的网络和治疗时间会更长一些&#xff0c;但…

【渝粤教育】国家开放大学2018年春季 0045-22T烹饪原料学(1) 参考试题

编号&#xff1a;0045 座位号 2017&#xff5e;2018学年度第二学期期末考试 烹饪原料学&#xff08;1&#xff09;试题 2018年5月 一、名词解释&#xff08;本大题共4小题&#xff0c;每题5分&#xff0c;共20分&#xff09;。 腌腊制品 鱼翅 调味原料 香辛料 二、单项选…

光纤交换机的配置 光纤交换机的由来是什么

光纤交换机是一种高速的网络传输中继设备&#xff0c;又叫做光纤通道交换机、SAN交换机&#xff0c;它较普通交换机而言&#xff0c;采用了光纤电缆作为传输介质。那么&#xff0c;什么是光纤交换机&#xff0c;光纤交换机的由来是什么&#xff0c;光纤交换机的配置有哪些呢&am…

Java Stream:第2部分,计数始终是计数吗?

在上一篇有关该主题的文章中 &#xff0c;我们了解到JDK 8 stream()::count需要更长的时间来执行Stream更多的元素。 对于较新的JDK&#xff08;例如Java 11&#xff09;&#xff0c;简单流管道不再是这种情况。 了解JDK本身如何进行改进。 Java 8 在上一篇文章中&#xff0c…

【渝粤教育】国家开放大学2018年春季 0062-21T港台文学专题讲座 参考试题

试卷编号&#xff1a;0062 座位号 2017——2018学年度第二学期期末考试 中国当代文学专题 试题&#xff08;开&#xff09; 2018年7月 什么叫“伤痕文学”&#xff1f;什么叫“反思文学”&#xff1f;什么叫“改革文学”&#xff1f;各说出两位作家及作品。1&#xff0e;说明…

mysql 横向分表合并_MySQL横向扩展-分库分表解决方案总结

从业务场景看分库分表互联网行业中&#xff0c;业务场景通常写少读多的情况居多&#xff0c;在MySQL的使用前期&#xff0c;读性能大多可以通过SQL优化来解决&#xff0c;但随着业务的持续发展&#xff0c;单纯依靠SQL的查询优化会越来越难以达到业务服务要求。因此&#xff0c…

【渝粤教育】国家开放大学2018年春季 0089-22DInternet和Intranet应用 参考试题

编号&#xff1a;0089 17-18学年第1学期期末考试 Internet和Intranet应用 试题答案 一、填空题&#xff1a;&#xff08;每空4分&#xff0c;共40分&#xff09; 1&#xff0e;网络接口层  网际网层  传输层  应用层 2&#xff0e;路由器(或网关)  目的主机 3&#xff…

家用、商用、工业交换机的用途与区别

以太网交换机一般分为&#xff1a;商用(以太网)交换机、工业(以太网)交换机、家用(以太网)交换机。那么&#xff0c;家用交换机&#xff0c;商业交换机&#xff0c;工业交换机之间有什么区别呢&#xff1f;接下来我们就跟随飞畅科技的小编一起来详细了解下吧&#xff01; 商用…

【渝粤教育】国家开放大学2018年春季 0105-22T酒店营销实务 参考试题

科目编号&#xff1a;0105 座位号 2017年度第二学期期末考试 酒店营销实务 试题 2018年7月 一、名词解释&#xff1a;&#xff08;每题5分&#xff0c;共20分&#xff09; 酒店广告&#xff1a; 酒店市场细分&#xff1a; 3&#xff0e;市场调查&#xff1a; 目标市场&…

【渝粤教育】国家开放大学2018年春季 0176-22T电机学(一) 参考试题

编号&#xff1a; 0176 2017-2018年度第二学期期末考试 电机学&#xff08;1&#xff09; 试 题 2018年 7 月 一、填空题&#xff08;每空4分&#xff0c;共40分&#xff09; 1&#xff0e;一台变压器&#xff0c;加额定电压时&#xff0c;主磁通为Φ&#xff0c;空载电流为I…

python中dump用法_python中json庫中的load、loads、dump、dumps的區別與用法

一、json.dumps(i)&#xff1a;json中的dumps方法是用來將特定格式的數據進行字符串化的操作&#xff0c;比如列表字典都可以進行字符串化操作然后寫入json的file&#xff1b;而且如果是要寫入json文件就必須要進行dumps操作&#xff1b;二、json.dump():和dumps差一個s&#x…

监控POE供电交换机最大传输距离有多远?

经常有朋友问到关于POE供电技术的问题&#xff0c;比如说POE供电最大传输距离是多远?其实&#xff0c;PoE最大传输距离的问题&#xff0c;先要弄清楚决定最大距离的关键因素是什么。事实上&#xff0c;用标准以太网线缆(双绞线)传输直流电是可以传输很远的&#xff0c;这个距离…

【渝粤教育】国家开放大学2018年春季 0233-21T学前儿童语言教育 参考试题

试卷编号&#xff1a;0233 2017——2018学年度第二学期期末考试 学前儿童语言教育 2018年7月 1.前语言阶段&#xff0c;儿童发展了三方面能力&#xff0c;即前语言 、前语言 和前语言 。 2. 和 &#xff0c;这是儿童学话中的关键的两步&#xff0c;因为语言基本的奥秘已开始渗…