SpringBoot26 RestTemplate、WebClient


 

1 RestTemplate

  RestTemplate是在客戶端訪問 Restful 服務的一個核心類;RestTemplate通過提供回調方法和允許配置信息轉換器來實現個性化定制RestTemplate的功能,通過RestTemplate可以封裝請求對象,也可以對響應對象進行解析。

  技巧01:RestTemplate默認使用JDK提供的包去建立HTTP連接,當然,開發者也可以使用諸如 Apache HttpComponents, Netty, and OkHttp 去建立HTTP連接。

  技巧02:RestTemplate內部默認使用HttpMessageConverter來實現HTTTP messages 和 POJO 之間的轉換,可以通過RestTemplate的成員方法 setMessageConverters(java.util.List<org.springframework.http.converter.HttpMessageConverter<?>>). 去修改默認的轉換器。

  技巧03:RestTemplate內部默認使用SimpleClientHttpRequestFactory and DefaultResponseErrorHandler 去創建HTTP連接和處理HTTP錯誤,可以通過HttpAccessor.setRequestFactory(org.springframework.http.client.ClientHttpRequestFactory) and setErrorHandler(org.springframework.web.client.ResponseErrorHandler)去做相應的修改。.

  1.1 RestTemplate中方法概覽

    RestTemplate為每種HTTP請求都實現了相關的請求封裝方法

    技巧01:這些方法的命名是有講究的,方法名的第一部分表示HTTP請求類型,方法名的第二部分表示響應類型

      例如:getForObject 表示執行GET請求並將響應轉化成一個Object類型的對象  

    技巧02:利用RestTemplate封裝客戶端發送HTTP請求時,如果出現異常就會拋出 RestClientException 類型的異常;可以通過在創建RestTemplate對象的時候指定一個ResponseErrorHandler類型的異常處理類來處理這個異常

    技巧02:exchange 和 excute 這兩個方法是通用的HTTP請求方法,而且這兩個方法還支持額外的HTTP請求類型【PS: 前提是使用的HTTP連接包也支持這些額外的HTTP請求類型】

     技巧03:每種方法都有3個重載方法,其中兩個接收String類型的請求路徑和響應類型、參數;另外一個接收URI類型的請求路徑和響應類型。

    技巧04:使用String類型的請求路徑時,RestTemplate會自動進行一次編碼,所以為了避免重復編碼問題最好使用URI類型的請求路徑

      例如:restTemplate.getForObject("http://example.com/hotel list") becomes"http://example.com/hotel%20list"

    技巧05:URI 和URL 知識點掃盲

      參考博文01  參考博文02

    技巧06:利用接收URI參數的RestTemplate.getForObject方法發送Get請求

   1.2 常用構造器

    技巧01:利用無參構造器創建RestTemplate實例時,什么都是使用默認的【即:使用HttpMessageConverter來實現HTTTP messages 和 POJO 之間的轉換、使用SimpleClientHttpRequestFactory and DefaultResponseErrorHandler 去創建HTTP連接和處理HTTP錯誤

    技巧02:利用 RestTemplate(ClientHttpRequestFactory requestFactory) 創建RestTemplate實例時使用自定義的requestFactory去創建HTTP連接

    技巧03:利用 RestTemplate(java.util.List<HttpMessageConverter<?>> messageConverters)  創建RestTemplate實例時使用自定義的轉換器列表實現HTTTP messages 和 POJO 之間的轉換

  1.3 GET相關方法

    技巧01:本博文使用的是SpringBoot項目,利用了一個配置文件來將RestTemplate注入的容器中

package cn.xiangxu.test_demo.common.config;

import cn.xiangxu.test_demo.domain.domain_do.Student;
import cn.xiangxu.test_demo.utils.proxy.JdkProxy;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author 王楊帥
 * @create 2018-08-18 13:07
 * @desc 創建Bean的配置類
 **/
@Configuration
public class BeanConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }


}
BeanConfig.java

    模擬后台代碼:點擊獲取

package cn.xiangxu.rest_server.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * @author 王楊帥
 * @create 2018-08-18 21:56
 * @desc 模擬get請求的服務端
 **/
