springboot3筆記第六章遠(yuǎn)程訪(fǎng)問(wèn)@HttpExchange[SpringBoot 3]

動(dòng)力節(jié)點(diǎn)SpringBoot3第六章

6?遠(yuǎn)程訪(fǎng)問(wèn)@HttpExchange[SpringBoot 3]

遠(yuǎn)程訪(fǎng)問(wèn)是開(kāi)發(fā)的常用技術(shù)状土,一個(gè)應(yīng)用能夠訪(fǎng)問(wèn)其他應(yīng)用的功能荔燎。Spring Boot提供了多種遠(yuǎn)程訪(fǎng)問(wèn)的技術(shù)胸嘁。 基于HTTP協(xié)議的遠(yuǎn)程訪(fǎng)問(wèn)是支付最廣泛的埠居。Spring Boot3提供了新的HTTP的訪(fǎng)問(wèn)能力嘁灯,通過(guò)接口簡(jiǎn)化HTTP遠(yuǎn)程訪(fǎng)問(wèn),類(lèi)似Feign功能栅哀。Spring包裝了底層HTTP客戶(hù)的訪(fǎng)問(wèn)細(xì)節(jié)。 SpringBoot中定義接口提供HTTP服務(wù)称龙。生成的代理對(duì)象實(shí)現(xiàn)此接口留拾,代理對(duì)象實(shí)現(xiàn)HTTP的遠(yuǎn)程訪(fǎng)問(wèn)。需要理解:

[if !supportLists]·?[endif]@HttpExchange?

[if !supportLists]·?[endif]WebClient

WebClient特性:我們想要調(diào)用其他系統(tǒng)提供的HTTP 服務(wù)茵瀑,通臣渫裕可以使用 Spring 提供的 RestTemplate 來(lái)訪(fǎng)問(wèn),RestTemplate 是 Spring 3 中引入的同步阻塞式 HTTP 客戶(hù)端马昨,因此存在一定性能瓶頸竞帽。Spring 官方在 Spring 5 中引入了 WebClient 作為非阻塞式HTTP 客戶(hù)端。

[if !supportLists]·?[endif]非阻塞鸿捧,異步請(qǐng)求

[if !supportLists]·?[endif]它的響應(yīng)式編程的基于Reactor

[if !supportLists]·?[endif]高并發(fā)屹篓,硬件資源少。

[if !supportLists]·?[endif]支持Java 8 lambdas 函數(shù)式編程

什么是異步非阻塞理解:異步和同步匙奴,非阻塞和阻塞上面都是針對(duì)對(duì)象不一樣異步和同步針對(duì)調(diào)度者,調(diào)用者發(fā)送請(qǐng)求,如果等待對(duì)方回應(yīng)之后才去做其他事情,就是同步,如果發(fā)送請(qǐng)求之后不等著對(duì)方回應(yīng)就去做其他事情就是異步

阻塞和非阻塞針對(duì)被調(diào)度者,被調(diào)度者收到請(qǐng)求后,做完請(qǐng)求任務(wù)之后才給出反饋就是阻塞,收到請(qǐng)求之后馬上給出反饋然后去做事情,就是非阻塞

6.1?準(zhǔn)備工作:

1.安裝GsonFormat插件堆巧,方便json和Bean的轉(zhuǎn)換 2.介紹一個(gè)免費(fèi)的、24h在線(xiàn)的Rest Http服務(wù),每月提供近20億的請(qǐng)求谍肤,關(guān)鍵還是免費(fèi)的啦租、可公開(kāi)訪(fǎng)問(wèn)的。https://jsonplaceholder.typicode.com/

6.1.1?聲明式HTTP遠(yuǎn)程服務(wù)

需求:訪(fǎng)問(wèn)https://jsonplaceholder.typicode.com/提供的todos服務(wù)荒揣。 基于RESTful風(fēng)格篷角,添加新的todo,修改todo系任,修改todo中的title恳蹲,查詢(xún)某個(gè)todo。聲明接口提供對(duì)象https://jsonplaceholder.typicode.com/todos服務(wù)的訪(fǎng)問(wèn)

