SpringBoot基礎教程2-1-11 RestTemplate整合HttpClient

1 概述

Http請求在服務端開發中必不可少,本文使用RestTemplate作門面,HttpClient作實現,演示基礎的Http請求例子。html

2 源碼分析

2.1 添加pom.xml依賴

  • RestTemplateSpring-Web模塊中內置,SpringBoot自動引入
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.5</version>
</dependency>
<!-- 若是不配異步(AsyncRestTemplate),則不須要這個依賴 -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.5.Final</version>
</dependency>

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

2.2 配置文件application.yml(可選)

# yml配置的優先級高於java配置;若是yml配置和java配置同時存在,則yml配置會覆蓋java配置
####restTemplate的yml配置開始####
---
spring:
  restTemplate:
    maxTotalConnect: 1000 #鏈接池的最大鏈接數,0表明不限;若是取0,須要考慮鏈接泄露致使系統崩潰的後果
    maxConnectPerRoute: 200
    connectTimeout: 3000
    readTimeout: 5000
    charset: UTF-8
####restTemplate的 yml配置開始####

2.3 編寫RestTemplate配置(必備)

// 必備
@Configuration
@ConfigurationProperties(prefix = "spring.restTemplate")
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
public class RestTemplateConfig {

    // java配置的優先級低於yml配置;若是yml配置不存在,會採用java配置
    // ####restTemplate的 java配置開始####
    private int maxTotalConnection = 500; //鏈接池的最大鏈接數

    private int maxConnectionPerRoute = 100; //同路由的併發數

    private int connectionTimeout = 2 * 1000; //鏈接超時,默認2s

    private int readTimeout = 30 * 1000; //讀取超時,默認30s

    private String charset = "UTF-8";

    public void setMaxTotalConnection(int maxTotalConnection) {
        this.maxTotalConnection = maxTotalConnection;
    }

    public void setMaxConnectionPerRoute(int maxConnectionPerRoute) {
        this.maxConnectionPerRoute = maxConnectionPerRoute;
    }

    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }

    //建立HTTP客戶端工廠
    @Bean(name = "clientHttpRequestFactory")
    public ClientHttpRequestFactory clientHttpRequestFactory() {
        return createClientHttpRequestFactory(this.connectionTimeout, this.readTimeout);
    }

    //初始化RestTemplate,並加入spring的Bean工廠,由spring統一管理
    @Bean(name = "restTemplate")
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return createRestTemplate(factory);
    }

    //初始化支持異步的RestTemplate,並加入spring的Bean工廠,由spring統一管理
    //若是你用不到異步,則無須建立該對象
    @Bean(name = "asyncRestTemplate")
    @ConditionalOnMissingBean(AsyncRestTemplate.class)
    public AsyncRestTemplate asyncRestTemplate(RestTemplate restTemplate) {
        final Netty4ClientHttpRequestFactory factory = new Netty4ClientHttpRequestFactory();
        factory.setConnectTimeout(this.connectionTimeout);
        factory.setReadTimeout(this.readTimeout);
        return new AsyncRestTemplate(factory, restTemplate);
    }

    private ClientHttpRequestFactory createClientHttpRequestFactory(int connectionTimeout, int readTimeout) {
        //maxTotalConnection 和 maxConnectionPerRoute 必需要配
        if (this.maxTotalConnection <= 0) {
            throw new IllegalArgumentException("invalid maxTotalConnection: " + maxTotalConnection);
        }
        if (this.maxConnectionPerRoute <= 0) {
            throw new IllegalArgumentException("invalid maxConnectionPerRoute: " + maxTotalConnection);
        }

        //全局默認的header頭配置
        List<Header> headers = new LinkedList<>();
        headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
        headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en;q=0.6"));

        //禁用自動重試,須要重試時,請自行控制
        HttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler(0, false);

        //建立真正處理http請求的httpClient實例
        CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultHeaders(headers)
                .setRetryHandler(retryHandler)
                .build();

        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
                httpClient);
        factory.setConnectTimeout(connectionTimeout);
        factory.setReadTimeout(readTimeout);
        return factory;
    }

    private RestTemplate createRestTemplate(ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate(factory);

        //咱們採用RestTemplate內部的MessageConverter
        //從新設置StringHttpMessageConverter字符集,解決中文亂碼問題
        modifyDefaultCharset(restTemplate);

        //設置錯誤處理器
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());

        return restTemplate;
    }

    private void modifyDefaultCharset(RestTemplate restTemplate) {
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        HttpMessageConverter<?> converterTarget = null;
        for (HttpMessageConverter<?> item : converterList) {
            if (StringHttpMessageConverter.class == item.getClass()) {
                converterTarget = item;
                break;
            }
        }
        if (null != converterTarget) {
            converterList.remove(converterTarget);
        }
        Charset defaultCharset = Charset.forName(charset);
        converterList.add(1, new StringHttpMessageConverter(defaultCharset));
    }
}
  • 作完上述配置,就生成了可用的RestTemplate實例

