常用工具类

String

// 占位符带编号
String url = MessageFormat.format("https://search.bilibili.com/all?keyword={0}", name);
// 占位符不带编号
String url = String.format("https://search.bilibili.com/all?keyword=%s",name);
// 比较文本相似度:https://juejin.cn/post/6844903992812634119

// 业务实体通过反射覆写toString方法 ()  org.apache.commons.lang
public String toString() {
    return ReflectionToStringBuilder.toString(this);
}

Map

https://blog.csdn.net/p812438109/article/details/105758128

TreeMap:Map按key排序

Pattern/Matcher

https://blog.csdn.net/woaigaolaoshi/article/details/50970527

https://vimsky.com/examples/usage/matcher-hitend-method-in-java-with-examples.html

todo

Lists

// 拷贝list
List<TransferOrderBO> updateOrders = Lists.newArrayList(subOrders);
// 切片分组
List<List<T>> partition(List<T> list, int size);
// addAll方法一定要判空,否则会有NPE问题
list.addAll(null)

// 数组转List
//不可变数组
List<String> il = ImmutableList.of("string", "elements"); //推荐,需要用guava
Arrays.asList("element1", "element2");// 有坑:https://blog.csdn.net/youanyyou/article/details/106228442

//可变数组
List<String> l3 = Lists.newArrayList("or", "string", "elements"); //推荐,需要用guava
new ArrayList<>(Arrays.asList(array));

//List转数组
String[] strs = list.toArray(new String[list.size()]);
Integer[] a = list.toArray(new Integer[list.size()]);
int[] b = list.stream().mapToInt(Integer::valueOf).toArray();

//stdout
List<String> list = Arrays.asList("1", "2", "3");
System.out.println("list=" + list); //list=[1, 2, 3] ,已经做了重载

StringUtils

依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

用法

// 判空
StringUtils.isAnyBlank("str1","str2","str3",...)
// A is In (B,C,D...)
StringUtils.equalsAnyIgnoreCase("baseStr","strA","strB",...)
// 分割字符串,直接看源码方法上的注释
// 按字符分割
StringUtils.split(String str, char separatorChar)
// 按字符串分割,("ab   de fg", null) = ["ab","de","fg"]
StringUtils.splitByWholeSeparator(String str, String separator)
// 按字符串分割,("ab   de fg", null) = ["ab", "", "", "de", "fg"]
StringUtils.splitByWholeSeparatorPreserveAllTokens(String str, String separator)

// 统计字符串中子串出现的次数
StringUtils.countMatches()
// 字符串是否包含 字符/子串
StringUtils.contains()

// 字符串首字母大写以及小写
StringUtils.capitalize()/StringUtils.uncapitalize()

// 转化默认字符串
StringUtils.defaultIfEmpty("str", "defaultStr");

// 拼接字符串,List<String>扁平化
StringUtils.join(list, ";")

StringEscapeUtils

依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.9</version>
</dependency>

用法

// 去除转义
StringEscapeUtils.unescapeJava(url/json) 

NumberUtils

// 判断字符串是否是数字
NumberUtils.isNumber("5.96");//结果是true
NumberUtils.isNumber("s5");//结果是false
NumberUtils.isNumber("0000000000596");//结果是true

// 判断字符串中是否全为数字
NumberUtils.isDigits("0000000000.596");//false
NumberUtils.isDigits("0000000000596");//true

FileUtils

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

用法

FileUtils.sizeOf(File file)

Collections

// 空集合,单例
Collections.emptyMap()
Collections.emptyList()
Collections.emptySet()
catch (Exception e) {
  ...
	return Collections.emptyMap(); //单例,不要用Maps.newHashMap(),防止大量错误打满内存
}

// 不可修改集合。调用修改数据的方法,代码就会抛出UnsupportedOperationException异常
List<T> listA = Lists.newArrayList(T1,T2,..);
Collections.unmodifiableList(listA);

// 返回两个集合是否有交集,有交集返回false,否则返回true
boolean Collections.disjoint(Collection<?> c1, Collection<?> c2)