@RestController
@RequestMapping(value = "/get")
@Slf4j
public class GetServerConroller {

    /**
     * 不帶參數的get請求
     * @return
     */
    @GetMapping(value = "/get01")
    public String get01() {
        String result = "【get01】連接模擬的get服務端成功";
        log.info(result);
        return result;
    }

    /**
     * 帶有請求參數的get請求
     * 筆記:
     *  1 get請求參數類型:
     *      》 url中?后面的請求參數,格式以 key=value 的形式傳遞;后台需要用@RequestParam注解
     *          如果前端的 key 和 后台方法的參數名稱一致時可以不用@RequestParam注解【因為@RequestParam注解時默認的參數注解】
     *      》 url中的路徑參數
     *          需要配合@RequestMapping和@PathVariable一起使用
     * @param username 請求參數
     * @return
     */
    @GetMapping(value = "/get02")
    public String get02(  @RequestParam(value = "name", required = false, defaultValue = "王楊帥") String username) {
        String result = "【get02】獲取到的請求參數為:name = " + username;
        log.info(result);
        return result;
    }

    /**
     * 帶有路徑參數的get請求
     * @param userId
     * @return
     */
    @GetMapping(value = "/get03/{id}")
    public String get03(
            @PathVariable(value = "id") Integer userId
    ) {
        String result = "【get03】獲取到的路徑參數為:userId = " + userId;
        log.info(result);
        return result;
    }

    /**
     * 既有路徑參數又有請求參數的get請求
     * 筆記:
     *  1 @PathVariable和@RequestParam都可以設定是否必傳【默認必傳】
     *  2 @PathVariable不可以設定默認值,@RequestParam可以設定默認值【默認值就是不傳入的時候代替的值】
     *  3 @PathVariable如果設置必傳為true,前端不傳入時就會報錯【技巧:開啟必傳】
     *  4 @RequestParam如果設置必傳為true,前端不傳入還是也會報錯【技巧:關閉必傳,開啟默認值】
     *  5 @PathVariable可以設置正則表達式【詳情參見:https://www.cnblogs.com/NeverCtrl-C/p/8185576.html】
     * @param userId
     * @param username
     * @return
     */
    @GetMapping(value = "/get04/{id}")
    public String get04(
            @PathVariable(value = "id") Integer userId,
            @RequestParam(value = "name", required = false, defaultValue = "王楊帥") String username
    ) {
        String result = "【get04】獲取到的路徑參數為:userId = " + userId + " 獲取到的請求參數為:" + username;
        log.info(result);
        return result;
    }

}
遠程Restful服務

    模擬前端請求:點擊獲取

package cn.xiangxu.test_demo.controller;

import cn.xiangxu.test_demo.TestDemoApplicationTests;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

@Component
@Slf4j
public class GetClientControllerTest extends TestDemoApplicationTests {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testDemo() throws Exception {
        System.out.println("Hello Boy");


    }


    /**
     * 請求無參數的get請求
     * @return
     */
    @Test
    public void get01() throws Exception {
        String forObject = restTemplate.getForObject(
                "http://127.0.0.1:8080/get/get01",
                String.class
        );
        log.info("響應數據為:" + forObject);
    }

    /**
     * 請求有請求參數的get請求:利用占位符進行請求參數傳遞
     * @return
     */
    @Test
    public void get0201() throws Exception {
        String forObject = restTemplate.getForObject(
                "http://127.0.0.1:8080/get/get02?name={1}",
                String.class,
                "三少"
        );
        log.info("響應數據為:" + forObject);
    }

    /**
     * 請求有請求參數的get請求:利用Map進行請求參數傳遞
     * @return
     */
    @Test
    public void get0202() throws Exception {
        Map<String, Object> params = new HashMap<>();
        params.put("name", "warrior");
        String forObject = restTemplate.getForObject(
                "http://127.0.0.1:8080/get/get02?name={name}",
                String.class,
                params
        );
        log.info("響應數據為:" + forObject);
    }

