使用RestTemplate進行restful調用,你真的會了嗎

微信公衆號:跟着老萬學java
歡迎關注,瞭解更多編程技巧,一塊兒交流,一塊兒成長。html

RestTemplate是一種更優雅的調用RESTful服務的方式,而且能結合Ribbon一塊兒使用。那麼,你真的會使用它嗎?java

一、各類請求的傳參有什麼注意事項?web

二、怎麼傳遞json格式參數spring

三、怎麼修改底層的http鏈接方式,怎麼添加線程池配置apache

四、怎麼結合ribbon實現負載均衡調用編程

五、怎麼自定義轉換器json

若是這些你都不瞭解,那麼這篇文章多是你須要的。
api

概述

spring框架提供的RestTemplate類可用於在應用中調用rest服務,它簡化了與http服務的通訊方式,統一了RESTful的標準,封裝了http連接, 咱們只須要傳入url及返回值類型便可。相較於以前經常使用的HttpClient,RestTemplate是一種更優雅的調用RESTful服務的方式。微信

在Spring應用程序中訪問第三方REST服務與使用Spring RestTemplate類有關。RestTemplate類的設計原則與許多其餘Spring *模板類(例如JdbcTemplate、JmsTemplate)相同,爲執行復雜任務提供了一種具備默認行爲的簡化方法。併發

RestTemplate默認依賴JDK提供http鏈接的能力(HttpURLConnection),若是有須要的話也能夠經過setRequestFactory方法替換爲例如 Apache HttpComponents、Netty或OkHttp等其它HTTP library。

考慮到RestTemplate類是爲調用REST服務而設計的,所以它的主要方法與REST的基礎緊密相連就不足爲奇了,後者是HTTP協議的方法:HEAD、GET、POST、PUT、DELETE和OPTIONS。例如,RestTemplate類具備headForHeaders()、getForObject()、postForObject()、put()和delete()等方法。

實現

最新api地址:
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html

RestTemplate包含如下幾個部分:
HttpMessageConverter 對象轉換器
ClientHttpRequestFactory 默認是JDK的HttpURLConnection
ResponseErrorHandler 異常處理
ClientHttpRequestInterceptor 請求攔截器

get請求

請求接口

    @GetMapping("/users")
    public String getUser1(String username, HttpServletRequest request){
        return "獲取用戶"+username+"的信息";
    }

    @GetMapping("/users/{username}")
    public String getUser2(@PathVariable String username){
        return "獲取用戶"+username+"的信息";
    }

get請求

 @Test
    public void getRequestTest(){
        RestTemplate template = new RestTemplate();
        String url = "http://127.0.0.1:8080/users?username={username}";
        String url2 = "http://127.0.0.1:8080/users/laowan";

        //一、使用getForObject請求接口,  順序傳入參數
        String result1 = template.getForObject(url, String.class, "laowan");
        System.out.println("result1====================" + result1);

        //二、使用getForObject請求接口   使用HashMap傳入參數
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put("username""laowan");
        String result2 = template.getForObject(url, String.class, paramMap);
        System.out.println("result2====================" + result1);

        //三、使用url路徑變量PathVariable
        String result3 = template.getForObject(url2, String.class);
        System.out.println("result3====================" + result1);

        ResponseEntity<String> responseEntity = template.getForEntity(url2, String.class);
        System.out.println("getForEntity請求====================" + responseEntity.getBody());

        //四、使用exchange請求接口
        ResponseEntity<String> response2 = template.exchange(url, HttpMethod.GET, null, String.class,paramMap);
        System.out.println("result4====================" + response2.getBody());

        //五、使用exchange請求接口,能夠封裝HttpEntity對象傳遞header參數
        HttpHeaders headers = new HttpHeaders();
        headers.set("username""laowan");
        HttpEntity httpEntity = new HttpEntity(null,headers);
        ResponseEntity<String> response5 = template.exchange(url, HttpMethod.GET, httpEntity, String.class,paramMap);
        System.out.println("result5====================" + response5.getHeaders());
    }

注意事項

  1. get請求的參數傳遞,必定要在url後面拼接,可使用?號後面拼接,也能夠採用路徑參數。

  2. 傳遞參數有2中方法,一種是傳入多個參數值,會按順序填充到url後面的參數佔位符中,一種是採用map傳入多個參數,這時必定要使用HashMap

  3. get請求若是須要傳遞header參數,必定要採用exchange方法,封裝HttpEntity對象


post請求

請求接口

  @PostMapping("/user")
    public User addUser(String username,Integer age){
        User user = new User();
        user.setAge(age);
        user.setUsername(username);
        log.info("新增用戶{}", JSON.toJSONString(user));
        return user;
    }

    @PostMapping("/user/add")
    public User addUser(User user){
        log.info("新增用戶{}", JSON.toJSONString(user));
        return user;
    }

    @PostMapping("/user/json")
    public User addUserJson(@RequestBody  User user){
        log.info("新增用戶{}", JSON.toJSONString(user));
        return user;
    }