// 求差集(a-b)
Collection subtract = CollectionUtils.subtract(a, b);
// 求交集
Collection intersection = CollectionUtils.intersection(a, b);
// 求并集
Collection union = CollectionUtils. union(a, b);

// 乱序
Collections.shuffle(questionBank.choiceQuestionList);

Pair

函数返回值有两个数据时,无需定义结构体

import javafx.util.Pair;
private Pair<Integer, Integer> queryMinAndMaxId() {
  //TODO select min(id),max(id) from xxx
  return null;
}

Optional

https://www.cnblogs.com/mengw/p/13793712.html

// 让函数返回Optional对象,由调用方去判断
public static <T> Optional<T> op(){
		return Optional.empty(); 
    return (Optional<T>)Optional.of(data); //data==null抛空指针异常
    return (Optional<T>)Optional.ofNullable(json); //data==null返回Optional.empty()
}

// map和flatMap
// 参数都接收函数,返回对象实例都由Optional包装
// flatMap接收函数接口的返回类型为Optional,map接收其他类型
// flatMap:将多个Optional嵌套缩减成一层Optional嵌套
// https://xiaoyyu.blog.csdn.net/article/details/103251436
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value));//直接返回Function执行的结果
    }
}
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value));//会使用Optional的ofNullable方法包装Function函数返回的值
    }
}

Stream

Stream分析:https://blog.csdn.net/justloveyou_/article/details/79562574 最后总结的Stream特性较为经典

Stream分析+源码:https://blog.csdn.net/qq_36263268/article/details/113175067

// map是Intermediate操作,不修改源里的数据
List<String> resultList = new ArrayList<>();
List<String> ano = new ArrayList<>();
resultList.add("a");
resultList.stream().map(str -> ano.add(str));
System.out.println(ano.size()); // output:0
resultList.stream().forEach(str -> ano.add(str));
System.out.println(ano.size()); // output:1

example

Stream操作大全:https://blog.csdn.net/huangjhai/article/details/107852137

// 数组和Stream转化
Stream<T> stream = Arrays.stream(T[]);
T[] array = list.stream().toArray(new T[size]);

stream().anyMatch()
stream().allMatch()
stream().noneMatch()
stream().max()
stream().min()
stream().distinct()

// 连续判空
if (Stream.of(request.getShowIdList(), request.getVideoIdList()).allMatch(CollectionUtils::isEmpty)) {
  ...
}

// 判空和数据操作
List<Long> orderIds = Optional.ofNullable(orderResponse)
                             .map(OrderResponse::getOrderList)
                             .map(list -> list.stream() //list为空也不会抛异常
                                              .map(Order::getId)
                                              .collect(Collectors.toList()))
                             .orElse(null);

List<BasicNameValuePair> pairs = Optional.ofNullable(req)
                                         .map(PostFormHttpReq::getParams)
                                         .map(map -> map.entrySet().stream()
                                                        .map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
                                                        .collect(Collectors.toList()))
                                         .orElse(Lists.newArrayList());

// 将list<DTO>的DTO中某个元素抽取出来作为新List
List<Long> voucherIds = Optional.ofNullable(orderResp)
  															.map(OrderResponse::getSubOrders)
            										.map(
  																	list -> list.stream().map(SubOrderDTO::getVoucherId).collect(Collectors.toList())
																).orElse(null);

showList.stream()
        .map(ShowBO::getDetails)
        .map(list -> list.stream().map(xxx).collect(Collectors.toList()))
        .collect(Collectors.toList());

// 聚合为Map
Map<K, V> cacheResult = resultList.stream()
   .collect(Collectors.toMap(
       item -> buildKey(return K),
       item -> buildValue(return V),
       (pre, cur) -> pre
   ));

Stream.foreach

foreach:https://segmentfault.com/a/1190000021006514

foreach和map的区别:https://developer.aliyun.com/article/694263

