常用工具类
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#
简单使用: ①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();
ThreadPoolExecutor
是 ExecutorService
的一个具体实现
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!