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

欢迎关注博主公众号

相关文章
相关标签/搜索