stream().foreach和Collection.forEach区别:https://baijiahao.baidu.com/s?id=1637952388544934539&wfr=spider&for=pc

生成一个新的对象的时候,使用 map 会更好;只是操作 list 内部的对象时,用 forEach

map方法接收一个功能型接口,功能型接口接收一个参数,返回一个值。map 方法的用途是将旧数据转换后变为新数据,是一种 1:1 的映射,每个输入元素按照规则转换成另一个元素。该方法是 Intermediate 操作

forEach方法并不保证元素消费的先后顺序,forEachOrdered保证元素顺序消费

forEach不能修改自己包含的本地变量值

Stream.flatMap

https://zhaoshuxiang.blog.csdn.net/article/details/106083097

https://www.cnblogs.com/bonelee/p/7814563.html

// List<Map>扁平化为Map
List<Map<String, String>> parallelResult = ...;        
return parallelResult.stream().filter(Objects::nonNull)
    .flatMap(m -> m.entrySet().stream())
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b));

// List<List<Object>>扁平化为List<Object>
List<List<String>> temp = ...;
List<String> result = temp.stream()
                          .flatMap(list -> list.stream())
                          .collect(Collectors.toList());

// 多级平整
Arrays.stream(response.getAllHeaders())  
      .flatMap(header -> Arrays.stream(header.getElements())
      .filter(Objects::nonNull))
      .collect(Collectors.toMap(HeaderElement::getName,HeaderElement::getValue,(a,b)->b));
// 等价于
Map<String, String> responseHeaders = Maps.newHashMap();
for (Header header : response.getAllHeaders()) {
    HeaderElement[] headerElements = header.getElements();
    if (Objects.nonNull(headerElements) && headerElements.length > 0) {
        for (HeaderElement headerElement : headerElements) {
            responseHeaders.put(headerElement.getName(), headerElement.getValue());
        }
    }
}

Stream.groupBy

// List按照某种关系聚合成Map<key,List>
Map<K, List<T>> groupListMap = orgList.stream()
  																		.collect(Collectors.groupingBy(T -> return K));

Map<Long, List<OrderBO>> transferIdSubList = orderList.stream()
            .collect(Collectors.groupingBy(bo -> bo.getBaseBO().getTransferId()));

Stream.reduce

https://blog.csdn.net/dalinsi/article/details/78093130

//集合元素求和
Integer sum = intList.stream().reduce(0, Integer::sum); // 0是初始值
Integer sum = intList.stream().reduce(0, (a,b)->a+b);
Integer sum = intList.stream().reduce(Integer::sum);
Integer sum = intList.stream().reduce((a,b)->a+b);
T reduce(T identity, BinaryOperator<T> accumulator);
/**
	T result = identity;   
	for (T element : this stream)       
    result = accumulator.apply(result, element)   
  return result;
*/

//求最大值,最小值
Optional<Integer> maxNum = intList.stream().reduce(Integer::max);
Optional<Integer> minNum = intList.stream().reduce(Integer::min);
//自定义比较符,两者比较返回seconds较大的节目实体
Optional<ShowBO> dstShow = showList.stream().reduce(
    (showA, showB) -> showA.getSeconds() > showB.getSeconds() ? showA : showB);

Stream.sorted

// 按工资升序排序(自然排序)
List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
    .collect(Collectors.toList());
// 按工资倒序排序
List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed()).map(Person::getName).collect(Collectors.toList());
// 先按工资再按年龄升序排序
List<String> newList3 = personList.stream().sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName).collect(Collectors.toList());
// 先按工资再按年龄自定义排序(降序)
List<String> newList4 = personList.stream().sorted((p1, p2) -> {
    if (p1.getSalary() == p2.getSalary()) {
        return p2.getAge() - p1.getAge();
    } else {
        return p2.getSalary() - p1.getSalary();
    }
  }).map(Person::getName).collect(Collectors.toList());

FunctionalInterface