    /**
     * 請求有路徑參數的get請求:
     * @return
     */
    @Test
    public void get03() throws Exception {
        String forObject = restTemplate.getForObject(
                "http://127.0.0.1:8080/get/get03/88888888",
                String.class);
        log.info("響應數據為:" + forObject);
    }

    /**
     * 請求既有路徑參數又有請求參數逇get請求
     * @return
     */
    @Test
    public void get04() throws Exception {
        String forObject = restTemplate.getForObject(
                "http://127.0.0.1:8080/get/get04/99999?name={1}",
                String.class,
                "fury"
        );
        log.info("響應數據為:" + forObject);
    }

    /**
     * 請求無參數的get請求
     * @return
     */
    @Test
    public void get01_e() throws Exception {
        ResponseEntity<String> forObject = restTemplate.getForEntity(
                "http://127.0.0.1:8080/get/get01",
                String.class
        );
        log.info("狀態碼:" + forObject.getStatusCode());
        log.info("狀態值:" + forObject.getStatusCodeValue());
        log.info("響應數據為:" + forObject);
    }

    /**
     * 請求有請求參數的get請求:利用占位符進行請求參數傳遞
     * @return
     */
    @Test
    public void get0201_e() throws Exception {
        ResponseEntity<String> forObject = restTemplate.getForEntity(
                "http://127.0.0.1:8080/get/get02?name={1}",
                String.class,
                "三少"
        );
        log.info("響應數據為:" + forObject);
    }

    /**
     * 請求有請求參數的get請求:利用Map進行請求參數傳遞
     * @return
     */
    @Test
    public void get0202_e() throws Exception {
        Map<String, Object> params = new HashMap<>();
        params.put("name", "warrior");
        ResponseEntity<String> forObject = restTemplate.getForEntity(
                "http://127.0.0.1:8080/get/get02?name={name}",
                String.class,
                (Map<String, ?>) params);
        log.info("響應數據為:" + forObject);
    }

    /**
     * 請求有路徑參數的get請求:
     * @return
     */
    @Test
    public void get03_e() throws Exception {
        ResponseEntity<String> forObject = restTemplate.getForEntity(
                "http://127.0.0.1:8080/get/get03/888888",
                String.class
        );
        log.info("響應數據為:" + forObject);
    }

    @Test
    public void get04_e() throws Exception {
    }

    /**
     * 請求既有路徑參數又有請求參數逇get請求
     * @return
     */
    @Test
    public void test01() throws Exception {
        ResponseEntity<String> forObject = restTemplate.getForEntity(
                "http://127.0.0.1:8080/get/get04/99999?name={1}",
                String.class,
                "fury"
        );
        log.info("響應數據為:" + forObject);
    }

}
模擬客戶端

    1.3.1 public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables)

      1》遠程服務代碼【不帶請求參數的】

      1》模擬客戶端代碼【不帶請求參數的】

      2》遠程服務代碼【帶請求參數的】

        技巧01:HTTP請求中url路徑?后面的參數就是請求參數格式以 key=value 的形式傳遞;后台需要用@RequestParam注解,如果前端的 key 和 后台方法的參數名稱一致時可以不用@RequestParam注解【因為@RequestParam注解時默認的參數注解】

        技巧02:對於請求參數,最好在服務端利用@RequestParam注解設置該請求參數為非必傳參數並設定默認值

      2》模擬客戶端代碼【帶請求參數的】

 

      3》遠程服務代碼【帶路徑參數的】

        技巧01:HTTP請求的路徑可以成為路徑參數,前提是服務端進行路徑配置【即:需要配合@RequestMapping和@PathVariable一起使用】

        技巧02:由於路徑參數不能設置默認是,所以在后台通過@PathVariable將路徑參數設置成必傳可以減少出錯率

        技巧03:@PathVariable可以設置正則表達式【詳情參見:https://www.cnblogs.com/NeverCtrl-C/p/8185576.html

      3》模擬客戶端代碼【帶路徑參數的】

      4》遠程服務代碼【帶路徑參數和請求參數的】

        技巧01: @PathVariable和@RequestParam都可以設定是否必傳【默認必傳】
        技巧02:@PathVariable不可以設定默認值,@RequestParam可以設定默認值【默認值就是不傳入的時候代替的值】
        技巧03: @PathVariable如果設置必傳為true,前端不傳入時就會報錯【技巧:開啟必傳】
        技巧04:@RequestParam如果設置必傳為true,前端不傳入還是也會報錯【技巧:關閉必傳,開啟默認值】
        技巧05:@PathVariable可以設置正則表達式【詳情參見:https://www.cnblogs.com/NeverCtrl-C/p/8185576.html】

      4》模擬客戶端代碼【帶路徑參數和請求參數的】

 

     1.3.2 public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)

       1》遠程服務代碼【帶請求參數的】

       1》模擬客戶端代碼【帶請求參數的】

 

    1.3.3 public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)

       技巧01:getForObject 和 getForEntity 的區別:后者可以獲取到更多的響應信息,前者這可以獲取到響應體的數據

      1》遠程服務代碼【不帶請求參數的】

      1》模擬客戶端代碼【不帶請求參數的】

      2》遠程服務代碼【帶請求參數的】

      2》模擬客戶端代碼【帶請求參數的】

      3》遠程服務代碼【帶路徑參數的】

      3》模擬客戶端代碼【帶路徑參數的】

      4》遠程服務代碼【帶路徑參數和請求參數的】

      4》模擬客戶端代碼【帶路徑參數和請求參數的】

     1.3.4 public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)

      1》遠程服務代碼【帶請求參數的】

      1》模擬客戶端代碼【帶請求參數的】

  1.4 POST

    服務端源代碼:點擊前往

