Springcloud学习笔记42(Part01)--封装HttpClienUtil, 实现服务A向服务B发送Http请求

1.HttpClientUtil 工具类封装

1.1 在yml文件中配置Http的相关参数

http:
  maxTotal: 100
  defaultMaxPerRoute: 20
  connectTimeout: 1000
  connectionRequestTimeout: 5000
  socketTimeout: 10000
  staleConnectionCheckEnable: true

1.2 编写HttpClient的相关配置类

1.2.1  @Configuration

@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

  • @Configuration配置spring并启动spring容器
  • @Configuration启动容器+@Bean注册Bean
  • @Configuration启动容器+@Component注册Bean

使用 AnnotationConfigApplicationContext 注册 AppContext 类的两种方法
配置Web应用程序(web.xml中配置AnnotationConfigApplicationContext)

注意:@Configuration注解的类在springboot启动时就会加载;

2022-03-25 15:15:51.116 | INFO  | main | org.springframework.web.context.ContextLoader:283 | [] - Root WebApplicationContext: initialization completed in 1270 ms
//此时,进入断点
2022-03-25 15:41:08.945 | INFO  | main | com.ttbank.flep.core.FileFlepApplication:59 | [] - Started FileFlepApplication in 238.843 seconds (JVM running for 240.545)

在Root WebApplicationContext: initialization completed启动完成之后,会进入debug状态;

1.3.2 @Component和@Bean注解

@Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean。

@Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。通常方法体中包含了最终产生bean实例的逻辑。

两者的目的是一样的,都是注册bean到Spring容器中。

复制代码
package com.ttbank.flep.core.config;

import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class HttpClientConfig {
    @Value("${http.maxTotal}")
    private Integer maxTotal;

    @Value("${http.defaultMaxPerRoute}")
    private Integer defaultMaxPerRoute;

    @Value("${http.connectTimeout}")
    private Integer connectTimeout;

    @Value("${http.connectionRequestTimeout}")
    private Integer connectionRequestTimeout;

    @Value("${http.socketTimeout}")
    private Integer socketTimeout;

    @Value("${http.staleConnectionCheckEnable}")
    private boolean staleConnectionCheckEnable;

    /**
     * 首先实例化一个连接管理器,设置最大连接数,并发数
     */
    @Bean(name="httpClientConnectionManager")
    public PoolingHttpClientConnectionManager getHttpClientConnectionManager(){
        PoolingHttpClientConnectionManager httpClientConnectionManager=new PoolingHttpClientConnectionManager();
        //最大连接数
        httpClientConnectionManager.setMaxTotal(maxTotal);
        //并发数
        httpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
        return httpClientConnectionManager;

    }

    /**
     * 实例化连接池
     */
    @Bean(name="httpClientBuilder")
    public HttpClientBuilder getHttpClientBuilder(@Qualifier("httpClientConnectionManager")PoolingHttpClientConnectionManager httpClientConnectionManager){
        HttpClientBuilder httpClientBuilder=HttpClientBuilder.create();
        httpClientBuilder.setConnectionManager(httpClientConnectionManager);
        //以下可不设置,如果指定了要清理的过期连接,才会启动清理线程,默认不启动。会有一个单独的线程来扫描连接池中的连接,发现离最近一次使用超时设置后,就会清理。
        //设置是否清理过期连接
        httpClientBuilder.evictExpiredConnections();
        //连接存活时间
        httpClientBuilder.setConnectionTimeToLive(10000, TimeUnit.SECONDS);
        //设置清理空闲连接的时间
        httpClientBuilder.evictIdleConnections(60000, TimeUnit.MILLISECONDS);
        return httpClientBuilder;
    }


    /**
     * 注入连接池,用于获取httpclient
     */
    @Bean
    public CloseableHttpClient getCloseableHttpClient(@Qualifier("httpClientBuilder")HttpClientBuilder httpClientBuilder){
        return httpClientBuilder.build();
    }

    /**
     * Builder是RequestConfig的一个内部类,利用custom方法可获取一个Builder对象
     * 设置builder的连接信息
     */
    @Bean(name="builder")
    public RequestConfig.Builder getBuilder(){
        RequestConfig.Builder builder = RequestConfig.custom();
        return builder.setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .setSocketTimeout(socketTimeout);
    }

    /**
     * 使用builder构建一个RequestConfig对象
     */
    @Bean
    public RequestConfig getRequestConfig(@Qualifier("builder") RequestConfig.Builder builder){
        return builder.build();
    }
}
复制代码