創(chuàng)建新的Spring Boot項(xiàng)目Lession18-HttpService, Maven構(gòu)建工具俩滥,JDK19嘉蕾。 Spring Web, Spring Reactive Web , Lombok依賴(lài)。包名稱(chēng):com.bjpowernode.http

step1:Maven依賴(lài)pom.xml

| ?org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-webflux

step2:聲明Todo數(shù)據(jù)類(lèi)

| /**

[if !supportLists]·?[endif]根據(jù)https://jsonplaceholder.typicode.com/todos/1的結(jié)構(gòu)創(chuàng)建的*/ public class Todo { private int userId; private int id; private String title; private boolean completed; //省略 set 霜旧, get方法 public boolean getCompleted() { return completed; } public void setCompleted(boolean completed) { this.completed = completed; }@Override??public String toString() { return "Todo{" + "userId=" + userId + ", id=" + id + ", title='" + title + '\'' + ", completed=" + completed + '}'; } } | | --- |

step3:聲明服務(wù)接口

| import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; //... public interface TodoService { @GetExchange("/todos/{id}") Todo getTodoById(@PathVariable?Integer id); @PostExchange(value?= "/todos",accept = MediaType.APPLICATION_JSON_VALUE) ?Todo createTodo(@RequestBodyTodo newTodo); ?@PutExchange("/todos/{id}") ResponseEntity modifyTodo(@PathVariable?Integer id,@RequestBody?Todo todo); ?@PatchExchange("/todos/{id}") HttpHeaders pathRequest(@PathVariable?Integer id, @RequestParam?String title); ?@DeleteExchange("/todos/{id}") void removeTodo(@PathVariable?Integer id);

}

step4:創(chuàng)建HTTP服務(wù)代理對(duì)象

| @Configuration(proxyBeanMethods?= false) ?public class HttpConfiguration {@Bean??public TodoService requestService(){ WebClient webClient = WebClient.builder().baseUrl("https://jsonplaceholder.typicode.com/").build(); HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();

return proxyFactory.createClient(TodoService.class);

}

}

step5:單元測(cè)試