2.4 Get請求演示

@Slf4j
@RestController
public class GetTestController {

    @Resource
    private RestTemplate restTemplate;

    //最簡單的get操做
    @GetMapping("/baidu1/{key}")
    public String get1(@PathVariable String key) throws UnsupportedEncodingException {
        String encodeKey = URLEncoder.encode(key, "UTF-8");

        String url = "http://www.baidu.com/s?bdorz_come=1&ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=" + encodeKey;

        return restTemplate.getForObject(url, String.class); //返回百度主站html
    }

    //須要自定義header頭的get操做
    @GetMapping("/baidu2/{key}")
    public String get2(@PathVariable String key) throws UnsupportedEncodingException {
        HttpHeaders headers = new HttpHeaders();

        headers.set("MyHeaderKey", "MyHeaderValue");

        HttpEntity entity = new HttpEntity(headers);

        String encodeKey =URLEncoder.encode(key, "UTF-8");

        String url = "http://www.baidu.com/s?bdorz_come=1&ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=" + encodeKey;

        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);

        return  response.getBody(); //返回百度主站html
    }

}

2.5 Post請求演示

@Slf4j
@RestController
public class PostTestController {

    @Resource
    private RestTemplate restTemplate;

    //post表單演示
    @GetMapping("/postForm")
    public String testPostForm() {
        // 填寫url
        String url = "";
        MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();

        // 填寫表單
        form.add("name", "**");
        form.add("age", "**");

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        //headers.add("xx", "yy");//能夠加入自定義的header頭
        HttpEntity<MultiValueMap<String, String>> formEntity = new HttpEntity<>(form, headers);
        String json = restTemplate.postForObject(url, formEntity, String.class);
        return json;
    }

    @RequestMapping("/postBody")
    public String testPostBody() {
        // 填寫url
        String url = "";

        // 填寫json串
        String jsonBody = "{\n"
                + "    \"name\": \"XX\",\n"
                + "    \"age\": \"12\",\n"
                + "    \"sex\": \"man\"\n"
                + "}\n";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        //headers.add("xx", "yy");//能夠加入自定義的header頭
        HttpEntity<String> bodyEntity = new HttpEntity<>(jsonBody, headers);

        //1.直接拿原始json串
        String json = restTemplate.postForObject(url, bodyEntity, String.class);

        //2.將原始的json傳轉成java對象,rest template能夠自動完成
        ResultVo resultVo = restTemplate.postForObject(url, bodyEntity, ResultVo.class);
        if (resultVo != null && resultVo.success()) {
            Object res = resultVo.getData();
            log.info("處理成功,返回數據: {}", resultVo.getData());
        } else {
            log.info("處理失敗,響應結果: {}", resultVo);
        }

        return json;//返回的是分包api的json
    }
    
}

2.6 文件上傳與下載請求演示

@Slf4j
@RestController
public class FileTestController {
    @Resource
    private RestTemplate restTemplate;