使用httpclient作为http请求的客户端时,我们一般都会设置超时时间,这样就可以避免因为接口长时间无响应或者建立连接耗时比较久导致自己的系统崩溃。通常在RequestConfig里面设置。

setConnectionRequestTimeout():从httpclient连接池中获取连接的超时时间,如果连接池中的连接都被占用,那么超过该时间还未获取到连接就会抛出异常。
setConnectTimeout():建立连接发生三次握手时的超时时间,如果超过该设置的时间连接还未建立成功就会抛出异常。
setSocketTimeout():读取数据超时时间,注意这个时间不是接口响应耗时时间,是接口返回数据两次数据包之间间隔时间,如果超过这个时间间隔还没有新的数据返回,那么就会抛出异常。

1.3 HttpClientUtil

采用@Component注解将这个类交给spring容器管理;因此,HttpClientUtil类中的两个成员变量httpClient,requestConfig可以由spring完成对象的注入;

注意:使用HttpClientUtil类,必须采用@autowired注解进行注入,不能采用new 对象的方式;获取spring托管的bean

复制代码
package com.ttbank.flep.core.util;

import com.ttbank.flep.core.dto.Result;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Author lucky
 * @Date 2022/3/25 9:23
 */
@Component
public class HttpClientUtil {
    @Autowired
    private CloseableHttpClient httpClient;

    @Autowired
    private RequestConfig requestConfig;

    public Result doPost(String url) throws Exception{
        return this.doPost(url,null);
    }

    /**
     * 带参数的post方法
     * @param url
     * @param map
     */
    public Result doPost(String url, Map<String, Object> map) throws Exception{
        HttpPost httpPost=new HttpPost(url);
        httpPost.setConfig(requestConfig);
        if(map!=null){
            List<NameValuePair> list=new ArrayList<>();
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                list.add(new BasicNameValuePair(entry.getKey(),entry.getValue().toString() ));
            }
            //构造form表单对象
            UrlEncodedFormEntity urlEncodedFormEntity=new UrlEncodedFormEntity(list,"UTF-8");
            //把表单放到post里
            httpPost.setEntity(urlEncodedFormEntity);
        }
        //发起请求
        CloseableHttpResponse response = this.httpClient.execute(httpPost);
        return new Result(String.valueOf(response.getStatusLine().getStatusCode()),"",EntityUtils.toString(response.getEntity(),"UTF-8"));
    }

    public Result doPostJson(String url,String json) throws Exception{
        HttpPost httpPost=new HttpPost(url);
        httpPost.setConfig(requestConfig);
        StringEntity entity=new StringEntity(json);
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept","application/json");
        httpPost.setHeader("Content-type","application/json");
        CloseableHttpResponse response=this.httpClient.execute(httpPost);
        return new Result(String.valueOf(response.getStatusLine().getStatusCode()),"",EntityUtils.toString(response.getEntity(),"UTF-8"));
    }

    public Result doPostFile(String url, String key,String params, InputStream fileInputStream) throws IOException {
        HttpPost httpPost=new HttpPost(url);
        httpPost.setConfig(requestConfig);
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.addBinaryBody("file",fileInputStream, ContentType.create("multipart/form-data"),"test" );
        StringBody body=new StringBody(params,ContentType.create("text/plain", Consts.UTF_8 ));
        builder.addPart(key,body);
        HttpEntity entity=builder.build();
        httpPost.setEntity(entity);
        //发起请求
        CloseableHttpResponse response = this.httpClient.execute(httpPost);
        return new Result(String.valueOf(response.getStatusLine().getStatusCode()),"",EntityUtils.toString(response.getEntity(),"UTF-8"));
    }

    /**
     * 不带参数的get请求
     * @param url
     */
    public Result doGet(String url) throws IOException {
        HttpGet httpGet=new HttpGet(url);
        //加载配置信息
        httpGet.setConfig(requestConfig);
        //发起请求
        CloseableHttpResponse response = this.httpClient.execute(httpGet);
        return new Result(String.valueOf(response.getStatusLine().getStatusCode()),"",EntityUtils.toString(response.getEntity(),"UTF-8"));
    }

    public Result doGet(String url,Map<String,Object> map) throws Exception{
        URIBuilder uriBuilder=new URIBuilder(url);
        if(map!=null){
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                uriBuilder.setParameter(entry.getKey(),entry.getValue().toString() );
            }
        }
        return this.doGet(uriBuilder.build().toString());
    }
}
复制代码