package cn.xiangxu.rest_server.controller;

import cn.xiangxu.rest_server.domain.Student;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * @author 王楊帥
 * @create 2018-08-19 9:41
 * @desc 模擬post服務端接口
 **/
@RestController
@RequestMapping(value = "/post")
@Slf4j
public class PostServerController {

    /**
     * 模擬只有請求體參數的post請求
     * @param student 請求體參數
     * @return
     */
    @PostMapping(value = "/post01")
    public Student post01(
            @RequestBody Student student
    ) {
        log.info("【post01】獲取到的請求體參數為:" + student);
        student.setName("服務端修改");
        return student;

    }

    /**
     * 模擬既有請求體參數又有請求參數的post請求
     * @param student 請求體參數
     * @param infoMsg 請求參數
     * @return
     */
    @PostMapping(value = "/post02")
    public String post02(
            @RequestBody Student student,
            @RequestParam(value = "info", required = false, defaultValue = "默認請求參數") String infoMsg
    ) {
        String result = "【post02】-方法體參數為:student -> " + student + " 請求參數為:infoMsg -> " + infoMsg;
        log.info(result);
        return result;
    }

    /**
     * 模擬既有請求體參數又有請求參數和路徑參數的post請求
     * @param student 請求體參數
     * @param infoMsg 請求參數
     * @param userId 路徑參數
     * @return
     */
    @PostMapping(value = "/post03/{id}")
    public String post03(
            @RequestBody Student student,
            @RequestParam(value = "info", required = false, defaultValue = "默認請求參數") String infoMsg,
            @PathVariable(value = "id", required = false) Integer userId
    ) {

        String result = "【psot03】-獲取到的請求體參數為:student -> " + student + "\n" +
                "請求參數為:infoMsg -> " + infoMsg + "\n" +
                "路徑參數為:userId -> " + userId;

        log.info(result);

        return result;

    }

    @GetMapping(value = "/connect")
    public String connect() {
        String result = "【post】-前后端連接測試成功";
        log.info(result);
        return result;
    }

}
View Code

    模擬客戶端代碼:點擊前往

package cn.xiangxu.test_demo.controller;

import cn.xiangxu.test_demo.TestDemoApplicationTests;
import cn.xiangxu.test_demo.domain.domain_do.Student;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;

@Component
@Slf4j
public class PostClientControllerTest extends TestDemoApplicationTests {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void connect() throws Exception {
    }

    /**
     * 模擬只有請求體參數的post請求
     * @return
     */
    @Test
    public void post01() throws Exception {
        Student student = Student.builder()
                .name("warrior")
                .age(24)
                .build();

        Student postForObject = restTemplate.postForObject(
                "http://127.0.0.1:8080/post/post01",
                student,
                Student.class
        );

        System.out.println(postForObject);
    }