    // post文件上傳
    // 場景說明:只適合小文件(20MB之內)上傳
    @RequestMapping("/postFile")
    public String testPostFileBody() {
        String filePath = "D:/config.png";

        //經過磁盤文件上傳,若是產生了臨時文件,必定要記得刪除,不然,臨時文件越積越多,磁盤會爆
        FileSystemResource resource = new FileSystemResource(new File(filePath));

        String url = "***";//測試的時候換成本身的配置
        String appId = "***";//測試的時候換成本身的配置
        String secureKey = "***";//測試的時候換成本身的配置
        String time = String.valueOf(System.currentTimeMillis());
        String pubStr = "1";
        String tempStr = String.format("app_id=%s&is_public=%s&time=%s&vframe=0%s", appId, pubStr, time, secureKey);
        MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
        form.add("is_public", pubStr);
        form.add("vframe", "0");
        form.add("file", resource);
        form.add("app_id", appId);
        form.add("time", time);
        form.add("sign", DigestUtils.md5(tempStr));

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        //headers.add("xx", "yy");//能夠加入自定義的header頭
        HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(form, headers);
        String json = restTemplate.postForObject(url, formEntity, String.class);
        return json;
    }

    //文件下載
    //場景說明:只適合小文件(10MB之內)下載
    @RequestMapping("/downloadFile")
    public ResponseEntity testDownloadFile() throws Exception {
        String url = "http://editor.baidu.com/editor/download/BaiduEditor(Online)_5-9-16.exe";
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM));
        HttpEntity<String> entity = new HttpEntity<>(headers);
        ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.GET, entity, byte[].class);
        byte[] bytes = response.getBody();
        long contentLength = bytes != null ? bytes.length : 0;
        headers.setContentLength((int) contentLength);
        headers.setContentDispositionFormData("baidu.exe", URLEncoder.encode("百度安裝包.exe", "UTF-8"));
        return new ResponseEntity<>(response.getBody(), headers, HttpStatus.OK);
    }
}

3 採坑記錄

3.1 只配@ConfigurationProperties時,不會自動建立bean

正確姿式:java

@Configuration
@ConfigurationProperties(prefix = "spring.restTemplate")
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
public class RestTemplateConfig {
}

錯誤姿式:git

@ConfigurationProperties(prefix = "spring.restTemplate")
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
public class RestTemplateConfig {
}

3.2 @ConfigurationProperties沒法注入沒有setter的屬性

3.3 RestTemplate默認配置會亂碼

正確姿式:github

private RestTemplate createRestTemplate(ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate(factory);

        //咱們採用RestTemplate內部的MessageConverter
        //從新設置StringHttpMessageConverter字符集,解決中文亂碼問題
        modifyDefaultCharset(restTemplate);

        //設置錯誤處理器
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());

        return restTemplate;
    }

    private void modifyDefaultCharset(RestTemplate restTemplate) {
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        HttpMessageConverter<?> converterTarget = null;
        for (HttpMessageConverter<?> item : converterList) {
            if (StringHttpMessageConverter.class == item.getClass()) {
                converterTarget = item;
                break;
            }
        }
        if (null != converterTarget) {
            converterList.remove(converterTarget);
        }
        Charset defaultCharset = Charset.forName(charset);
        converterList.add(1, new StringHttpMessageConverter(defaultCharset));
    }

錯誤姿式:spring

@Bean
public RestTemplate getRestTemplate(){
    RestTemplate rest = new RestTemplate(this.createFactory);
    return rest;
}

4 如何調試RestTemplate

  • 能夠在logback裏單獨配一個debug級別的logger,把org.apache.http下面的日誌定向到控制檯:
<logger name="org.apache.http" level="DEBUG" additivity="false">
    <appender-ref ref="STDOUT" />
</logger>

5 工程目錄

6 結束語

說點什麼呢,有任何建議,歡迎留言探討,本文源碼apache


歡迎關注博主公衆號,第一時間推送最新文章json

歡迎關注博主公衆號

相關文章
相關標籤/搜索