<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          拋棄Servlet API和Postman開發(fā)RESTful

          共 9061字,需瀏覽 19分鐘

           ·

          2020-10-16 21:07

          導讀

          Spring WebFlux由Spring 5.0框架首次引入。它具有無需Servlet、異步兩大特征,從而更好地提高Web應用的可伸縮性。

          Spring WebFlux簡介





          Spring WebFlux由Spring 5.0框架首次引入。與傳統(tǒng)Spring MVC相比,主要提供了如下兩個優(yōu)勢:


          • 完全脫離了Servlet API。使用Spring WebFlux開發(fā)Web應用時,Servlet容器都成了可選項,默認使用Reactor Netty作為服務器。


          • Spring WebFlux實現了完全的異步非阻塞,可以很好地支持反應式流(Reactive Stream)編程范式,也能支持背壓(back pressure)等特征。


          Reactor框架采用Mono和Flux兩個類代表消息發(fā)布者,因此它們都實現了CorePublisher接口,它們的區(qū)別在于:


          • Mono代表0~1個非阻塞數據;而Flux則代表0~個非阻塞序列。


          • Mono相當于只是一個Optional值;而Flux才是Stream。


          簡單來說,Mono包含多個數據項,而Flux能包含多個數據項。Spring WebFlux一樣也要用Mono和Flux這兩個類。


          Spring WebFlux就是基于Reactor實現的,其中Flux名稱就是來自Reactor中的Flux類,WebFlux包括了對反應式HTTP、服務器推送事件(SSE:Server Send Event)及WebSocket的支持。


          Spring WebFlux提供了兩種開發(fā)方式:


          • 使用類似Spring MVC的注解方式。在這種方式下,依然使用@Controller、@RequestMapping等注解修飾類、方法即可。


          • 使用函數式編程模型的方式。在這種方式下,程序使用RouterFunction來注冊映射地址和處理器方法之間路由關系。


          上面這兩種編程模型只是形式上有所不同(代碼編寫方式上存在不同),它們本質上完全是一樣的,它們都運行在相同的反應式流的基礎之上。


          使用注解開發(fā)WebFlux





          下面先使用@Controller、@RequestMapping等注解來開發(fā)Spring WebFlux應用。依然按慣例創(chuàng)建一個基于maven-archetype-quickstart的Maven項目,并讓其pom.xml文件繼承spring-boot-starter-parent,并添加spring-boot-starter-webflux.jar的依賴。


          接下來定義如下控制器類:

          程序清單:Annotation\src\main\java\org\crazyit\app\controller\ItemController.java@RestController@RequestMapping("/item")public class ItemController{    @GetMapping("/hello")    public Mono hello()    {        return Mono.just("Hello WebFlux");    }}


          查看該類代碼,不難發(fā)現該控制器類與Spring MVC應用的控制器類非常相似,它們同樣使用@Controller或@RestController注解來修飾控制器類、同樣使用@RequestMapping或其變體注解修飾處理方法;區(qū)別只是處理方法的返回值,WebFlux應用的控制器的返回值類型是Mono或Flux(此處是Mono)。


          Mono和Flux正是Reactor框架中消息發(fā)布者API,它們都實現了CorePublisher接口,這就表示采用了基于“訂閱-發(fā)布”的異步模式。


          本應用的主類并沒有任何改變,依然通過SpringApplication的靜態(tài)run()方法來運行由@SpringBootApplication注解修飾的類即可。


          運行該應用的主類來啟動應用,將會在控制臺看到如下輸出:


          Netty started on port(s): 8080


          從上面輸出可以看出,WebFlux應用默認使用Netty作為嵌入式服務器,不再使用Tomcat作為服務器。


          然后使用瀏覽器或Postman向http://localhost:8080/item/hello發(fā)送GET請求,即可看到服務器生成如下響應:


          Hello WebFlux


          上面處理方法只是返回的Mono對象只是包含一個簡單的String數據,下面定義的處理方法返回的Mono對象將會包含復合對象。在ItemController類中添加如下方法:


          程序清單:Annotation\src\main\java\org\crazyit\app\controller\ItemController.java@Autowiredprivate ItemService itemService;@GetMapping("/{id}")public Mono getByItemId(@PathVariable("id") Integer id){    return Mono.justOrEmpty(this.itemService.getItemById(id))            .switchIfEmpty(Mono.error(new ItemNotFoundException("商品找不到")));}@PostMapping("")public Mono create(@RequestBody Item item){    return Mono.just(this.itemService.createOrUpdate(item));}@PutMapping("")public Mono update(@RequestBody Item item){    Objects.requireNonNull(item);    return Mono.just(this.itemService.createOrUpdate(item));}@DeleteMapping("/{id}")public Mono delete(@PathVariable("id") Integer id){    return Mono.justOrEmpty(this.itemService.delete(id));}


          上面這些處理方法同樣很簡單,它們調用itemService組件來執(zhí)行CRUD操作,由于itemService的這4個CRUD方法的返回值只是單個Item對象或null,因此程序只要將該返回值放入Mono對象,這樣這些處理方法的返回值就變成了消息發(fā)布者。


          上面控制器類所依賴的ItemService組件實現類代碼如下:

          程序清單:Annotation\src\main\java\org\crazyit\app\service\impl\ItemService.java@Servicepublic class ItemServiceImpl implements ItemService{    private final Map data = new ConcurrentHashMap<>();    private static final AtomicInteger idGenerator = new AtomicInteger(0);    @Override    public Collection list()    {        return this.data.values();    }    @Override    public Item getItemById(Integer id)    {        return this.data.get(id);    }    @Override    public Item createOrUpdate(Item item)    {        // 修改用戶        if (item.getId() != null && data.containsKey(item.getId()))        {            this.data.put(item.getId(), item);        }        else        {            Integer id = idGenerator.incrementAndGet();            item.setId(id);            this.data.put(id, item);        }        return item;    }    @Override    public Item delete(Integer id)    {        return this.data.remove(id);    }}

          正如上面代碼所看到的,本Service組件并未依賴DAO組件來訪問真正的數據庫,而是使用內存中Map來模擬內存數據庫:當程序需要添加記錄時就向Map中添加一個key-value對;當程序需要刪除記錄時就刪除一個key-value對。


          提示

          使用Map模擬內存中的數據庫在學習控制器層和Service層開發(fā)時很有用,因為這樣可以避免涉及數據庫開發(fā),從而更好地聚焦正在學習的內容。

          運行該應用的主類來啟動應用,然后可使用Postman來發(fā)送GET、POST、PUT、DELETE請求來測試上面這些處理方法。


          使用curl代替Postman





          本節(jié)打算教讀者使用curl來測試它們。


          提示

          curl是一個Linux和windows系統(tǒng)都支持的命令行工具,如果能熟練地使用curl工具,你會發(fā)發(fā)現它非常強大,而且用起來非常方便——唯一的缺點是要記幾條命令。讀者可登錄https://curl.haxx.se下載和安裝curl工具,并可參考https://curl.haxx.se/docs/manpage.html快速掌握該工具的用法。當你熟練掌握它之后,你會發(fā)現它比Postman更高效、更好用。


          curl工具的基本用法如下:


          curl 選項 URL


          啟動命令行工具,執(zhí)行如下命令:


          curl?-H?"Content-Type:?application/json"?-X?POST?-d?@item.json?http://localhost:8080/item

          上面命令涉及如下幾個選項:


          • -H:該選項用于指定請求頭。

          • -X:該選項用于指定請求方法,可指定為GET、POST、PUT、DELETE等。

          • -d:該選項用于指定請求數據。請求數據即可直接給出,也可通過讀取文件,帶@符號就表示讀取文件內容來作為請求數據。


          提示

          讀者可能會把某個字符之間的間距當成空格。在這里可以告訴大家關于計算機命令格式的一個常識:空格是命令格式中非常敏感的字符。基本常識是:每個選項名(如-H、-X、-d等)與選項值之間有空格;選項值整體不能有空格,否則計算機會嘗試將它空格后面的內容解釋成下一個選項,因此如果選項值之間有空格或特殊字符,需要用雙引號括起來,比如上面"Content-Type: application/json"就是-H選項的選項值,它需要用引號括起來;第二個選項名與前一個選擇值之間有空格,例如-X選項與前面的"Content-Type: application/json"之間有空格,-d選項與前面的POST之間有空格。


          如果在Windows平臺上使用curl命令,最好使用讀取文件的方式來提交請求數據——因為Windows平臺的命令行窗口默認采用GBK字符集,因此處理起來比較煩人。


          上面命令中指定了-d @item.json選項,這意味著curl命令要讀取當前目錄下的item.json文件內容作為請求數據。因此還需在當前目錄(當你在Windows命令行窗口中執(zhí)行curl命令時,命令行窗口中>符號前的字符串就是當前目錄)下使用UTF-8字符集創(chuàng)建如下item.json文件。


          {    "name": "瘋狂Java講義",    "price": 128}


          執(zhí)行上面命令,將會在命令行窗口看到如下輸出:


          curl -H "Content-Type: application/json" -X POST -d @item.json http://localhost:8080/item{"id":1,"name":"瘋狂Java講義","price":128.0}


          上面第二行輸出就是服務器響應,這就表明向服務器發(fā)送POST請求添加數據成功。


          將item.json的數據略作修改(只能修改name屬性或price屬性的值),再次發(fā)送上面POST請求即可向服務器添加新的Item。


          執(zhí)行如下命令來發(fā)送GET請求:


          curl http://localhost:8080/item/1


          上面命令沒有指定任何選項,這意味著發(fā)送默認的GET請求,沒有請求數據,沒有指定額外的請求頭。執(zhí)行上面命令將會看到如下輸出:


          curl http://localhost:8080/item/1{"id":1,"name":"瘋狂Java講義","price":128.0}


          在當前目錄下使用UTF-8字符集創(chuàng)建如下item_update.json文件。


          {    "id": 1,    "name": "瘋狂Android講義",    "price": 128}


          上面JSON字符串定義的Item對象指定了id屬性,該字符串可用于更新id為1的Item對象。然后執(zhí)行如下命令來發(fā)送PUT請求:


          curl -H "Content-Type: application/json" -X PUT -d @item_update.json http://localhost:8080/item


          上面命令與前面的執(zhí)行POST請求的命令基本相同,只是將-X選項改成了PUT,并改為讀取當前目錄下item_update.json文件的內容作為請求數據。

          執(zhí)行上面命令將會看到如下輸出:


          curl -H "Content-Type: application/json" -X PUT -d @item_update.json http://localhost:8080/item{"id":1,"name":"瘋狂Android講義","price":128.0}

          這樣就服務端id為1的Item進行了修改,再次執(zhí)行curl http://localhost:8080/item/1命令來查看id為1的Item對象,即可看到它的name屬性值是修改后的屬性值了。


          執(zhí)行如下命令來發(fā)送DELETE請求:


          curl -X DELETE http://localhost:8080/item/1


          上面命令使用-X選項指定了發(fā)送DELETE請求,執(zhí)行上面命令將會看到如下輸出:


          curl -X DELETE http://localhost:8080/item/1{"id":1,"name":"瘋狂Android講義","price":128.0}


          上面命令執(zhí)行完成后,服務端id為1的Item對象就被刪除了。如果再次執(zhí)行curl http://localhost:8080/item/1命令來查看id為1的Item對象,即可看到如下輸出:


          curl http://localhost:8080/item/1{"timestamp":"2020-10-14T23:37:31.472+00:00","path":"/item/1","status":500,"error":"Internal Server Error","message":"商品找不到",...


          從服務器響應即可看出,id為1的Item對象不再存在。


          上面4個處理方法返回的都是包含單個數據的Mono對象,當服務器相應是多項數據時,可使用Flux返回值來定義發(fā)布者。在ItemController中添加如下處理方法:


          程序清單:Annotation\src\main\java\org\crazyit\app\controller\ItemController.java@GetMapping("")public Flux list(Integer size){    if (size == null || size == 0)    {        size = 5;    }    return Flux.fromIterable(this.itemService.list()).take(size);}


          上面代碼調用Flux的fromIterable()方法來將整個序列包含的數據變成消息發(fā)布者,然后調用Flux的take()方法來取出指定數量的數據項——本例將會根據size請求參數(如果該參數不存在,則使用默認值5)來取出數據項。


          再次運行主程序來啟動應用,先使用curl發(fā)送POST請求添加幾條數據,,然后使用curl執(zhí)行如下命令:


          curl http://localhost:8080/item?size=3


          上面命令沒有指定任何選項,這意味著它依然是發(fā)送GET請求,但發(fā)送請求時指定了size參數,運行該命令將會看到如下輸出:


          curl http://localhost:8080/item?size=3[{"id":1,"name":"瘋狂Java講義","price":128.0},{"id":2,"name":"瘋狂Python講義","price":118.0},{"id":3,"name":"瘋狂Android講義","price":138.0}]


          到此為止,可能有讀者會對WebFlux感到有點失望,好像WebFlux與Spring MVC并沒有什么區(qū)別,不僅開發(fā)方式差不多,連服務器生成的響應也差不多——實際上前面已經說過,WebFlux的變化主要是兩點:①、徹底拋棄Servlet API;②、基于訂閱-發(fā)布的異步機制。而這兩點的區(qū)別主要體現在底層服務器能以較小的線程池處理更高的并發(fā),從而提高應用的可伸縮性,它的區(qū)別往往并不體現在表面上。


          當然異步響應也還是略有不同的,在ItemController中再次添加如下處理方法:

          程序清單:Annotation\src\main\java\org\crazyit\app\controller\ItemController.java@GetMapping(value = "", produces = "application/stream+json")public Flux list(){    // 需要周期生成數據,使用 Flux.interval    return Flux.interval(Duration.ofMillis(2000))            .onBackpressureDrop()            // 每隔interval,執(zhí)行一次itemService.list()的方法            .map((interval) -> itemService.list())            // 將List轉換成Flux            .flatMapIterable(item -> item)            .log("生成信息");}


          上面@GetMapping注解中指定了produces = "application/stream+json"),這意味著該處理方法將負責處理Accept請求頭為“application/stream+json”的GET請求。


          上面list()方法中使用了Flux的interval()方法來周期性地生成數據,而且由于客戶端可接受“流式”JSON響應,這樣該方法將可每隔2秒向客戶端發(fā)送一次響應。


          再次運行主程序來啟動應用,先使用curl發(fā)送POST請求添加2條數據,,然后使用curl執(zhí)行如下命令:


          curl http://localhost:8080/item -i -H "Accept: application/stream+json"


          上面命令使用-H選項指定了Accept請求頭,還使用了一個 -i選項,該選項無需選項值,它的作用是控制輸出服務器響應的響應頭。


          運行上面命令將可看到如下輸出:


          curl http://localhost:8080/item -i -H "Accept: application/stream+jsonHTTP/1.1 200 OKtransfer-encoding: chunkedContent-Type: application/stream+json

          {"id":1,"name":"瘋狂Python講義","price":118.0}{"id":2,"name":"瘋狂Java講義","price":128.0}{"id":1,"name":"瘋狂Python講義","price":118.0}{"id":2,"name":"瘋狂Java講義","price":128.0}...


          此時將會看到服務器響應不斷地“跳出”,每次生成兩項數據——這是因為Flux訂閱者每次獲取的都只有兩條數據(itemService.list()方法只返回兩條數據)。


          啟動另一個命令行窗口,再次使用curl執(zhí)行POST請求添加一個Item對象,再次切換回原來的命令行窗口,此時由于系統(tǒng)中包含了3個Item對象(itemService.list()方法返三條數據),此時將可看到服務器每次會生成三條數據的響應。


          關于更多Spring編程的深入技巧可參考李剛老師的《輕量級Java Web企業(yè)應用實戰(zhàn)》

          喜歡請分享到朋友圈

          長按二維碼輕松關注


          瀏覽 19
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青春草视频在线观看 | 国产在线8 | 红桃视频一区二区三区四区五区在线视频 | 国产无码人妻 | 最新亚洲黄色视频 |