    /**
     * 模擬既有請求體參數又有請求參數的POST請求【方式01】
     * @return
     */
    @Test
    public void post0201() throws Exception {
        Student student = Student.builder()
                .name("王楊帥")
                .address("渝足")
                .build();
        String forObject = restTemplate.postForObject(
                "http://127.0.0.1:8080/post/post02?info={1}",
                student,
                String.class,
                "模擬前端"
        );
        log.info("響應數據㘝:" + forObject);
    }

    /**
     * 模擬既有請求體參數又有請求參數的POST請求【方式02】
     * @return
     */
    @Test
    public void post0202() throws Exception {
        Student student = Student.builder()
                .name("warrior")
                .address("大足")
                .build();

        Map<String, Object> param = new HashMap<>();
        param.put("info", "模擬前端");

        String forObject = restTemplate.postForObject(
                "http://127.0.0.1:8080/post/post02?info={info}",
                student,
                String.class,
                param
        );

        log.info("響應數據㘝:" + forObject);
    }

    /**
     * 模擬既有請求體參數又有請求參數和路徑參數的POST請求
     * @return
     */
    @Test
    public void post03() throws Exception {
        Student student = Student.builder()
                .name("fury")
                .address("合川")
                .build();

        String forObject = restTemplate.postForObject(
                "http://127.0.0.1:8080/post/post03/8888?info={1}",
                student,
                String.class,
                "模擬請求參數"

        );

        log.info("響應數據為:" + forObject);
    }

        /*
    下面是利用另外一個方法實現get請求:
        筆記:
            1 getForObject 和 getForEntity 的區別:后者可以獲取到更多的響應信息,前者這可以獲取到響應體的數據
     */

    /**
     * 模擬只有請求體參數的post請求
     * @return
     */
    @Test
    public void post01_e() throws Exception {
        Student student = Student.builder()
                .name("王楊帥")
                .address("渝足")
                .build();

        ResponseEntity<Student> forObject = restTemplate.postForEntity(
                "http://127.0.0.1:8080/post/post01",
                student,
                Student.class
        );

        log.info("響應數據為:" + forObject.getBody());
    }

    /**
     * 模擬既有請求體參數又有請求參數的POST請求【方式01】
     * @return
     */
    @Test
    public void post0201_e() throws Exception {
        Student student = Student.builder()
                .name("王楊帥")
                .address("渝足")
                .build();
        ResponseEntity<String> forObject = restTemplate.postForEntity(
                "http://127.0.0.1:8080/post/post02?info={1}",
                student,
                String.class,
                "模擬前端"
        );

        log.info("響應數據為:" + forObject.getBody());
    }

    /**
     * 模擬既有請求體參數又有請求參數的POST請求【方式02】
     * @return
     */
    @Test
    public void post0202_e() throws Exception {
        Student student = Student.builder()
                .name("warrior")
                .address("大足")
                .build();

        Map<String, Object> param = new HashMap<>();
        param.put("info", "模擬前端");

        ResponseEntity<String> forObject = restTemplate.postForEntity(
                "http://127.0.0.1:8080/post/post02?info={info}",
                student,
                String.class,
                param
        );

        log.info("響應數據為:" + forObject.getBody());
    }

    /**
     * 模擬既有請求體參數又有請求參數和路徑參數的POST請求
     * @return
     */
    @Test
    public void post03_e() throws Exception {
        Student student = Student.builder()
                .name("fury")
                .address("合川")
                .build();

        ResponseEntity<String> forObject = restTemplate.postForEntity(
                "http://127.0.0.1:8080/post/post03/8888?info={1}",
                student,
                String.class,
                "模擬請求參數"

        );

        log.info("響應數據為:" + forObject.getBody());
    }

    /**
     * 請求體封裝【利用HttpEntity可以自定義請求體和請求頭】
     */
    @Test
    public void test01() throws Exception {
        Student student = Student.builder()
                .name("bob")
                .address("成都")
                .build();

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);