post請求

@Test
    public void postRequestTest() {
        String url = "http://127.0.0.1:8080/user";
        String url2 = "http://127.0.0.1:8080/user/add";

        RestTemplate template = new RestTemplate();
        MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
        paramMap.add("username""laowan");
        paramMap.add("age"22);
        User user = template.postForObject(url, paramMap, User.class);
        log.info("result1的結果爲:{}",user);

         user = template.postForObject(url2, paramMap, User.class);
        log.info("result2的結果爲:{}",user);
    }

    //postForEntity方式的json請求
    @Test
    public void postRequestTest4() {
        String url2 = "http://127.0.0.1:8080/user/json";
        RestTemplate restTemplate = new RestTemplate();
        String reqJsonStr = "{\"username\":\"laowan\", \"age\":\"12\"}";

        HttpHeaders headers = new HttpHeaders(); 
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<String>(reqJsonStr,headers);
        ResponseEntity<User> resp = restTemplate.postForEntity(url2,entity, User.class);
        log.info("result1的結果爲:{}",resp.getBody());
    }

    //經過exchange調用post請求,傳遞json格式參數
    @Test
    public void postRequestTest2() {
        String url2 = "http://127.0.0.1:8080/user/json";
        RestTemplate restTemplate = new RestTemplate();
        String reqJsonStr = "{\"username\":\"laowan\", \"age\":\"12\"}";
       HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<String>(reqJsonStr,headers);
        ResponseEntity<User> resp = restTemplate.exchange(url2, HttpMethod.POST, entity, User.class);
        log.info("result1的結果爲:{}",resp.getBody());
    }

    //經過exchange調用post請求,傳遞x-www-form-urlencoded格式參數
    @Test
    public void postRequestTest3() {
        String url2 = "http://127.0.0.1:8080/user/add";
        RestTemplate restTemplate = new RestTemplate();

        MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
        paramMap.add("username""laowan");
        paramMap.add("age"22);
        HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<MultiValueMap<String, Object>>(paramMap,headers);
        ResponseEntity<User> resp = restTemplate.exchange(url2, HttpMethod.POST, entity, User.class);
       if( resp.getStatusCode().equals( HttpStatus.OK )){
            log.info("result1的結果爲:{}",resp.getBody());
        }else{
            throw new RuntimeException( resp.toString() );
        }
    }

注意事項

  1. post請求傳遞普通參數,必定要使用LinkedMultiValueMap對參數進行封裝,否則會接收不到參數值。

  2. post請求能夠傳遞json格式參數,須要在頭部參數中指定headers.setContentType(MediaType.APPLICATION_JSON);

  3. 返回ResponseEntity結果,主要是能夠從中獲取返回的狀態碼和請求頭信息。能夠方便對請求結果進行斷定,對請求異常進行處理。

       if( resp.getStatusCode().equals( HttpStatus.OK )){
            log.info("result1的結果爲:{}",resp.getBody());
        }else{
            throw new RuntimeException( resp.toString() );
        }

結合ribbon使用

RestTemplate負載均衡示例
核心是經過@LoadBalanced註解聲明RestTemplate實例,主要邏輯是給RestTemplate增長攔截器,在請求以前對請求的地址進行替換,或者根據具體的負載均衡策略選擇服務地址,而後再去調用,這就是@LoadBalanced的原理。

引入jar包依賴
也能夠不引用,eureka中已經引用了ribbon

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>

將服務rest註冊到Eureka
spring.application.name=rest

使用@LoadBalanced註解聲明RestTemplate實例

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

調用接口
修改調用的url,將IP+PORT改成服務名稱,也就是註冊到Eureka的名稱。

@SpringBootTest
class RestApplicationTests {
    @Autowired
    RestTemplate restTemplate;

    @Test
    void restRibbonTest() {
        String result3 = restTemplate.getForObject("http://rest/users/laowan", String.class);
    }
}

設置底層鏈接方式

RestTemplate默認依賴JDK提供http鏈接的能力(HttpURLConnection),若是有須要的話也能夠經過setRequestFactory方法替換爲例如 Apache HttpComponents、Netty或OkHttp等其它HTTP library。
實現原理是經過RestTemplate(ClientHttpRequestFactory requestFactory)的構造方法,指定requestFactory。

常規配置

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(RestTemplateBuilder restTemplateBuilder){
        return  restTemplateBuilder
                .basicAuthentication("username""password")
                .setConnectTimeout(Duration.ofSeconds(3000))
                .setReadTimeout(Duration.ofSeconds(5000))
                .rootUri("http://api.example.com/")
                .build();
       // return new RestTemplate();
    }

