日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2023/12/3 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JAX-RS和OpenAPI对Hypermedia API的支持:任重而道远 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

或早或晚,大多數(shù)積極使用REST(ful) Web服務(wù)和API的開發(fā)人員都偶然發(fā)現(xiàn)了這種真正的外星事物,即HATEOAS : 超文本作為應(yīng)用程序狀態(tài)的引擎 。 對(duì)HATEOAS是什么以及它與REST的關(guān)系的好奇最終將導(dǎo)致發(fā)現(xiàn)Richardson成熟度模型 ,該模型使REST和RESTful的行業(yè)定義神秘化。 后者是一個(gè)啟發(fā),但提出了一個(gè)問題:這些年來,我們是否一直在錯(cuò)誤地進(jìn)行REST ?

讓我們嘗試從不同的角度回答這個(gè)問題。 HATEOAS是REST核心架構(gòu)約束之一。 從這個(gè)角度來看,答案是“是”,為了聲稱符合REST ,Web服務(wù)或API應(yīng)該支持它。 但是,如果您四處看看(甚至參考您過去或現(xiàn)在的經(jīng)驗(yàn)),您可能會(huì)發(fā)現(xiàn)大多數(shù)Web服務(wù)和API只是域模型周圍的CRUD包裝器,而沒有HATEOAS支持。 這是為什么? 可能有多個(gè)原因,但是從開發(fā)人員的工具箱角度來看, HATEOAS的支持不是那么好。

在今天的帖子中,我們將討論有關(guān)HATEOAS的 JAX-RS 2.x必須提供的內(nèi)容 ,如何從服務(wù)器和客戶端的角度使用它以及如何增強(qiáng)OpenAPI v3.0.x規(guī)范以暴露超媒體。作為合同的一部分。 如果您很興奮,請(qǐng)讓我們開始吧。

因此,我們的JAX-RS Web API將圍繞管理公司及其員工而構(gòu)建。 基礎(chǔ)是Spring Boot和Apache CXF ,其中Swagger是OpenAPI規(guī)范的實(shí)現(xiàn)。 AppConfig是我們需要定義的唯一配置,以啟動(dòng)和運(yùn)行應(yīng)用程序(這要?dú)w功于Spring Boot的自動(dòng)配置功能)。

@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(); } }

Company和Person這個(gè)模型非常簡單(請(qǐng)注意,這兩個(gè)類之間沒有直接關(guān)系)。

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; }

很好,資源類尚未定義端點(diǎn),因此讓我們加強(qiáng)一下。 我們的第一個(gè)端點(diǎn)將通過標(biāo)識(shí)符查找公司,并以JSON格式返回其表示形式。 但是,由于我們沒有包含任何與員工相關(guān)的細(xì)節(jié),因此提示消費(fèi)者(Web UI或任何其他客戶端)在哪里查找真是太棒了。 有多種方法可以執(zhí)行此操作,但是由于我們堅(jiān)持使用JAX-RS ,因此可以使用開箱即用的Web鏈接 ( RFC-5988 )。 該代碼段包含數(shù)千個(gè)單詞。

@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" )); }

這里幾乎沒有發(fā)生任何事情。 我們關(guān)心的是使用ResponseBuilder :: links方法,其中提供了三個(gè)鏈接。 第一個(gè)是self ,它本質(zhì)上是鏈接上下文(定義為RFC-5988的一部分)。 第二個(gè)是collection ,它指向CompanyResource端點(diǎn),該端點(diǎn)返回公司列表(也包含在標(biāo)準(zhǔn)關(guān)系注冊(cè)表中)。 最后,第三個(gè)是我們自己的員工關(guān)系,我們通過一個(gè)名為getStaff的方法實(shí)現(xiàn)的另一CompanyResource端點(diǎn)組裝(我們將看到它不久)。 這些鏈接將在“ 鏈接”響應(yīng)標(biāo)頭中傳遞,并指導(dǎo)客戶端下一步去向。 讓我們通過運(yùn)行該應(yīng)用程序來實(shí)際查看它。

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

然后使用curl檢查來自此資源端點(diǎn)的響應(yīng)(不必要的詳細(xì)信息已被濾除)。

$ 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." }

鏈接頭在那里,指的是其他感興趣的端點(diǎn)。 從客戶的角度來看,事情看起來也很簡單。 Response類提供專用的getLinks方法來包裝對(duì)Link響應(yīng)標(biāo)頭的訪問,例如:

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本質(zhì)上是Web API合同的一部分,因此讓我們?cè)谧郎险页鯫penAPI規(guī)范所具有的內(nèi)容。 不幸的是, 到目前為止 尚不支持 HATEOAS ,但是從好的方面來說,存在鏈接的概念(盡管不應(yīng)將它們與Web鏈接混淆,它們有些相似,但并不相同)。 為了說明作為OpenAPI規(guī)范一部分的鏈接的用法,讓我們用Swagger注釋裝飾端點(diǎn)。

@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) { // ... }

如果我們運(yùn)行該應(yīng)用程序并瀏覽到瀏覽器中的http:// localhost:8080 / api / api-docs (這是Swagger UI的托管位置),我們將能夠看到每個(gè)響應(yīng)中的鏈接部分。

但是除此之外……您可以使用那里的鏈接做很多事情(如果您對(duì)該主題感興趣,請(qǐng)注意此問題 )。 吸引公司員工的資源終點(diǎn)看起來非常相似。

@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的鏈接之外,它還包括指向公司的鏈接。 當(dāng)我們使用curl嘗試時(shí),預(yù)期的響應(yīng)標(biāo)頭將返回。

$ 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" } ]

那么我們可以得出什么樣的結(jié)論呢? HATEOAS實(shí)際上通過動(dòng)態(tài)地驅(qū)動(dòng)對(duì)話來統(tǒng)一Web API提供者和使用者之間的交互模型。 這非常強(qiáng)大,但是其中的大多數(shù)框架和工具要么都對(duì)HATEOAS提供了相當(dāng)基本的支持(例如Web Linking ),要么根本沒有。

在很多情況下,只要使用Web鏈接就足夠了(到目前為止,我們已經(jīng)看到了示例,例如分頁,導(dǎo)航等),但是假設(shè)創(chuàng)建,編輯或修補(bǔ)現(xiàn)有資源又如何呢? 如何用超媒體豐富集合中返回的各個(gè)元素(在RFC-6537中進(jìn)行描述)? HATEOAS是否值得所有這些努力?

與往常一樣,答案是“取決于”,也許我們應(yīng)該超越JAX-RS ? 在下一篇文章中(s_,我們將繼續(xù)解決問題。

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

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

總結(jié)

以上是生活随笔為你收集整理的JAX-RS和OpenAPI对Hypermedia API的支持:任重而道远的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。