| @SpringBootTest??class HttpApplicationTests {@Resource??private TodoService requestService;@Test??void testQuery() { Todo todo = requestService.getTodoById(1); System.out.println("todo = " + todo); }@Test??void testCreateTodo() { Todo todo = new Todo(); todo.setId(1001); todo.setCompleted(true); todo.setTitle("錄制視頻"); todo.setUserId(5001); Todo save = requestService.createTodo(todo); System.out.println(save); }@Test??void testModifyTitle() {//org.springframework.http.HttpHeaders?HttpHeaders entries = requestService.pathRequest(5, "homework"); entries.forEach( (name,vals)->{ System.out.println(name); vals.forEach(System.out::println); System.out.println("========================="); }); }@Test??void testModifyTodo() { Todo todo = new Todo(); todo.setCompleted(true); todo.setTitle("錄制視頻!!!"); todo.setUserId(5002); ResponseEntity result = requestService.modifyTodo(2,todo); HttpStatusCode statusCode = result.getStatusCode(); HttpHeaders headers = result.getHeaders(); Todo modifyTodo = result.getBody(); System.out.println("statusCode = " + statusCode); System.out.println("headers = " + headers); System.out.println("modifyTodo = " + modifyTodo); }@Test??void testRemove() { requestService.removeTodo(2); }

}

6.1.1.1?Http服務(wù)接口的方法定義

@HttpExchange注解用于聲明接口作為HTTP遠(yuǎn)程服務(wù)错忱。在方法、類(lèi)級(jí)別使用颁糟。通過(guò)注解屬性以及方法的參數(shù)設(shè)置HTTP請(qǐng)求的細(xì)節(jié)航背。

快捷注解簡(jiǎn)化不同的請(qǐng)求方式

[if !supportLists]·?[endif]GetExchange

[if !supportLists]·?[endif]PostExchange

[if !supportLists]·?[endif]PutExchange

[if !supportLists]·?[endif]PatchExchange

[if !supportLists]·?[endif]DeleteExchange

@GetExchange就是@HttpExchange表示的GET請(qǐng)求方式

| @HttpExchange(method?= "GET") ?public @interface?GetExchange { @AliasFor(annotation?= HttpExchange.class) ?String value() default "";@AliasFor(annotation?= HttpExchange.class) ?String url() default "";@AliasFor(annotation?= HttpExchange.class) ?String[] accept() default {};

}

作為HTTP服務(wù)接口中的方法允許使用的參數(shù)列表

參數(shù)說(shuō)明

URI設(shè)置請(qǐng)求的url,覆蓋注解的url屬性

HttpMethod請(qǐng)求方式棱貌,覆蓋注解的method屬性

@RequestHeader?添加到請(qǐng)求中header玖媚。 參數(shù)類(lèi)型可以為Map, MultiValueMap,單個(gè)值 或者 Collection

@PathVariable?url中的占位符,參數(shù)可為單個(gè)值或Map<String,?>

@RequestBody?請(qǐng)求體婚脱,參數(shù)是對(duì)象

@RequestParam?請(qǐng)求參數(shù)今魔,單個(gè)值或Map, MultiValueMap,Collection

@RequestPart?發(fā)送文件時(shí)使用

@CookieValue?向請(qǐng)求中添加cookie

接口中方法返回值

返回值類(lèi)型說(shuō)明

void執(zhí)行請(qǐng)求障贸,無(wú)需解析應(yīng)答

HttpHeaders存儲(chǔ)response應(yīng)答的header信息

對(duì)象解析應(yīng)答結(jié)果错森,轉(zhuǎn)為聲明的類(lèi)型對(duì)象

ResponseEntity,ResponseEntity解析應(yīng)答內(nèi)容篮洁,得到ResponseEntity,從ResponseEntity可以獲取http應(yīng)答碼涩维,header,body內(nèi)容袁波。

反應(yīng)式的相關(guān)的返回值包含Mono,Mono,Mono,Flux Mono,Mono,Mono瓦阐。

6.1.1.2?組合使用注解

@HttpExchange,@GetExchange等可以組合使用篷牌。 ?這次使用Albums遠(yuǎn)程服務(wù)接口睡蟋,查詢(xún)Albums信息

step1:創(chuàng)建Albums數(shù)據(jù)類(lèi)

| public class Albums { private int userId; private int id; private String title; //省略 set ,get@Override??public String toString() { return "Albums{" + "userId=" + userId + ", id=" + id + ", title='" + title + '\'' + '}'; }

}

step2:創(chuàng)建AlbumsService接口 接口聲明方法,提供HTTP遠(yuǎn)程服務(wù)枷颊。在類(lèi)級(jí)別應(yīng)用@HttpExchange接口戳杀,在方法級(jí)別使用@HttpExchange?, @GetExchange等

| @HttpExchange(url?= " https://jsonplaceholder.typicode.com/") public interface AlbumsService { @GetExchange("/albums/{aid}") Albums getById(@PathVariable?Integer aid); @HttpExchange(url= "/albums/{aid}",method =?"GET", ?contentType = MediaType.APPLICATION_JSON_VALUE) Albums getByIdV2(@PathVariable?Integer aid);

}

類(lèi)級(jí)別的url和方法級(jí)別的url組合在一起為最后的請(qǐng)求url地址该面。

step3:聲明代理

| @Configuration(proxyBeanMethods?= true) ?public class HttpServiceConfiguration {@Bean??public AlbumsService albumsService(){ WebClient webClient = WebClient.create(); HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)) .build(); return proxyFactory.createClient(AlbumsService.class); }

}

step4:單元測(cè)試

| @SpringBootTest??public class TestHttpExchange {@Resource??AlbumsService albumsService;@Test??void getQuery() { Albums albums = albumsService.getById(1); System.out.println("albums = " + albums); }@Test??void getQueryV2() { Albums albums = albumsService.getByIdV2(2); System.out.println("albums = " + albums); }

}