1.4 使用测试案例

复制代码
package com.ttbank.flep.core.controller;

import com.sun.media.jfxmedia.logging.Logger;
import com.ttbank.flep.core.util.HttpClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * @Author lucky
 * @Date 2022/3/25 11:41
 */
@RestController
@RequestMapping("/httpclient")
@Slf4j
public class HttpClientController {
    @Autowired
    HttpClientUtil httpClientUtil;

    @PostMapping("/sayHello")
    public void sayHello(){
        String url="http://127.0.0.1:7011/task/getAttr";
        Map<String,Object> paramMap=new HashMap<>();
        paramMap.put("name","lucky" );
        paramMap.put("address","tiantai" );
        try {
            httpClientUtil.doPost(url,paramMap);
            log.info("send OK");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @GetMapping("/sayBye")
    public void sayBye(){
        String url="http://127.0.0.1:7011/task/getInfo";
        Map<String,Object> paramMap=new HashMap<>();
        paramMap.put("name","linda" );
        paramMap.put("address","hangzhou" );
        try {
            httpClientUtil.doGet(url,paramMap);
            log.info("send success");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
复制代码

1.5 postman调用

http://127.0.0.1:7010/httpclient/sayBye

1.6 服务B接收Http请求

复制代码
package com.ttbank.flep.task.controller;

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

/**
 * @Author lucky
 * @Date 2022/3/25 14:31
 */
@RestController
@RequestMapping("/task")
@Slf4j
public class TaskController {
    @PostMapping("/getAttr")
    public void getAttr(@RequestParam String name,@RequestParam String address){
        log.info(name+":"+address);
    }

    @GetMapping("/getInfo")
    public void getInfo(@RequestParam String name,@RequestParam String address){
        log.info(name+":"+address);
    }
}
复制代码

此时,控制台输出日志:

2022-03-25 17:25:04.985 | INFO  | http-nio-7011-exec-8 | com.ttbank.flep.task.controller.TaskController:19 | [] - lucky:tiantai
2022-03-25 17:50:42.897 | INFO  | http-nio-7011-exec-5 | com.ttbank.flep.task.controller.TaskController:21 | [] - linda:hangzhou

 2. 关闭HttpClient

2.1 关闭流

HttpEntity entity = response.getEntity();

InputStream is=entity.getContent();

可以使用is.close();将底层流关闭,也可以使用EntityUtils.consume(entity)把流给关闭。

2.2 关闭Httpclient的流程

(1) 自己配置的Http连接池

当使用了自己配置的Http连接池后,可以如下关闭

复制代码
        CloseableHttpClient client =   自定义的获取client客户端的方法;//注意此client做成单例

        CloseableHttpResponse  response = null;
        // 发送get请求
        HttpGet request = null;
        try{
            request = new HttpGet(url);
            response = client.execute(request);

            //业务代码
        }catch (Exception e){
            log.error("get请求提交失败:" + url, e);
        }
        finally{
           if(null != response ){
                try {
                    response .close();//此处就只关闭Response等底层流,而httpclient不用关闭,保持socket放入缓存池中
                } catch (IOException e) {
                    log.error("关闭失败:" , e);
                }
            }
        }
复制代码

(2) 每一次请求生成一个httpclient

复制代码
finally {
                if (null != response) {
                    try {
                        response.close();
                    }catch (IOException e) {
                        logger.log(Level.SEVERE, e.getMessage(), e);
                    }
                }
                if (null != client){
                    try {
                        client.close();
                    }catch (IOException e) {
                        logger.log(Level.SEVERE, e.getMessage(), e);
                    }
                }
            }
复制代码

 

 参考文献:

https://blog.csdn.net/u013305783/article/details/83105102 (推荐)

https://blog.csdn.net/yuhaibao324/article/details/92082164

https://blog.csdn.net/I_am_hardy/article/details/123947516 (HTTP请求头大小写问题)

https://blog.csdn.net/u010285974/article/details/85696239 (推荐)

https://blog.csdn.net/javamine/article/details/103681059 (非常推荐)

 

posted @   雨后观山色  阅读(569)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
历史上的今天:
2020-03-25 深度学习笔记11-循环神经网络(RNN)和长短时记忆(LSTM)----非常经典
点击右上角即可分享
微信分享提示