Springcloud学习笔记42--利用HttpClient实现服务A向服务B发送Http请求


1.服务A发送Http请求

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

http:
  maxTotal: 100
  defaultMaxPerRoute: 20
  connectTimeout: 1000
  connectionRequestTimeout: 500
  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();
    }
}

1.3 HttpClientUtil

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

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

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 map) throws Exception{
        HttpPost httpPost=new HttpPost(url);
        httpPost.setConfig(requestConfig);
        if(map!=null){
            List list=new ArrayList<>();
            for (Map.Entry 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 map) throws Exception{
        URIBuilder uriBuilder=new URIBuilder(url);
        if(map!=null){
            for (Map.Entry 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 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 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

2.服务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