改用httpclient的實現

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.4.1</version>
</dependency>
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
      //   restTemplate.setRequestFactory(okHttpClient());
        restTemplate.setRequestFactory(clientHttpRequestFactory());
        //restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        return restTemplate;
    }

@Bean
    public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
        try {
            HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(nullnew TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                    return true;
                }
            }).build();
            httpClientBuilder.setSslcontext(sslContext);
            HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
            // 註冊http和https請求
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslConnectionSocketFactory).build();
            // 開始設置鏈接池
            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            poolingHttpClientConnectionManager.setMaxTotal(500); // 最大鏈接數500
            poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100); // 同路由併發數100
            httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3true)); // 重試次數

            HttpClient httpClient = httpClientBuilder.build();
            HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); // httpClient鏈接配置
            clientHttpRequestFactory.setConnectTimeout(20000);              // 鏈接超時
            clientHttpRequestFactory.setReadTimeout(30000);                 // 數據讀取超時時間
            clientHttpRequestFactory.setConnectionRequestTimeout(20000);    // 鏈接不夠用的等待時間
            return clientHttpRequestFactory;
        } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
           // log.error("初始化HTTP鏈接池出錯", e);
        }
        return null;
    }
}

改用okhttp的實現

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>3.11.0</version>
</dependency>
@Configuration
public class OkHttpConfig {

    @Bean
    public OkHttpClient okHttpClient() {
        return new OkHttpClient.Builder()
                .sslSocketFactory(sslSocketFactory(), x509TrustManager())
                .retryOnConnectionFailure(false)
                .connectionPool(pool())
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30,TimeUnit.SECONDS)
                .build();
    }

    @Bean
    public X509TrustManager x509TrustManager() {
        return new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };
    }

    @Bean
    public SSLSocketFactory sslSocketFactory() {
        try {
            //信任任何連接
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(nullnew TrustManager[]{x509TrustManager()}, new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Bean
    public ConnectionPool pool() {
        return new ConnectionPool(2005, TimeUnit.MINUTES);
    }
}

能夠發現,在使用httpclient和okhttp均可以配置鏈接池connectionPool,相信可以在必定程度上提升http請求的速度。

手動指定轉換器

調用reseful接口傳遞的數據內容是json格式的字符串,返回的響應也是json格式的字符串。然而restTemplate.postForObject方法的請求參數RequestBean和返回參數ResponseBean卻都是java類。
是RestTemplate經過HttpMessageConverter自動幫咱們作了轉換的操做。
默認狀況下RestTemplate自動幫咱們註冊了一組HttpMessageConverter用來處理一些不一樣的contentType的請求。
如StringHttpMessageConverter來處理text/plain;
MappingJackson2HttpMessageConverter來處理application/json;
MappingJackson2XmlHttpMessageConverter來處理application/xml。

RestTemplate的無參構造方法中的轉化器配置部分源碼:

 this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
        if (romePresent) {
            this.messageConverters.add(new AtomFeedHttpMessageConverter());
            this.messageConverters.add(new RssChannelHttpMessageConverter());
        }

        if (jackson2XmlPresent) {
            this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
        } else if (jaxb2Present) {
            this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }

        if (jackson2Present) {
            this.messageConverters.add(new MappingJackson2HttpMessageConverter());
        } else if (gsonPresent) {
            this.messageConverters.add(new GsonHttpMessageConverter());
        } else if (jsonbPresent) {
            this.messageConverters.add(new JsonbHttpMessageConverter());
        }

你能夠在org.springframework.http.converter包下找到全部spring幫咱們實現好的轉換器。
若是現有的轉換器不能知足你的需求,你還能夠實現org.springframework.http.converter.HttpMessageConverter接口本身寫一個。詳情參考官方api。
選好了HttpMessageConverter後怎麼把它註冊到咱們的RestTemplate中呢。

 RestTemplate restTemplate = new RestTemplate();
    //獲取RestTemplate默認配置好的全部轉換器
    List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
    //默認的MappingJackson2HttpMessageConverter在第7個 先把它移除掉
    messageConverters.remove(6);
    //添加上GSON的轉換器
    messageConverters.add(6new GsonHttpMessageConverter());

這個簡單的例子展現瞭如何使用GsonHttpMessageConverter替換掉默認用來處理application/json的MappingJackson2HttpMessageConverter。

總結

一、RestTemplate的常規調用方法和注意事項
二、RestTemplate結合Ribbon的負載均衡調用
三、RestTemplate底層鏈接的設置
四、RestTemplate的轉換器設置

參考:
https://blog.csdn.net/u014692324/article/details/98876094
https://blog.csdn.net/LDY1016/article/details/80002126

更多精彩,關注我吧。

圖注:跟着老萬學java


本文分享自微信公衆號 - 跟着老萬學java(douzhe_2019)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索