JDK预定义接口名 函数式接口 lambda表达式
Predicate boolean test(T t) T -> boolean
BinaryPredicate boolean test(T t, T, r) (T, T) -> boolean
Function R apply(T t) T -> R
BiFunction R apply(T t, U u) (T, U) -> R
Consumer void accept(T, t) (T) -> void
Supplier T get() () -> T
UnaryOperator T apply(T t) T -> T
BinaryOperator T apply(T t1, T t2) (T, T) -> T

CompletableFuture

https://blog.csdn.net/sermonlizhi/article/details/123356877

// 无Async后缀:将异步任务继续在前一异步任务线程内执行;
// 有Async后缀:将异步任务交给其他线程执行;

// 提交任务(提交后立刻交予线程池执行)
// 1.无返回值异步任务
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
// 2.有返回值异步任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
// sample
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    System.out.println("有返回值的异步任务");
    return "Hello World";
});
  
// 获取执行结果
// 同步等待,抛出受检异常
U result = future.get();
// 同步等待,抛出非受检异常CompletionException
U result = future.join();

// 回调处理
// 1.结果数据转换 U apply(T t) : T -> U
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
// 2.结果数据消费
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
// 3.回调任务,不传递数据
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);

// 任务组合
// thenCombine:合并两个任务的结果
CompletionStage<R> thenCombine(other, biFunction);
CompletionStage<R> thenCombineAsync(other, biFunction, executor);
CompletableFuture<Integer> result = future1.thenCombine(future2, 
		new BiFunction<Integer, Integer, Integer>() {
        @Override
        public Integer apply(Integer x, Integer y) {
            return x + y;
        }
    });
// 组合f(x),g(x)计算任务,返回新的CF
CompletableFuture<Object> combineFuture = future1.thenCombine(future2,
   (fResult, gResult) -> fResult+gResult);

// thenCompose:返回CompletableFuture实例的函数,该函数的参数是先前计算步骤的结果
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
// 组合f(x),g(x)计算任务,返回新的CF
CompletableFuture<String> composeFuture = future1.thenCompose(fResult ->
 			CompletableFuture.supplyAsync(() -> String.valueOf(fResult + g(x))));

// thenAcceptBoth:两个CompletionStage都正常完成计算的时候,执行提供的action消费两个异步的结果 
public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
// 组合f(x),g(x)计算任务,打印计算值
future1.thenAcceptBoth(future2, (fResult, gResult) -> 
                      logger.info("fResult={},gResult={}"),fResult, gResult)

// anyOf
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
// 多个任务任何一个完成返回这个CompletableFuture
Object result = CompletableFuture.anyOf(future1, future2).join();
// allOf
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
// 多个任务全部执行完再返回
CompletableFuture.allOf(future1, future2).join(); // 这里会同步等待
for(CompletableFuture future : futures) {
	 Object singleResult = future.join(); // 获取每个任务的结果
}

URLUtils

public class URLUtil {
    
    private static final Logger logger = LoggerFactory.getLogger(URLUtil.class);
    
    public static String urlEncode(String source) {
        return urlEncode(source, StandardCharsets.UTF_8.toString());
    }
    
    public static String urlEncode(String source, String charset) {
        if (StringUtils.isBlank(source)) {
            return source;
        }
        try {
            return URLEncoder.encode(source, charset);
        } catch (UnsupportedEncodingException e) {
            LogUtil.error(e, logger, null, "urlEncode不支持的编码", "source={0}, encodeCharset={1}", source, charset);
        }
        return null;
    }
    
    public static String urlDecode(String source) {
        return urlDecode(source, StandardCharsets.UTF_8.toString());
    }
    
    public static String urlDecode(String source, String charset) {
        if (StringUtils.isBlank(source)) {
            return source;
        }
        try {
            return URLDecoder.decode(source, charset);
        } catch (Exception e) {
            LogUtil.error(e, logger, null, "urlDecode解码失败", "source={0}, encodeCharset={1}", source, charset);
        }
        return null;
    }
    
    private static BitSet dontNeedEncoding;
    
    static {
        dontNeedEncoding = new BitSet(256);
        int i;
        for (i = 'a'; i <= 'z'; i++) {
            dontNeedEncoding.set(i);
        }
        for (i = 'A'; i <= 'Z'; i++) {
            dontNeedEncoding.set(i);
        }
        for (i = '0'; i <= '9'; i++) {
            dontNeedEncoding.set(i);
        }
        //去掉+号的判断
        //dontNeedEncoding.set('+');
        /**
        * 这里会有误差,比如输入一个字符串 123+456,它到底是原文就是123+456还是123 456做了urlEncode后的内容呢?<br>
        * 其实问题是一样的,比如遇到123%2B456,它到底是原文即使如此,还是123+456 urlEncode后的呢? <br>
        * 在这里,我认为只要符合urlEncode规范的,就当作已经urlEncode过了<br>
        * 毕竟这个方法的初衷就是判断string是否urlEncode过<br>
        */
        
        dontNeedEncoding.set('-');
        dontNeedEncoding.set('_');
        dontNeedEncoding.set('.');
        dontNeedEncoding.set('*');
    }
    
    /**
    * 判断str是否已经encode
    * 经常遇到这样的情况,拿到一个URL,但是搞不清楚到底要不要encode.
    * 不做encode吧,担心出错,做encode吧,又怕重复了
    */	
    public static boolean hasUrlEncoded(String source) {
        /**
        * 支持JAVA的URLEncoder.encode出来的string做判断。 即: 将' '转成'+' <br>
        * 0-9a-zA-Z保留 <br>
        * '-','_','.','*'保留 <br>
        * 其他字符转成%XX的格式,X是16进制的大写字符,范围是[0-9A-F]
        */
        boolean needEncode = false;
        for (int i = 0; i < source.length(); i++) {
            char c = source.charAt(i);
            if (dontNeedEncoding.get((int) c)) {
                continue;
            }
            if (c == '%' && (i + 2) < source.length()) {
                // 判断是否符合urlEncode规范
                char c1 = source.charAt(++i);
                char c2 = source.charAt(++i);
                if (isDigit16Char(c1) && isDigit16Char(c2)) {
                    continue;
                }
            }
            // 其他字符,肯定需要urlEncode
            needEncode = true;
            break;
        }
        
        return !needEncode;
    }
    
    /**
    * 判断c是否是16进制的字符
    *
    * @param c
    * @return
    */
    private static boolean isDigit16Char(char c) {
        return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F');
    }
    
}

HttpUtil


import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.NameValuePair;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


@Slf4j
@Component
public class HttpUtil {

    private static final String SCHEME_HTTP = "http";
    private static final String SCHEME_HTTPS = "https";
    private static final int HTTP_PORT = 80;
    private static final int HTTPS_PORT = 443;
    private static final int DEFAULT_SOCKET_TIMEOUT = 1000;
    private static final int DEFAULT_CONNECT_TIMEOUT = 2000;
    private static final int DEFAULT_MAX_PER_ROUTE = 2;
    private static final int DEFAULT_MAX_TOTAL = 500;
    private static final int DEFAULT_TIME_TO_LIVE = 3000;
    private static final int CONNECTION_IDLE_TIME_OUT = 3000;

