動(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)求之后馬上給出反饋然后去做事情,就是非阻塞
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/
需求:訪(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); }
}
@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瓦阐。
@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)用。
測(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ì)象蚕泽。
設(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ā)異常代碼泛领。