        HttpEntity<Student> studentHttpEntity = new HttpEntity<>(student, headers);

        String forObject = restTemplate.postForObject(
                "http://127.0.0.1:8080/post/post02?info={1}",
                studentHttpEntity,
                String.class,
                "模擬前端"
        );

        log.info(forObject);

    }

}
View Code

    1.4.1 public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables)

      參數解釋:

        url -> String類型的請求路徑

        request -> 請求體對象

        responseType -> 響應數據類型

        uriVariables -> 請求參數

    1.4.2 public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables)

      參數解釋:

        url -> String類型的請求路徑

        request -> 請求體對象

        responseType -> 響應數據類型

        uriVariables -> 請求參數

    1.4.3 public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType)

      參數解釋:

        url -> URI類型的請求路徑

        request -> 請求體對象

        responseType -> 響應數據類型

    1.4.4 請求體對象說明

      技巧01:請求體對象(@Nullable Object request)可以直接傳一個實體,服務端利用@RequestBody接收這個實體即可

      技巧02:請求體對象(@Nullable Object request)也可以傳入一個  HttpEntity 的實例,服務端的代碼不變;創建 HttpEntity 實例時可以設定請求體數據和請求頭數據(詳情請參見 HttpEntity 的相關構造函數)

  1.5 其他請求和GET、POST類似

    待更新......

 

2 WebClient

  WebClient 是一個非阻塞、響應式的HTTP客戶端,它以響應式被壓流的方式執行HTTP請求;WebClient默認使用 Reactor Netty 作為HTTP連接器,當然也可以通過ClientHttpConnector修改其它的HTTP連接器。

  技巧01:使用WebClient需要進入Spring5的相關依賴,如果使用的是SpringBoot項目的話直接引入下面的依賴就可以啦

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.web.reactive.function.client;

