常用工具类

String/byte[]#

// 占位符带编号
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);
}

// String和byte数组之间的转化
String str = "dada";
byte[] strByte = str.getBytes(StandardCharsets.UTF_8);
String strOut = new String(strByte, StandardCharsets.ISO_8859_1);

Base64#

sun和java.util的base64性能对比:

https://blog.csdn.net/weixin_42173616/article/details/105477571

//将字节数组编码成Base64字符串
String src = "jie";
String enc = Base64.getEncoder().encodeToString(src.getBytes());
//不指定编码会使用默认的编码 Charset.defaultCharset().name()
System.out.println(enc);

//将Base64字符串解码成字节数组
byte[] decode = Base64.getDecoder().decode(enc);
String aSrc = new String(decode);
System.out.println(aSrc);

String defaultCharset = Charset.defaultCharset().name();
System.out.println(defaultCharset);//UTF-8

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

ENUM#

public enum xxxEnum {

    A1(1, "A1"),

    A2(2, "A2"),

    A3(3, "A3");

    private Integer code;

    private String desc;

    TicketTypeEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    private static Map<Integer, xxxEnum> CODE_TO_ENUM = Maps.newHashMap();
    static {
        Arrays.stream(xxxEnum.values()).forEach(typeEnum -> CODE_TO_ENUM.put(typeEnum.getCode(), typeEnum));
    }
    public static xxxEnum getxxxEnum(Integer code) {
        return Optional.ofNullable(code).map(CODE_TO_ENUM::get).orElse(null);
    }

    public Integer getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

BeanUtil#

public class BeanUtilExtend
{
    /**
     * 方法用途:将map转换成bean,用到java内省
     *
     * @param map
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T mapToBean(Map<String, ? extends Object> map, Class<T> clazz)
    {
        if (clazz == null)
        {
            return null;
        }

        T object;
        try
        {
            object = clazz.newInstance();
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors)
            {
                String propertyName = propertyDescriptor.getName();
                if (map.containsKey(propertyName))
                {
                    Object value = map.get(propertyName);
                    Method setter = propertyDescriptor.getWriteMethod();
                    setter.invoke(object, value);
                }
            }
        } catch (Exception e)
        {
            throw new BusinessException("mapToBean异常", e);
        }

        return object;
    }

    public static <T> Map<String, Object> beanToMap(T obj)
    {

        if (obj == null)
        {
            return null;
        }
        Map<String, Object> map = new HashMap<String, Object>();
        try
        {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : propertyDescriptors)
            {
                String key = property.getName();

                // 过滤class属性
                if (!key.equals("class"))
                {
                    // 得到property对应的getter方法
                    Method getter = property.getReadMethod();
                    Object value = getter.invoke(obj);
                    if (null != value)
                    {
                        map.put(key, value);
                    }
                }

            }
        } catch (Exception e)
        {
            throw new BusinessException("beanToMap,obj=" + JSONUtil.toJSON(obj), e);
        }
        return map;

    }

    /**
     * 获取bean的所有属性值
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> Set<String> getBeanPropertyList(T obj)
    {
        Set<String> set = new HashSet<>();
        if (obj == null)
        {
            return set;
        }
        Map<String, Object> map = new HashMap<String, Object>();
        try
        {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : propertyDescriptors)
            {
                String key = property.getName();

                // 过滤class属性
                if (!key.equals("class"))
                {
                    set.add(key);
                }

            }
        } catch (Exception e)
        {
            throw new BusinessException("getBeanPropertyList,obj=" + JSONUtil.toJSON(obj), e);
        }
        return set;
    }

    public static <T> List<T> setToList(Set<T> set)
    {
        List<T> list = new ArrayList<>();
        if (set == null || set.isEmpty())
        {
            return list;
        }

        for (T item : set)
        {
            list.add(item);
        }

        return list;
    }

    public static <T extends Serializable> String assemble(List<T> list, String spliter)
    {
        if (list == null || list.isEmpty())
        {
            return "";
        }

        String result = "";
        for (T item : list)
        {
            result += item + spliter;
        }

        return result.replaceAll(spliter + "$", "");
    }

    public static <T, E> List<E> copyList(List<T> sourceList, Class<E> targetClazz) throws BusinessException
    {
        if (sourceList == null || sourceList.isEmpty())
        {
            return new ArrayList<E>();
        }

        List<E> targetList = new ArrayList<>();
        for (T source : sourceList)
        {
            E target = null;
            try
            {
                target = targetClazz.newInstance();
            } catch (Exception e)
            {
                throw new BusinessException("copyList异常, sourceList=" + JSONUtil.toJSON(sourceList), e);
            }
            BeanUtils.copyProperties(source, target);
            targetList.add(target);
        }

        return targetList;
    }

}

SprintContextHolder#

import org.apache.commons.lang.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.annotation.Annotation;
import java.util.Map;

/**
 *
 * @Description: 以静态变量保存Spring ApplicationContext,可在任意代码中取出ApplicationContext.
 * @version 1.0
 * Copyright (c) 2014 youku, All Rights Reserved.
 */
public class SpringContextHolder implements ApplicationContextAware
{

	private static ApplicationContext applicationContext;