測(cè)試方法能正常完成遠(yuǎn)程方法調(diào)用。

6.1.1.3?Java Record

測(cè)試Java Record作為返回類(lèi)型信卡,由框架的HTTP代理轉(zhuǎn)換應(yīng)該內(nèi)容為Record對(duì)象 step1:創(chuàng)建Albums的Java Record

| public record AlbumsRecord(int userId, int id, String title) {

}

step2:AlbumsService接口增加新的遠(yuǎn)程訪(fǎng)問(wèn)方法隔缀,方法返回類(lèi)型為Record

| @GetExchange("/albums/{aid}")

AlbumsRecord getByIdRecord(@PathVariable?Integer aid);

step3:單元測(cè)試,Record接收結(jié)果

| @Test??void getQueryV3() { AlbumsRecord albums = albumsService.getByIdRecord(1); System.out.println("albums = " + albums);

}

JavaRecord能夠正確接收應(yīng)該結(jié)果坐求,轉(zhuǎn)為AlbumsRecord對(duì)象蚕泽。

6.1.1.4?定制HTTP請(qǐng)求服務(wù)

設(shè)置HTTP遠(yuǎn)程的超時(shí)時(shí)間, 異常處理 在創(chuàng)建接口代理對(duì)象前桥嗤,先設(shè)置WebClient的有關(guān)配置。

step1:設(shè)置超時(shí)仔蝌,異常處理

|?@Beanpublic AlbumsService albumsService(){ HttpClient httpClient = HttpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000)?//連接超時(shí) .doOnConnected(conn -> { conn.addHandlerLast(new ReadTimeoutHandler(10)); //讀超時(shí) conn.addHandlerLast(new WriteTimeoutHandler(10)); //寫(xiě)超時(shí) }); WebClient webClient = WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) //定制4XX,5XX的回調(diào)函數(shù) .defaultStatusHandler(HttpStatusCode::isError,clientResponse -> { System.out.println("WebClient請(qǐng)求異常**"); return Mono.error(new RuntimeException( "請(qǐng)求異常"+ clientResponse.statusCode().value())); }).build(); HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder( WebClientAdapter.forClient(webClient)).build(); return proxyFactory.createClient(AlbumsService.class);

}

step2:單元測(cè)試超時(shí)和400異常

|? @Test??void testError() { Albums albums = albumsService.getById(111); System.out.println("albums = " + albums);

}

測(cè)試方法執(zhí)行會(huì)觸發(fā)異常代碼泛领。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市敛惊,隨后出現(xiàn)的幾起案子渊鞋,更是在濱河造成了極大的恐慌,老刑警劉巖瞧挤,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锡宋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡特恬,警方通過(guò)查閱死者的電腦和手機(jī)执俩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)癌刽,“玉大人役首,你說(shuō)我怎么就攤上這事∠园荩” “怎么了衡奥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)远荠。 經(jīng)常有香客問(wèn)我矮固,道長(zhǎng),這世上最難降的妖魔是什么譬淳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任档址,我火速辦了婚禮,結(jié)果婚禮上瘦赫,老公的妹妹穿的比我還像新娘辰晕。我一直安慰自己,他們只是感情好确虱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布含友。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窘问。 梳的紋絲不亂的頭發(fā)上辆童,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音惠赫,去河邊找鬼把鉴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛儿咱,可吹牛的內(nèi)容都是我干的庭砍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼混埠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼怠缸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起钳宪,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤揭北,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后吏颖,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體搔体,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年半醉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疚俱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奉呛,死狀恐怖计螺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瞧壮,我是刑警寧澤登馒,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站咆槽,受9級(jí)特大地震影響陈轿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秦忿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一麦射、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧灯谣,春花似錦潜秋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)罗售。三九已至,卻和暖如春钩述,著一層夾襖步出監(jiān)牢的瞬間寨躁,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工牙勘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留职恳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓方面,卻偏偏與公主長(zhǎng)得像放钦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子葡幸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容