import java.net.URI;
import java.nio.charset.Charset;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import org.reactivestreams.Publisher;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriBuilderFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface WebClient {
    WebClient.RequestHeadersUriSpec<?> get();

    WebClient.RequestHeadersUriSpec<?> head();

    WebClient.RequestBodyUriSpec post();

    WebClient.RequestBodyUriSpec put();

    WebClient.RequestBodyUriSpec patch();

    WebClient.RequestHeadersUriSpec<?> delete();

    WebClient.RequestHeadersUriSpec<?> options();

    WebClient.RequestBodyUriSpec method(HttpMethod var1);

    WebClient.Builder mutate();

    static WebClient create() {
        return (new DefaultWebClientBuilder()).build();
    }

    static WebClient create(String baseUrl) {
        return (new DefaultWebClientBuilder()).baseUrl(baseUrl).build();
    }

    static WebClient.Builder builder() {
        return new DefaultWebClientBuilder();
    }

    public interface RequestBodyUriSpec extends WebClient.RequestBodySpec, WebClient.RequestHeadersUriSpec<WebClient.RequestBodySpec> {
    }

    public interface RequestHeadersUriSpec<S extends WebClient.RequestHeadersSpec<S>> extends WebClient.UriSpec<S>, WebClient.RequestHeadersSpec<S> {
    }

    public interface ResponseSpec {
        WebClient.ResponseSpec onStatus(Predicate<HttpStatus> var1, Function<ClientResponse, Mono<? extends Throwable>> var2);

        <T> Mono<T> bodyToMono(Class<T> var1);

        <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> var1);

        <T> Flux<T> bodyToFlux(Class<T> var1);

        <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> var1);
    }

    public interface RequestBodySpec extends WebClient.RequestHeadersSpec<WebClient.RequestBodySpec> {
        WebClient.RequestBodySpec contentLength(long var1);

        WebClient.RequestBodySpec contentType(MediaType var1);

        WebClient.RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> var1);

        <T, P extends Publisher<T>> WebClient.RequestHeadersSpec<?> body(P var1, Class<T> var2);

        <T, P extends Publisher<T>> WebClient.RequestHeadersSpec<?> body(P var1, ParameterizedTypeReference<T> var2);

        WebClient.RequestHeadersSpec<?> syncBody(Object var1);
    }

    public interface RequestHeadersSpec<S extends WebClient.RequestHeadersSpec<S>> {
        S accept(MediaType... var1);

        S acceptCharset(Charset... var1);

        S cookie(String var1, String var2);

        S cookies(Consumer<MultiValueMap<String, String>> var1);

        S ifModifiedSince(ZonedDateTime var1);

        S ifNoneMatch(String... var1);

        S header(String var1, String... var2);

        S headers(Consumer<HttpHeaders> var1);

        S attribute(String var1, Object var2);

        S attributes(Consumer<Map<String, Object>> var1);

        WebClient.ResponseSpec retrieve();

        Mono<ClientResponse> exchange();
    }

    public interface UriSpec<S extends WebClient.RequestHeadersSpec<?>> {
        S uri(URI var1);

        S uri(String var1, Object... var2);

        S uri(String var1, Map<String, ?> var2);

        S uri(Function<UriBuilder, URI> var1);
    }

    public interface Builder {
        WebClient.Builder baseUrl(String var1);

        WebClient.Builder defaultUriVariables(Map<String, ?> var1);

        WebClient.Builder uriBuilderFactory(UriBuilderFactory var1);

        WebClient.Builder defaultHeader(String var1, String... var2);

        WebClient.Builder defaultHeaders(Consumer<HttpHeaders> var1);

        WebClient.Builder defaultCookie(String var1, String... var2);

        WebClient.Builder defaultCookies(Consumer<MultiValueMap<String, String>> var1);

        WebClient.Builder clientConnector(ClientHttpConnector var1);

        WebClient.Builder filter(ExchangeFilterFunction var1);

        WebClient.Builder filters(Consumer<List<ExchangeFilterFunction>> var1);

        WebClient.Builder exchangeFunction(ExchangeFunction var1);

        WebClient.Builder exchangeStrategies(ExchangeStrategies var1);

        WebClient.Builder clone();

        WebClient.Builder apply(Consumer<WebClient.Builder> var1);

        WebClient build();
    }
}
WebClient源碼

  2.1 創建WebClient實例的方式

    技巧01:從WebClient的源碼中可以看出,WebClient接口提供了三個靜態方法來創建WebClient實例

    2.1.1 利用create()創建

      該方法不接受任何參數,返回類型是WebClient

      技巧01:由於利用create()創建的WebClient對象沒有設定baseURL,所以  uri("http://127.0.0.1:8080/get/get01") 返回的結果就是最終需要請求的遠程服務器的URL;這里的 uri("http://127.0.0.1:8080/get/get01")相當於重寫baseURL。

    2.2.2 利用create(String baseUrl)創建

      該方法接收一個String類型的對象作為baseUrl,直接返回WebClient對象

      技巧01:由於利用create(String baseUrl)創建的WebClient對象時已經設定了baseURL,所以 uri("/get01") 會將返回的結果和baseUrl進行拼接組成最終需要遠程請求的資源RUL

 

    2.2.3 利用builder創建【推薦使用】

      該方法不接受任何參數,返回的是 WebClient.Builder

      技巧01:由於返回的不是WebClient類型而是WebClient.Builder,我們可以通過返回的 WebClient.Builder 設置一些配置參數(例如:baseUrl等),然后在調用build就可以返回WebClient對象了【WebClient.Builder先關知識點請繼續往下看】

    2.2.4 重寫baseURL

      》如果在創建WebClient時沒有指定baseURL就可以用uri進行重寫【前提:uri的參數必須是一個完整的路徑】 -> 完全重寫

      》即使在創建WebClient的時候指定了baseURL也可以用uri進行重寫【前提:uri的參數必須是一個完整的路徑】-> 部分重寫

      》即使在創建WebClient的時候指定了baseURL也可以用uri進行重寫【前提:此時需要用到參數是一個函數式接口的uri方法】 ->  部分重寫

        技巧01:S uri(Function<UriBuilder, URI> var1)  -> 這個uri方法接收一個lambda表達式【輸入是UriBuilder,輸出時RUI對象】

        技巧02:uri方法返回的URI對象會被轉化成遠程請求資源的URL

        技巧03:UriBuilder的replacePath方法是替換端口后面的所有信息【詳情參見UriBuilder,其實UriBuilder和URI很像】

      》實戰技巧

        在使用uri時盡量使用S uri(Function<UriBuilder, URI> var1) ,因為這個方法可以設定更多的配置信息【例如:設置端口、設置參數、拼接路徑、替換路徑、替換參數......】

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.web.util;