	/**
	 * 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
	 */
	public void setApplicationContext(ApplicationContext applicationContext)
	{
		SpringContextHolder.applicationContext = applicationContext;
	}

	/**
	 * 取得存储在静态变量中的ApplicationContext.
	 */
	public static ApplicationContext getApplicationContext()
	{
		checkApplicationContext();
		return applicationContext;
	}

	/**
	 * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getBean(String name)
	{
		checkApplicationContext();
		return (T) applicationContext.getBean(name);
	}

	public static <T> T getBean(Class<T> clazz) {
		checkApplicationContext();
		return (T) applicationContext.getBean(clazz);
	}

	private static void checkApplicationContext()
	{
		if (applicationContext == null)
		{
			throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextUtil");
		}
	}

	public static <T> Map<String, T> getBeanByInterface(Class<T> clazz) {
		return applicationContext.getBeansOfType(clazz);
	}

	public static Map<String, Object> getBeanByAnnotation(Class<? extends Annotation> annotationType) {
		return applicationContext.getBeansWithAnnotation(annotationType);
	}

	public static String getBeanName(Class<?> clazz) {
		String[] name = applicationContext.getBeanNamesForType(clazz);
		return name == null || name.length <= 0 ? StringUtils.EMPTY : name[0];
	}
}

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

https://colobu.com/2016/02/29/Java-CompletableFuture/

// 无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;
        };
    }

}

FastJsonUtil#

public class FastJsonUtil {

	  private static final Logger logger = LoggerFactory.getLogger(FastJsonUtil.class);

    private static final Set<Class> BASIC_TYPE = Sets.newHashSet(
        String.class,
        Integer.class,
        Long.class,
        Double.class,
        Float.class,
				Boolean.class
    );

    /**
     * 反序列化配置
     *
     * @param json json格式配置项
     * @param clazz  序列化对象class
     * @param <T>    泛型参数
     * @return
     */
    public static <T> Optional<T> deserialize(String json, Class<T> clazz) {
    	if (StringUtils.isBlank(json)) {
    		return Optional.empty();
			}
        try {
            if (BASIC_TYPE.contains(clazz)) {
                if (clazz == String.class) {
                    return (Optional<T>)Optional.ofNullable(json);
                }
                if (clazz == Long.class) {
                    return (Optional<T>)Optional.ofNullable(Long.valueOf(json));
                }
                if (clazz == Integer.class) {
                    return (Optional<T>)Optional.ofNullable(Integer.valueOf(json));
                }
                if (clazz == Double.class) {
                    return (Optional<T>)Optional.ofNullable(Double.valueOf(json));
                }
                if (clazz == Float.class) {
                    return (Optional<T>)Optional.ofNullable(Float.valueOf(json));
                }
                if (clazz == Boolean.class) {
                    return (Optional<T>)Optional.ofNullable(Boolean.valueOf(json));
                }
            }
            return Optional.ofNullable(json)
				.map(str -> JSON.parseObject(json, new TypeReference<JSONObject>() {}, Feature.DisableCircularReferenceDetect))
				.map(jsonObject -> jsonObject.toJavaObject(clazz));
        } catch (Exception e) {
            logger.error("FastJsonUtil:deserialize error, json={}, clazz={}", json, clazz.getSimpleName(), e);
        }
        return Optional.empty();
    }

    public static <T> Optional<String> serialize(T object) {
    	if (Objects.isNull(object)) {
			return Optional.empty();
		}
    	if(object instanceof String){
    	    return Optional.of(object.toString());
        }
    	if (object instanceof JSONObject) {
    		return Optional.ofNullable(((JSONObject)object).toString(SerializerFeature.DisableCircularReferenceDetect));
		}
        // https://blog.csdn.net/wxwzy738/article/details/30244993
        return serialize(object, SerializerFeature.DisableCircularReferenceDetect);
    }


    /**
     * 序列化配置
     *
     * @param object 必须满足序列化要求
     * @param <T>
     * @return
     */
    public static <T> Optional<String> serialize(T object, SerializerFeature... features) {
        try {
        	if (Objects.isNull(object)) {
        		return Optional.empty();
			}
            List<SerializerFeature> featureList = Lists.newArrayList(features);
            featureList.add(SerializerFeature.DisableCircularReferenceDetect);
            String jsonConfig = JSON.toJSONString(object, featureList.toArray(new SerializerFeature[featureList.size()]));
            return Optional.ofNullable(jsonConfig);
        } catch (Exception e) {
            logger.error("FastJsonUtil:serialize error", e);
        }
        return Optional.empty();
    }

    public static Optional<JSONObject> parseJsonObject(String json) {
    	try {
    	    return Optional.ofNullable(json).map(JSONObject::parse).map(object -> (JSONObject)object);
		} catch (Exception e) {
    		logger.error("FastJsonUtil:parseJsonObject error, json={}", json, e);
    		return Optional.empty();
		}
	}

	public static List<String> parseList(String json) {
		try {
			return Optional.ofNullable(json).map(JSON::parseArray)
				.map(array -> array.toArray(new String[0]))
				.map(Lists::newArrayList)
				.orElse(Lists.newArrayList());
		} catch (Exception e) {
			logger.error("FastJsonUtil:parseList error, json={}", json, e);
			return Lists.newArrayList();
		}

	}

    /**
     * 读取json文件并解析为对象
     *
     * @param fileName json文件名,必须存放在相同maven module下的resources下
     * @return
     */
	public static Optional<JSONObject> readJsonFromFile(String fileName) {

	    // 获取fileName绝对路径
	    String path = FastJsonUtil.class.getClassLoader().getResource(fileName).getPath();
        String jsonStr = null;
        try {
            File jsonFile = new File(path);
            FileReader fileReader = new FileReader(jsonFile);
            Reader reader = new InputStreamReader(new FileInputStream(jsonFile),"utf-8");
            int ch = 0;
            StringBuffer sb = new StringBuffer();
            while ((ch = reader.read()) != -1) {
                sb.append((char) ch);
            }
            fileReader.close();
            reader.close();
            jsonStr = sb.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return parseJsonObject(jsonStr);
    }
}

Builder#

https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247490723&idx=1&sn=c5f55b7d828818b3a6b823505888c5fe&chksm=ebd75da2dca0d4b4366463f59b0fcd90b80b8998ce55c9f8a07ad9dfe58f80eab9e17904505e&token=1936697047&lang=zh_CN#rd

简单使用: ①lombok ②IDEA插件:Builder Generator (ctrl+enter:生成builder)

@Data
@Builder
@NoArgsConstructor
public class MessageTask {
    private String taskId;
    private String content;
}

进阶:上下文 数据+状态 build()时进行自检

public class EventContext {

    private String currentState;

    private EventDataSet dataSet;

    @Data
    public static class EventDataSet {
        private RedEntity red;
    }

    public static EventContextBuilder builder() {
        return new EventContextBuilder();
    }

    public static class EventContextBuilder {

        private EventContext ctx;

        EventContextBuilder() {
        }

        private EventContext checkCtxIfExists() {
            if (Objects.nonNull(ctx)) {
                return ctx;
            }
            ctx = new EventContext();
            EventDataSet dataSet = new EventDataSet();
            ctx.setDataSet(dataSet);
            return ctx;
        }

        public EventContextBuilder currentState(String currentState) {
            checkCtxIfExists().setCurrentState(currentState);
            return this;
        }

        public EventContextBuilder red(RedEntity redEntity) {
            checkCtxIfExists().getDataSet().setRed(redEntity);
            return this;
        }

        public EventContext build() throws Exception {
            ctx = checkCtxIfExists();
            if(Objects.isNull(ctx.currentState)) {
                throw new IllegalArgumentException();
            }
            return ctx;
        }
    }
}

责任链#

-chain

​ -builder

​ -NodeProcessorBuilder

​ -TranscodeNodeProcessorBuilder

​ -AbstractLinkedNodeProcessorBuilder

​ -DefaultNodeProcessorChain

​ -NodeProcessor

​ -NodeProcessorChain

-common

​ -SpringContextHolder

Node

public interface NodeProcessor {
    
    void handler(BizGlobalContext bizGlobalContext);
    
    void apply(BizGlobalContext bizGlobalContext);
}
public abstract class AbstractLinkedNodeProcessor implements NodeProcessor {

    private AbstractLinkedNodeProcessor next;

    @Override
    public void apply(BizGlobalContext bizGlobalContext) {
        if (next != null) {
            next.handler(bizGlobalContext);
        }
    }

    public AbstractLinkedNodeProcessor getNext() {
        return next;
    }

    public void setNext(AbstractLinkedNodeProcessor next) {
        this.next = next;
    }

}

Chain

public abstract class NodeProcessorChain  {

    public abstract void addProcessor(AbstractLinkedNodeProcessor processor);

    public abstract AbstractLinkedNodeProcessor getStartProcessor();

}
public class DefaultNodeProcessorChain extends NodeProcessorChain {

    final AbstractLinkedNodeProcessor firstProcessor = new AbstractLinkedNodeProcessor() {

        @Override
        public void handler(BizGlobalContext bizGlobalContext) {
            super.apply(bizGlobalContext);
        }
    };

    AbstractLinkedNodeProcessor lastProcessor = firstProcessor;

    @Override
    public void addProcessor(AbstractLinkedNodeProcessor processor) {
        lastProcessor.setNext(processor);
        lastProcessor = processor;
    }


    public AbstractLinkedNodeProcessor getStartProcessor() {
        return firstProcessor;
    }
}

Builder

public interface NodeProcessorChainBuilder {

    NodeProcessorChain buildChain();

}
public class xxxNodeProcessorChainBuilder implements NodeProcessorChainBuilder {

    XXX1Processor xxx1Processor = SpringContextHolder.getBean(XXX1Processor.class);
    XXX2Processor xxx2Processor = SpringContextHolder.getBean(XXX2Processor.class);
    XXX3Processor xxx3Processor = SpringContextHolder.getBean(XXX3Processor.class);

    @Override
    public NodeProcessorChain buildChain() {
        DefaultNodeProcessorChain chain = new DefaultNodeProcessorChain();
        chain.addProcessor(xxx1Processor);
        chain.addProcessor(xxx2Processor);
        chain.addProcessor(xxx3Processor);
        return chain;
    }
}

使用

NodeProcessorChain defaultNodeProcessorChain = new xxxNodeProcessorChainBuilder().buildChain();
defaultNodeProcessorChain.getStartProcessor().apply(bizGlobalContext);
@Slf4j
@Component
public class XXXProcessor extends AbstractLinkedNodeProcessor {

    @Override
    public void handler(BizGlobalContext bizGlobalContext) {
        //...
        apply(bizGlobalContext);
    }

}

handler入参也可以用handler(BizGlobalContext bizGlobalContext,NodeProcessorContext nodeProcessorCtx)

public class NodeProcessorContext {


    private NodeProcessorTypeEnum nodeProcessorType;
    private Integer progress;
    private Integer status;


    public NodeProcessorContext(NodeProcessorType nodeProcessorType) {
        this.nodeProcessorType = nodeProcessorType;
    }

    public NodeProcessorType getNodeProcessorType() {
        return nodeProcessorType;
    }

    public Integer getProgress() {
        return progress;
    }

    public void setProgress(Integer progress) {
        this.progress = progress;
    }

    public void setNodeProcessorType(NodeProcessorType nodeProcessorType) {
        this.nodeProcessorType = nodeProcessorType;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }
}

Aviator表达式引擎-灰度工具#

<dependency>
  <groupId>com.googlecode.aviator</groupId>
  <artifactId>aviator</artifactId>
  <version>4.1.0</version>
</dependency>

一、实例&添加自定义函数#

public class AviatorInstance {

    private static final Logger LOGGER = LoggerFactory.getLogger(AviatorInstance.class);

    private AviatorEvaluatorInstance evaluator;

    private AviatorInstance() {
        evaluator = AviatorEvaluator.newInstance();
        evaluator.addFunction(new ListContainFunction());
        evaluator.addFunction(new UtidGrayRatioFunction());
    }

    public AviatorEvaluatorInstance getEvaluator() {
        return evaluator;
    }

    public static AviatorInstance getInstance() {
        return AviatorInstanceHolder.AVIATOR_INSTANCE;
    }

    private static class AviatorInstanceHolder {
        public static final AviatorInstance AVIATOR_INSTANCE = new AviatorInstance();
    }
}
/**
 * listContain(String s, String seperator, String element) : Boolean
 * 将s按照seperator分割为列表,如果element包含在列表中,则返回true
 */
public class ListContainFunction extends AbstractFunction {
    @Override
    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2, AviatorObject arg3) {
        String originS = FunctionUtils.getStringValue(arg1, env);
        String seperator = FunctionUtils.getStringValue(arg2, env);
        String element = FunctionUtils.getStringValue(arg3, env);
        if (StringUtils.isBlank(originS) || StringUtils.isBlank(seperator)) {
            return AviatorBoolean.FALSE;
        }
        if (SimpleStringUtils.split(originS, seperator).contains(element)) {
            return AviatorBoolean.TRUE;
        }
        return AviatorBoolean.FALSE;
    }

    @Override
    public String getName() {
        return "listContain";
    }
}

使用表达式:"!listContain('A1,A2', ',', $category)"
/**
 * utidGrayRatio(String utid):Double
 * 将输入参数utid,转化为取值为[0,1]的小数并返回
 */
public class UtidGrayRatioFunction extends AbstractFunction {
    @Override
    public AviatorObject call(Map<String, Object> env, AviatorObject arg1) {
        String utid = FunctionUtils.getStringValue(arg1, env);
        return new AviatorDouble(getDeviceSampleNum(utid));
    }

    private static final String BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    private static final int MIN_UTID_LEN = 4;
    /**
     * 将utid通过采样算法转化为[0,1]的小数,用于灰度
     * E.g. utid转化为的数字为0.354,小于灰度比例0.412,则该设备命中灰度
     * @param utid
     * @return
     */
    
    public static double getDeviceSampleNum(String utid) {
        if (null == utid || MIN_UTID_LEN > utid.length()) {
            return 0.0;
        }
        int len = utid.length();
        int r4 = BASE64.indexOf(utid.charAt(len - 4));
        int r3 = BASE64.indexOf(utid.charAt(len - 3));
        int r2 = BASE64.indexOf(utid.charAt(len - 2));
        double sampleNum = 4096.0 * r4 + 64.0 * r3 + r2;
        return sampleNum / 262144;
    }

    @Override
    public String getName() {
        return "utidGrayRatio";
    }

}

使用表达式:"utidGrayRatio($utdid)<0.412"

入参KV构造paramMap工具

public class BaseGrayParamMapBuilder {

    public void addStringFieldToParamMap(Map<String, Object> paramMap, String key, String value) {
        paramMap.put(key, null != value ? value : PlayConstants.DEFUALT_STR_VALUE);
    }

    public void addLongFieldToParamMap(Map<String, Object> paramMap, String key, Long value) {
        paramMap.put(key, null != value ? value : PlayConstants.DEFAULT_LONG_VALUE);
    }

    public void addIntegerFieldToParamMap(Map<String, Object> paramMap, String key, Integer value) {
        paramMap.put(key, null != value ? value : PlayConstants.DEFUALT_INTEGER_VALUE);
    }

    public void addBooleanFieldToParamMap(Map<String, Object> paramMap, String key, Boolean value) {
        paramMap.put(key, null != value ? value : Boolean.FALSE);
    }

    public void addDoubleFieldToParamMap(Map<String, Object> paramMap, String key, Double value) {
        paramMap.put(key, null != value ? value : PlayConstants.DEFAULT_DOUBLE_VALUE);
    }

    public void addListFieldToParamMap(Map<String, Object> paramMap, String key, List value) {
        paramMap.put(key, null != value ? value : new ArrayList<>());
    }

}

定义灰度规则入参涉及的维度

@Builder
@Data
public class PolicyScheduleGrayParamBO extends BaseGrayParamMapBuilder {

    private String A1;
    private String A2;

    public Map<String, Object> toParamMap() {
        Map<String, Object> paramMap = new HashMap<>();
        addStringFieldToParamMap(paramMap, "$A1", this.A1);
        addStringFieldToParamMap(paramMap, "$A2", this.A2);
        return paramMap;
    }
}

二、使用方式#

1.构造灰度规则,编写规则表达式

[
  {"id":10, "businessType":"schedule_grey_rule1", "passGray":true, "passResult":{}, "matchRule":"($sourceType=='1') && ($testResource==true || $state=='normal')", "status":1, "priority":1000, "desc":"人工规则"},
  {"id":20, "businessType":"schedule_grey_rule2", "passGray":true, "passResult":{}, "matchRule":"($sourceType=='2') && ($testResource==true || $state=='normal') && (!listContain('A1,A2', ',', $category) || listContain('c,c以下,d,d以下', ',', $scheduleRating)) ", "status":1, "priority":3000, "desc":"AAA"}
]
public class DirectlyQueryRpcRuleBO {
    /**
     * 规则id
     */
    private Integer id;
    /**
     * 当前规则所属的系统环境
     */
    private EnvType envType;
    /**
     * 符合Aviator表达式规范的匹配条件
     */
    private String matchRule;
    /**
     * Aviator规则引擎根据matchRule字段,预编译生成的规则表达式
     */
    private Expression matchRuleExpression;
    /**
     * 当前规则的状态,1代表生效,2代表失效
     */
    private Integer status;
    /**
     * 当前规则的优先级,取值越大,优先级越低
     */
    private Long priority;
    /**
     * 当前规则的描述信息
     */
    private String desc;
}

设置内存变量,监听配置变更

public class DirectlyQueryRpcConfig {

    private static Logger logger = BaseLogFactory.getLogger(DirectlyQueryRpcConfig.class);

    /**
     * 灰度规则落到内存里
     */
    private static List<DirectlyQueryRpcRuleBO> bizDirectlyQueryRpcRuleList = new CopyOnWriteArrayList<>();

    static {
        init();
    }

    private static void init() {
        try {
            String configStr = xxxHelper.getIniValueByName(xxxKey());
            logger.info(xxx);
            updateBizDirectlyQueryRpcRuleList(configStr);

            xxx.addListener(xxxKey(), "domain",
                    new xxxListenerAdapter() {
                        @Override
                        public void innerReceive(String dataId, String group, String configInfo) {
                            logger.info(xxx);
                            updateBizDirectlyQueryRpcRuleList(configInfo);
                        }
                    });
        } catch (Exception e) {
            logger.error(xxx);
        }
    }

    private static void updateBizDirectlyQueryRpcRuleList(String listStr) {
        List<DirectlyQueryRpcRuleDO> ruleDOList = JSON.parseArray(listStr, DirectlyQueryRpcRuleDO.class);
        List<DirectlyQueryRpcRuleBO> ruleBOList = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(ruleDOList)) {
            for (DirectlyQueryRpcRuleDO ruleDO : ruleDOList) {
                DirectlyQueryRpcRuleBO rpcRuleBO = new DirectlyQueryRpcRuleBO();
                rpcRuleBO.setId(ruleDO.getId());
                rpcRuleBO.setXXX(ruleDO.getXXX());
                ...
                rpcRuleBO.setMatchRule(ruleDO.getMatchRule());
                rpcRuleBO.setMatchRuleExpression(AviatorInstance.getInstance().getEvaluator().compile(ruleDO.getMatchRule(), true));
  							...
                ruleBOList.add(rpcRuleBO);
            }
            // priority越大灰度优先级越高
            ruleBOList.sort(Comparator.comparingLong(DirectlyQueryRpcRuleBO::getPriority));
        }
        bizDirectlyQueryRpcRuleList = ruleBOList;
    }

    public static List<DirectlyQueryRpcRuleBO> getBizDirectlyQueryRpcRuleList() {
        return bizDirectlyQueryRpcRuleList;
    }

    @Data
    public static class DirectlyQueryRpcRuleDO {
        /**
         * 规则id
         */
        @JSONField(name = "id")
        private int id;
        /**
         * 当前规则所属的系统环境
         */
        @JSONField(name = "envType")
        private String envType;
        /**
         * 符合Aviator表达式规范的匹配条件
         */
        @JSONField(name = "matchRule")
        private String matchRule;
        /**
         * 当前规则的状态,1代表生效,2代表失效
         */
        @JSONField(name = "status")
        private int status;
        /**
         * 当前规则的优先级,取值越大,优先级越低
         */
        @JSONField(name = "priority")
        private long priority;
        /**
         * 当前规则的描述信息
         */
        @JSONField(name = "desc")
        private String desc;
    }

}

业务代码里判断是否命中规则

    private boolean queryByRpcStrategyMatchService(PermissionRequestDto requestDto) {
        List<DirectlyQueryRpcRuleBO> ruleList = DirectlyQueryRpcConfig.getBizDirectlyQueryRpcRuleList();
        if (CollectionUtils.isEmpty(ruleList)) {
            return false;
        }

        DirectlyQueryRpcParamBO paramBO = DirectlyQueryRpcParamBO.Builder
                .newBuilder()
                .xxx(xxx)
                .build();
        Map<String, Object> paramMap = paramBO.toParamMap();

        // 命中一条规则即通过
        for (DirectlyQueryRpcRuleBO ruleBO : ruleList) {
            // 规则未生效
            if (1 != ruleBO.getStatus()) {
                continue;
            }
            try {
                if (Boolean.TRUE.equals((Boolean) ruleBO.getMatchRuleExpression().execute(paramMap))) {
                    return true;
                }
            } catch (Throwable t) {
                logger.error(xxx);
            }
        }
      	// else
				...
        return false;
    }

Lombook#

IDEA lombok plugin:最新即可

注意:lombok低maven版本不支持@builder

尽量用2020-02的1.18.12

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

常用注解

https://www.cnblogs.com/ooo0/p/12448096.html

IDEA插件lombok的介绍页可直接索引到注解官方文档

注解转化为代码

先确定IDEA插件lombok为Enable

左键点击下注解 -> 窗口栏Refactor里找到Delombok

注解生成代码:编译 -> 在/target包下去查看生成的文件

MapStruct#

MapStruct优势:静态编译,比反射效率高

https://mp.weixin.qq.com/s/vF3hADt--ejaB8sCXi5zLA

mapstruct和lombok一起使用不兼容处理方法:

https://www.cnblogs.com/readiay/p/13894444.html

@Mapping(target = "deliverBO.userId", source = "deliverId")
@Mapping(target = "deliverBO.deliverTypeEnum", expression = "java(DeliverTypeEnum.getDeliverTypeEnum(transferOrderDO.getDeliverType()).get())")
TransferOrderBO do2bo(TransferOrderDO transferOrderDO);

@Named("bool2byteUtil")
default Byte bool2byte(Boolean source) {
    if(null == source) {
        return null;
    }
    if(Boolean.TRUE.equals(source)) {
        return Byte.valueOf("1");
    }
    return Byte.valueOf("0");
}

Springboot单元测试&集成测试#

简单Junit单元测试(无需Spring Boot功能)

public class SimpleJunitTest {  
    @Test
    public void testSayHi() {
        System.out.println("Hi Junit.");
    }
}

https://www.cnblogs.com/myitnews/p/12330297.html

一个单元测试用例执行顺序为:@BeforeClass → @Before → @Test → @After → @AfterClass

每一个测试方法的调用顺序为:@Before → @Test → @After

  • @BeforeClass:针对所有测试,只执行一次,且必须为static void
  • @Before:初始化方法
  • @Test:测试方法,在这里可以测试期望异常和超时时间
  • @After:释放资源
  • @AfterClass:针对所有测试,只执行一次,且必须为static void
  • @Ignore:忽略的测试方法

https://blog.csdn.net/weixin_42205594/article/details/126087217

https://www.mianshigee.com/question/150402spo/

https://blog.csdn.net/Numb_ZL/article/details/122217202

https://aliyuque.antfin.com/gtc3ao/bg9fpp/xacpf3#6cf32e0a

https://segmentfault.com/a/1190000040553331

集成测试

-java(测试包)

​ -com.xxx.xxx

​ -datasource

​ -DBTest

​ AbstractTest

​ TestApplication

-resources

​ test.properties

module-start pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
</dependency>

AbstractTest

@RunWith(SpringRunner.class)
@DelegateTo(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class AbstractTest {
}

@RunWith将Spring和Junit链接了起来

@SpringBootTest替代了spring-test中的@ContextConfiguration注解,目的是加载ApplicationContext,启动spring容器,加载所有被管理的bean,基本等同于启动了整个服务

编写Spring的测试上下文环境TestApplication.class(这样test包可以访问到其他模块的上下文Springbean)

@SpringBootApplication(scanBasePackages = {"com.xxx.xxx"})
@PropertySource(value = { "classpath:test.properties" })
@EnableAsync
public class TestApplication {
}

编写test.properties,指定测试环境下的配置,一般从application.properties复制一份即可

编写测试程序

public class DBTest extends AbstractTest {

    @Autowired
    private xxxAbilityMapper xxxAbilityMapper;

    @Test
    public void testInsertDPA() {
        xxxAbilityDO temp = new xxxAbilityDO();
        temp.setBrand("huasuo");

        int count = xxxAbilityMapper.insertSelective(temp);
        System.out.println(count);
    }
}

线程池#

JUC#

ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(() -> {
    // 执行的任务
});
executorService.shutdown();

ThreadPoolExecutorExecutorService 的一个具体实现

Spring#

ThreadPoolTaskExecutor: Spring中提供的一个线程池执行器,封装了JUC的ThreadPoolExecutor

@Configuration
@EnableAsync
public class ExecutorConfig {

    /** Set the ThreadPoolExecutor's core pool size. */
    private int corePoolSize = 10;
    /** Set the ThreadPoolExecutor's maximum pool size. */
    private int maxPoolSize = 500;
    /** Set the capacity for the ThreadPoolExecutor's BlockingQueue. */
    private int queueCapacity = 500;

    @Bean(name="customExecutor")
    public ThreadPoolTaskExecutor customExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        // 配置队列大小
        executor.setQueueCapacity(queueCapacity);
        // 配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("CustomExecutor-");
        // rejection-policy:当pool已经达到max_size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

使用1

@Autowired
private ThreadPoolTaskExecutor customExecutor;

customExecutor.submit(
        () -> {
                // 需异步执行的方法体
        }
);

使用2

https://www.cnblogs.com/hsug/p/13303018.html

为了让@Async注解能够生效,需要在Spring Boot的主程序(Application)配置@EnableAsync,或者在线程池配置ExecutorConfig上配置@EnableAsync

@Async("customExecutor")
public void executeAsync() {
    // 方法体
}

Result#

public class BaseResult implements Serializable {

    private static final long serialVersionUID = -7628161277862523259L;

    public BaseResult(){}

    public BaseResult(boolean success){
        this.success = success;
    }

    private boolean success = false;

    private String resCode;

    private String resMsg;

    private Map bizExtInfo;

    public String getResMsg() {
        return resMsg;
    }

    public void setResMsg(String resMsg) {
        this.resMsg = resMsg;
    }

    public String getResCode() {
        return resCode;
    }

    public void setResCode(String resCode) {
        this.resCode = resCode;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public Map getBizExtInfo() {
        return bizExtInfo;
    }

    public void setBizExtInfo(Map bizExtInfo) {
        this.bizExtInfo = bizExtInfo;
    }
}
public class Result<T> extends BaseResult {

    private T model;

    public Result(){
        super();
    }

    public Result(boolean success) {
        super(success);
    }

    public T getModel() {
        return model;
    }

    public void setModel(T model) {
        this.model = model;
    }
}
public class ListResult<T> extends BaseResult {

    private List<T> data;

    private Integer total;

    private Integer totalPages;

    private Integer currentPage;

    private Integer pageSize;

    public ListResult(boolean success) {
        super(success);
    }

    public ListResult(){
        super();
    }

    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data;
    }

    public Integer getTotal() {
        return total;
    }

    public void setTotal(Integer total) {
        this.total = total;
    }

    public Integer getTotalPages() {
        return totalPages;
    }

    public void setTotalPages(Integer totalPages) {
        this.totalPages = totalPages;
    }

    public Integer getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(Integer currentPage) {
        this.currentPage = currentPage;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }
}
public class ResultBuilder {

    public static <T> Result<T> buildSingleResult(T data, boolean success, String errCode, String errMsg, Map bizExtMap) {
        Result<T> result = new Result<>(success);
        result.setModel(data);
        result.setResMsg(errMsg);
        result.setResCode(errCode);
        result.setBizExtInfo(bizExtMap);
        return result;
    }

    public static <T> Result<T> buildSuccessSingleResult(T data, Map bizExtMap) {
        return buildSingleResult(data, true, null, null, bizExtMap);
    }

    public static <T> Result<T> buildSuccessSingleResult(T data) {
        return buildSingleResult(data, true, null, null, null);
    }

    public static Result buildFailedSingleResult(ErrorEnum err) {
        return buildSingleResult(null, false, err.getErrorCode(), err.getErrorMessage(), null);
    }

    public static Result buildFailedSingleResult(ErrorEnum err, Map bizExtMap) {
        return buildSingleResult(null, false, err.getErrorCode(), err.getErrorMessage(), bizExtMap);
    }

    public static Result buildFailedSingleResult(ErrorEnum err, String extraMsg) {
        return buildSingleResult(null, false, err.getErrorCode(), err.getErrorMessage() + " | " + extraMsg, null);
    }

    public static Result buildFailedSingleResult(CustomResError err) {
        return buildSingleResult(null, false, err.getResCode(), err.getResMsg(), null);
    }

    public static <T> ListResult<T> buildListResult(List<T> data, boolean success, String errCode, String errMsg,
                                                    Integer totalRecord, Integer totalPage, Integer curPage,
                                                    Integer pageSize) {
        ListResult<T> listResult = new ListResult<>(success);
        listResult.setData(data);
        listResult.setTotal(totalRecord);
        listResult.setTotalPages(totalPage);
        listResult.setCurrentPage(curPage);
        listResult.setPageSize(pageSize);
        listResult.setResMsg(errMsg);
        listResult.setResCode(errCode);
        return listResult;
    }

    public static <T> ListResult buildSuccessListResult(List<T> data, Integer totalRecord, Integer totalPage,
                                                        Integer curPage, Integer pageSize) {
        return buildListResult(data, true, null, null, totalRecord, totalPage, curPage, pageSize);
    }

    public static ListResult buildFailedListResult(ErrorEnum err) {
        return buildListResult(null, false, err.getErrorCode(), err.getErrorMessage(), null, null, null, null);
    }

    public static ListResult buildFailedListResult(ErrorEnum err, String extraMsg) {
        return buildListResult(null, false, err.getErrorCode(), err.getErrorMessage() + " | " + extraMsg, null, null,
                null, null);
    }
}

CommandLineRunner#

https://blog.csdn.net/xuan_lu/article/details/107849392

https://blog.csdn.net/qq_33204709/article/details/133888330

@Component
public class DeviceOperationManager implements CommandLineRunner {
    private static Map<OperationEnum, AbstractDeviceOperationGenerator> POOL = new HashMap();

    @Autowired
    private ApplicationUtil applicationUtil;

    @Override
    public void run(String... strings) throws Exception {

        if (MapUtils.isEmpty(POOL)) {
            // 用annotation索引bean
            Map<String, Object> beans = applicationUtil.getBeanByAnnotation(DeviceOperationGenerator.class);
            Object bean;
            DeviceOperationGenerator source;
            OperationEnum operationEnum;
            for (String beanName : beans.keySet()) {
                bean = beans.get(beanName);
                if (!(bean instanceof AbstractDeviceOperationGenerator)) {
                    continue;
                }
                // 找到bean的annotation注解对应的枚举类
                source = AnnotationUtils.findAnnotation(bean.getClass(),
                    DeviceOperationGenerator.class);
                if (source == null) {
                    continue;
                }
                operationEnum = source.value();
                // 将枚举类及其对应的generator加入POOL
                POOL.put(operationEnum, (AbstractDeviceOperationGenerator)bean);
            }
        }
    }


    public DeviceOperationResultBO generator(DeviceManageBO request) {
        DeviceOperationResultBO ret = null;
        OperationEnum opEnum = OperationEnum.index(request.getOp());
        if (Objects.isNull(opEnum)) {
            return ret;
        }
        AbstractDeviceOperationGenerator generator = POOL.get(opEnum);
        if (Objects.nonNull(generator)) {
            ret = generator.generate(request);
        }
        return ret;
    }

}

annotation定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DeviceOperationGenerator {
    OperationEnum value();
}

具体的generator

public abstract class AbstractDeviceOperationGenerator {
    public abstract DeviceOperationResultBO generate(DeviceManageBO request);
}
@DeviceOperationGenerator(OperationEnum.APPLY_BIND)
@Component
public class ApplyBindDeviceGenerator extends AbstractDeviceOperationGenerator{
    @Override
    public DeviceOperationResultBO generate(DeviceManageBO request) {
        ...
    }
}

使用的时候可用 deviceOperationManager.generator(request) 去找到该request对应的处理generator

ApplicationListener#

https://segmentfault.com/a/1190000019786425

https://blog.csdn.net/weixin_33939380/article/details/92070034

通过监听ContextRefreshedEvent事件,在容器构建和bean注册完成后,将几个converter添加到POOL(注意converter应是无状态的)

@Component
public class DeviceConverter implements ApplicationListener<ContextRefreshedEvent> {

    private static final Map<String, IDeviceConverter> POOL = Maps.newHashMap();

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // root application context 没有parent
        if (event.getApplicationContext().getParent() != null) {
            return;
        }
		
        // 从容器中获取所有实现了IDeviceConverter接口的bean
        ApplicationContext applicationContext = event.getApplicationContext();
        Map<String, IDeviceConverter> beansOfType = applicationContext.getBeansOfType(IDeviceConverter.class);
        if (MapUtils.isEmpty(beansOfType)) {
            return;
        }
		
        // POOL中添加Converter
        for (IDeviceConverter deviceConverter : beansOfType.values()) {
            POOL.put(deviceConverter.getClass().getName(), deviceConverter);
        }
    }

    public static IDeviceConverter getConverter(String converterFullName) {
        if (StringUtils.isBlank(converterFullName)) {
            return null;
        }
        return POOL.get(converterFullName);
    }
	
    public ... bizMethod(xxxEnum xxx, ...) {
    	String converterFullName = deviceEnum.getConverterFullName();
        IDeviceConverter converter = getConverter(converterFullName);
        converter.bizMethod(...);
    }
}

IDeviceConverter接口/实例/枚举类

public interface IDeviceConverter {
	MethodResult bizMethod(xxxEnum xxx, ...);
}

@Component
public class DefaultDeviceConverter implements IDeviceConverter {
	@Override
    MethodResult bizMethod(xxxEnum xxx, ...) {
    	...
    }
}

// 枚举类中可指定到对应的converterFullName
public enum DeviceEnum {
    OTT("ott", "ott_ext", Constants.IDTYPE_DEVICE, DefaultDeviceConverter.class.getName()),
    MOBILE("mobile", "mobile_ext", Constants.IDTYPE_DEVICE, DefaultDeviceConverter.class.getName()),
    NFC("nfc", "nfc_ext", Constants.IDTYPE_DEVICE, NfcDeviceConverter.class.getName());
	
    ...
    private String converterFullName;
    
    ... 
}

在上层代码中即可通过 DeviceConverter.bizMethod(xxxEnum, ...) 调用枚举类对应converter的biz方法

不用POOL,每次在getConverter里去getBean?似乎POOL池化管理是一种更好的耦合,或许有本地缓存功能?

TODO
Spring Retry 中,@Retryable 和 @Recover

posted @   Red_Revolution  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示
主题色彩