    // 连接池复用
    private static volatile CloseableHttpClient reusableHttpClient;
    private static ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1,
            new BasicThreadFactory.Builder().namingPattern("httpclient-watcher-schedule-pool-%d").daemon(true).build());

    @PostConstruct
    public void init() throws Exception {
        try {
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new X509TrustManager[]{new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] xcs, String string) {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] xcs, String string) {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }}, new SecureRandom());

            // HTTP连接在内部使用java.net.Socket对象来处理数据的传输,依赖于ConnectionSocketFactory接口来创建、初始化和连接套接字
            // 创建套接字和将其连接到主机的过程是去耦合的,以便在连接操作中阻塞时可以关闭套接字,HttpClient用户能够在运行时提供应用程序特定的套接字初始化代码
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                                                                                     // 创建和初始化普通(未加密)套接字的默认工厂
                                                                                     .register(SCHEME_HTTP, PlainConnectionSocketFactory.INSTANCE)
                                                                                     // 除了信任证书和在SSL/TLS协议级别上执行的客户端认证之外(SSL信任验证),一旦建立连接,HttpClient可以可选地验证目标主机名是否与服务器的X.509证书中存储的名称匹配(主机名验证),提供服务器信任资料的真实性的额外保证
                                                                                     // DefaultHostnameVerifier(默认): 符合RFC 2818.主机名必须与证书指定的任何备用名称匹配
                                                                                     // NoopHostnameVerifier: 主机名验证关闭,接受任何SSL会话为有效并与目标主机匹配
                                                                                     .register(SCHEME_HTTPS, new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
                                                                                     .build();
            // Total time to live (TTL) set at construction time defines maximum life span of persistent connections regardless of their expiration setting.
            // No persistent connection will be re-used past its TTL value. 默认2000ms
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
                    socketFactoryRegistry, null, null, null, DEFAULT_TIME_TO_LIVE, TimeUnit.MILLISECONDS);
            // 每个域名设置单独的连接池数量
            connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);
            // 连接池总数
            connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL);
            connectionManager.setDefaultSocketConfig(SocketConfig.custom()
                                                                 // 关闭Nagle算法(Nagle算法减少小包的数量)
                                                                 .setTcpNoDelay(true)
                                                                 .build());

            RequestConfig defaultRequestConfig = RequestConfig.custom()
                                                              // 客户端发起TCP连接请求的超时时间
                                                              .setConnectTimeout(DEFAULT_CONNECT_TIMEOUT)
                                                              // 客户端等待服务端返回数据的超时时间
                                                              .setSocketTimeout(DEFAULT_SOCKET_TIMEOUT)
                                                              .build();

            reusableHttpClient = HttpClients.custom()
                                            .setConnectionManager(connectionManager)
                                            .setDefaultRequestConfig(defaultRequestConfig)
                                            .build();
            log.info("HttpUtil HttpClient init success!");

            // 服务端假设关闭了连接,对客户端是不透明的(可能造成大量CLOSED_WAIT连接),
            // HttpClient为了缓解这一问题,在某个连接使用前会检测这个连接是否过时,如果过时则连接失效,但是这种做法会为每个请求增加一定额外开销,
            // 因此有一个定时任务专门回收长时间不活动而被判定为失效的连接,可以某种程度上解决这个问题
            scheduler.scheduleAtFixedRate(
                    new Runnable() {
                        @Override
                        public void run() {
                            /**
                             // 打印当前连接池状态
                             PoolStats totalStats = connectionManager.getTotalStats();
                             log.info("totalStats:" + totalStats);
                             */
                            // 关闭失效连接并从连接池中移除
                            connectionManager.closeExpiredConnections();
                            // 关闭指定时间内不活动的连接并从连接池中移除,空闲时间从交还给连接管理器时开始
                            connectionManager.closeIdleConnections(CONNECTION_IDLE_TIME_OUT, TimeUnit.MILLISECONDS);
                        }
                    },
                    0, 10, TimeUnit.SECONDS);
        } catch (Throwable e) {
            log.error("HttpUtil HttpClient init error!", e);
            throw e;
        }
    }

    /**
     * 单请求单通道
     */
    public static CloseableHttpClient getDisposableHttpClient() {
        return HttpClients.createDefault();
    }

    public static String get(String url, HttpHost target, HttpHost proxy, Map<String, String> headers, int socketTimeout, int connectTimeout) {
        /**
         * wrapHttpGet
         */
        HttpGet request = new HttpGet(url);
        if (MapUtils.isNotEmpty(headers)) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                request.setHeader(entry.getKey(), entry.getValue());
            }
        }
        RequestConfig requestConfig = RequestConfig.custom()
                                                   .setSocketTimeout(socketTimeout)
                                                   .setConnectTimeout(connectTimeout)
                                                   // 通过代理访问, proxy可传null
                                                   // 一般代理使用流程:proxy=new HttpHost("xxx.xxx.com", 80, "http")且headers包含指定的请求头
                                                   .setProxy(proxy)
                                                   .build();
        request.setConfig(requestConfig);
        return execute(request, target);
    }

    public static String post(String url, HttpHost target, HttpHost proxy, Map<String, String> headers,
                              ContentType contentType, Charset charset, Map<String, String> params, JSONObject postBody,
                              int socketTimeout, int connectTimeout) {
        /**
         * wrapHttpPost
         */
        Assert.notNull(contentType, "contentType cannot be null.");
        HttpPost request = new HttpPost(url);
        RequestConfig requestConfig = RequestConfig.custom()
                                                   .setSocketTimeout(socketTimeout)
                                                   .setConnectTimeout(connectTimeout)
                                                   .setProxy(proxy)
                                                   .build();
        request.setConfig(requestConfig);

        if (MapUtils.isNotEmpty(headers)) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                request.setHeader(entry.getKey(), entry.getValue());
            }
        }

        if (ContentType.APPLICATION_JSON == contentType) {
            Assert.notNull(postBody, "postBody cannot be null.");
            StringEntity entity = new StringEntity(postBody.toJSONString(), ContentType.APPLICATION_JSON);
            request.setEntity(entity);
        } else if (ContentType.APPLICATION_FORM_URLENCODED == contentType) {
            Assert.notNull(params, "params cannot be null.");
            List<NameValuePair> pairs = new ArrayList<>();
            for (String key : params.keySet()) {
                pairs.add(new BasicNameValuePair(key, params.get(key)));
            }
            HttpEntity entity = new UrlEncodedFormEntity(pairs, charset);
            request.setEntity(entity);
        }

        return execute(request, target);
    }

    public static String postJsonData(String url, Map<String, String> headers, JSONObject postBody) {
        return post(url, null, null, headers, ContentType.APPLICATION_JSON, StandardCharsets.UTF_8,
                null, postBody, DEFAULT_SOCKET_TIMEOUT, DEFAULT_CONNECT_TIMEOUT);
    }

    public static String postFormData(String url, Map<String, String> headers, Map<String, String> params) {
        return post(url, null, null, headers, ContentType.APPLICATION_FORM_URLENCODED, StandardCharsets.UTF_8,
                params, null, DEFAULT_SOCKET_TIMEOUT, DEFAULT_CONNECT_TIMEOUT);
    }

    public static String execute(HttpRequestBase request, HttpHost target) {
        try {
            // 访问发布该服务的特定IP target=new HttpHost("33.2.3.65", 80, "http")
            HttpHost host = Objects.nonNull(target) ? target : URIUtils.extractHost(request.getURI());
            return reusableHttpClient.execute(host, request, getStringResponseHandler());
        } catch (ConnectTimeoutException | SocketTimeoutException e) {
            log.error("httpUtil execute timeOut!", e);
        } catch (Throwable e) {
            log.error("httpUtil execute error!", e);
        }
        return null;
    }

    /**
     * 从entity里解析data,response_header里的contentType为空时,默认采用UTF-8
     */
    private static ResponseHandler<String> getStringResponseHandler() {
        return httpResponse -> EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8);
    }

    /**
     * 可添加入参控制handler行为
     */
    private static ResponseHandler<Map<String, String>> getResponseHandler() {
        return httpResponse -> {
            Map<String, String> resultMap = Maps.newHashMap();
            resultMap.put("httpStatusCode", String.valueOf(httpResponse.getStatusLine().getStatusCode()));
            resultMap.put("content", EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8));
            resultMap.put("byteLength", String.valueOf(IOUtils.toByteArray(httpResponse.getEntity().getContent()).length));
            return resultMap;
        };
    }

}
posted @ 2023-03-06 17:01  Red_Revolution  阅读(52)  评论(0编辑  收藏  举报