import java.net.URI;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;

public interface UriBuilder {
    UriBuilder scheme(@Nullable String var1);

    UriBuilder userInfo(@Nullable String var1);

    UriBuilder host(@Nullable String var1);

    UriBuilder port(int var1);

    UriBuilder port(@Nullable String var1);

    UriBuilder path(String var1);

    UriBuilder replacePath(@Nullable String var1);

    UriBuilder pathSegment(String... var1) throws IllegalArgumentException;

    UriBuilder query(String var1);

    UriBuilder replaceQuery(@Nullable String var1);

    UriBuilder queryParam(String var1, Object... var2);

    UriBuilder queryParams(MultiValueMap<String, String> var1);

    UriBuilder replaceQueryParam(String var1, Object... var2);

    UriBuilder replaceQueryParams(MultiValueMap<String, String> var1);

    UriBuilder fragment(@Nullable String var1);

    URI build(Object... var1);

    URI build(Map<String, ?> var1);
}
UriBuilder.java

  

  2.2 WebClient相關接口概覽

    2.2.1 WebClient.Builder

    public interface Builder {
        WebClient.Builder baseUrl(String var1);

        WebClient.Builder defaultUriVariables(Map<String, ?> var1);

        WebClient.Builder uriBuilderFactory(UriBuilderFactory var1);

        WebClient.Builder defaultHeader(String var1, String... var2);

        WebClient.Builder defaultHeaders(Consumer<HttpHeaders> var1);

        WebClient.Builder defaultCookie(String var1, String... var2);

        WebClient.Builder defaultCookies(Consumer<MultiValueMap<String, String>> var1);

        WebClient.Builder clientConnector(ClientHttpConnector var1);

        WebClient.Builder filter(ExchangeFilterFunction var1);

        WebClient.Builder filters(Consumer<List<ExchangeFilterFunction>> var1);

        WebClient.Builder exchangeFunction(ExchangeFunction var1);

        WebClient.Builder exchangeStrategies(ExchangeStrategies var1);

        WebClient.Builder clone();

        WebClient.Builder apply(Consumer<WebClient.Builder> var1);

        WebClient build();
    }
View Code

      該接口可用來在創建WebClient實例時修改一些默認的配置,而且靈活性特別大;利用該接口對象可以設置WebClient的cookie、header等信息。

      》獲取WebClient.Builder對象

        技巧01:獲取WebClient.Builder對象或就可以進行一些配置設置了,配置完成后調用build方法就可以生成WebClient對象啦

WebClient.Builder builder = WebClient.builder();

    2.2.2 WebClient.RequestBodySpec 

      該接口主要用來這頂媒體類型和請求體的【需要請求體類型的HTTP請求需要用到,例如:POST】

      技巧01:調用post()后返回的類型是RequestBodyUriSpec

      技巧02:uri()方法需要 UriSpec 類型的實例才可以調用,但是 UriSpec  恰恰繼承了 RequestBodyUriSpec,uri() 方法返回的類型是RequestHeadersSpec

      技巧03:contentType() 方法需要RequestBodySpec類型的實例才可以調用,但是RequestHeadersSpec的父接口是RequestHeadersSpec,contentType() 的返回類型是WebClient.RequestBodySpec

      技巧04:body() 方法需要RequestBodySpec類型的實例才可以調用,body() 的返回值類型是WebClient.RequestHeadersSpec

      技巧總結:使用WebClient是由嚴格的調用你順序的,如下:

        HTTP請求類型 -> 請求URL -> 請求體類型 -> 請求體

 

    2.2.3 其他接口

      待更新......2018年8月20日21:20:46

      

  2.3 WebClient 請求方法概覽

    

    詳情信息待更新...... 2018年8月20日21:38:05

 

 

       

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM