Guava 骚操作,10分钟搞定日志脱敏需求!

4.log4j2实现日志脱敏 日志脱敏 日志基础研究脑图 日志基础框架图 主要参考资料:   源码分析: https://www.cnblogs.com/xiaoxi666/p/11426259.html https://cloud.tencent.com/d... 阅读详情

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料: 

b0a38903de4d884f9a851687e3d90154.gif

👉这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • Cloud 地址:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn

来源:juejin.cn/post/
7274626136328257588

22888629125144ee93bf967adafbadb8.jpeg


❝

「Guava」 之于「Javaer」 ,如同「Excel」 之于「办公达人」 。

都非常好用,但实际上大部分人只用到了其「1%不到」 的功能。

❞

日志脱敏到底是个啥

「敏感信息脱敏」 实际上是隶属于「安全领域」 的一个子领域,而「日志脱敏」 又是「敏感信息脱敏」 的一个子领域。

好了,打住,不闲聊这些有的没的,直接开整:到底什么是日志脱敏?

未脱敏之前

如下有一个关于个人信息的类

public class Person {
   private Long id;
   private String name;
   private String phone;
   private String account;
   // setter and gettr ...
}

在日志脱敏之前,我们一般会这样直接打印日志

log.info("个人信息:{}",JsonUtils.toJSONString(person));

然后打印完之后,日志大概是这样

个人信息:{"id":1,"name":"张无忌","phone":"17709141590","account":"14669037943256249"}

那如果是这种敏感信息打印到日志中的话,安全问题是非常大的。研发人员或者其他可以访问这些敏感日志的人就可能会故意或者不小心泄露用户的个人信息,甚至干些啥坏事。

f2c81a94a41c76c2568b1427d9d3e0f5.jpeg
坏事

脱敏后

那日志脱敏最后要达到什么效果呢?

如下:需要把敏感字段中的一部分字符使用特殊符号替换掉(这里我们用*来做特殊符号)

个人信息:{"id":1,"name":"**忌","phone":"177******90","account":"146*********49"}

所以,很自然的,我们就写了个「脱敏组件」 ,在每一个字段上用「注解」 来标识每一个字段是什么类型的敏感字段,需要怎么脱敏。

比如,对于上面的个人信息,在打印日志的时候需要研发人员做两件事:

  1. 「使用脱敏组件提供的注解来标识敏感字段」

public class Person {
   // id是非敏感字段,不需要脱敏
   private Long id;
   
   @Sensitive(type = SensitiveType.Name)
   private String name;
   
   @Sensitive(type = SensitiveType.Phone)
   private String phone;
   
   @Sensitive(type = SensitiveType.Account)
   private String account;
   // setter and gettr ...
}
  1. 「使用脱敏组件先脱敏,再打印日志」

如下,先使用脱敏组件提供的工具类脱敏个人信息,然后再打印日志

log.info("个人信息:{}", DataMask.toJSONString(person));

具体的使用和实现原理可以参考:

唯品会脱敏说明:https://github.sheincorp.cn/vipshop/vjtools/blob/master/vjkit/docs/data_masking.md。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

日志脱敏遇到了什么问题

到这,还只是一般脱敏组件提供的功能范畴。也就是说,到这你基本上都可以在github上搜索到一些现成的解决方案。

28d68ee159d9708320e56e68d5014f8c.jpeg
一般般

但是到了企业里面,就不是说到这就已经结束了。

到了企业里面,你得满足客户(也就是研发人员)奇奇怪怪(也许只是一开始「你觉得」 是奇奇怪怪,但是实际上很合理)的需求。

比如,我们就有研发人员提出:「需要按照Map中的Key来配置脱敏规则」

啥意思呢?简单来说,如果我有一个Map类型的数据,如下

Map<String,Object> personMap = new HashMap<>();  
personMap.put("name","张无忌");  
personMap.put("phone","17709141590");  
personMap.put("account","14669037943256249");  
personMap.put("id",1L);

那么在配置文件中指定好对应的key的脱敏规则后就可以把Map中的敏感数据也脱敏。

大概配置文件如下:

#指定Map中的key为name,name,account的value的脱敏规则分别是Name,Account,Phone
Name=name
Account=account
Phone=phone

那先不管需求是否合理吧,反正客户就是上帝,满足再说。

然后,我们就开始实现了。

基本思路:「复制Map」 ,然后遍历复制后的Map,找到Key有对应脱敏规则的value,按照脱敏规则脱敏,最后使用Json框架序列化脱敏后的Map。

public class DataMask{
  // other method...
 /**
  * 将需要脱敏的字段先进行脱敏操作,最后转成json格式
  * @param object 需要序列化的对象
  * @return 脱敏后的json格式
  */
 public static String toJSONString(Object object) {
  if (object == null) {
   return null;
  }
  try {

   // 脱敏map类型
   if (object instanceof Map) {
        return return maskMap(object);
   }

   // 其他类型
   return JsonUtil.toJSONString(object);
  } catch (Exception e) {
   return String.valueOf(object);
  }
 }
        
        private static String maskMap(Object object) {
  Map map = (Map) object;
  MaskJsonStr maskJsonStr = new MaskJsonStr();
  // 复制Map
  HashMap<String, Object> mapClone = new HashMap<>();
  mapClone.putAll(map);
  Map mask = maskJsonStr.maskMapValue(mapClone);
  return JsonUtil.getObjectMapper().writeValueAsString(mask);
 }
}

public class MaskJsonStr{
  // other method...
    public Map<String, Object> maskMapValue(Map<String, Object> map) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            Object val = entry.getValue();
            if (val instanceof Map) {
                maskMapValue((Map<String, Object>) val);
            } else if (val instanceof Collection) {
                Collection collVal = maskCollection(entry.getKey(), val);
                map.put(entry.getKey(), collVal);
            } else {
                // 根据key从脱敏规则中获取脱敏规则,然后脱敏
                Object maskVal = maskString(entry.getKey(), val);
                if (maskVal != null) {
                    map.put(entry.getKey(), maskVal);
                } else {
                    map.put(entry.getKey(), val);
                }
            }
        }

        return map;
    }
}

可以说,「在整体思路上,没啥毛病」 ,但是往往「魔鬼就在细节中」 。

208bd814febad3eb2b23758ff956244c.jpeg
细节

看到这,也许有些大神,直接从代码中已经看出问题了。不急,我们还是悠着点来,给你10分钟思量一下先。

28ba3f0d492da8463481d9af58017c98.jpeg

❝

「1」 分钟

❞

❝

「2」 分钟

❞

❝

「n」 分钟

❞

8ebf9c8e22996317053895b2232fde38.jpeg

好的,我知道的,你肯定是不会思考的。

我们直接看问题。有使用我们这个组件的研发人员找过说:我c a o,你们把我的业务对象「Map中的值修改掉了」 。

我们本来想直接回怼:你们是不是姿势不对,不会用啊。但是本着严(zhi)谨(qian)的(bei)原(da)则(nian)(guo),还是问了句,你在本地复现了吗?

结果,还真被打脸了。人家既有截图,又给我们发了可复现的代码。真是打脸打到家了。

09b6d2704d3689fc68f083877d8ec3b5.gif
打脸

那到底是啥问题呢?按道理,我们肯定是测试过的,正常情况下不会有问题。那到底是什么场景下有问题呢?

我们发现:「有嵌套类型的Map的时候就会有问题」

测试程序如下:

@Test  
public void testToJSONString() {  
    Map<String,Object> personMap = new HashMap<>();  
    personMap.put("name","张无忌");  
    personMap.put("phone","17709141590");  
    personMap.put("account","14669037943256249");  
    personMap.put("id",1L);  

    Map<String,Object> innerMap = new HashMap();  
    innerMap.put("name","张无忌的女儿");  
    innerMap.put("phone","18809141567");  
    innerMap.put("account","17869037943255678");  
    innerMap.put("id",2L);  
    personMap.put("daughter",innerMap);  

    System.out.println("脱敏后:"+DataMask.toJSONString(personMap));  
    System.out.println("脱敏后的原始Map对象:"+personMap);
}

输出结果如下:

脱敏后:{"name":"**忌","id":1,"phone":"177*****590","daughter":{"phone":"188*****567","name":"****女儿","id":2,"account":"1***************8"},"account":"1***************9"}
脱敏后的原始Map对象:{phone=17709141590, name=张无忌, id=1, daughter={phone=188*****567, name=****女儿, id=2, account=1***************8}, account=14669037943256249}

我们发现,脱敏时是成功的,但是却「把原始对象中的内嵌innerMap对象中的值修改了」 。

a5a177b669c1257e2229b8ff92c8e601.jpeg
震惊

❝

要知道,作为脱敏组件,你可以有点小bug,你也可以原始简单粗暴,「甚至你都可以脱敏失败」 (本该脱敏的却没有脱敏),但是你「千万不能修改业务中使用的对象」 啊。

❞

❝

虽然问题很大,但是我们还是要沉着应对问题,仔细分析问题。做到泰山崩于前而色不变,才是打工人的正确处事方式。不然只会「越急越慌,越慌越不冷静,最后买1送3」 (修1个bug,新引入3个bug)

❞

简单debug,加上看源代码,其实这个问题还是比较容易发现的,主要问题就是在「复制Map对象的姿势不对」

❝

如下,我们是使用这样的方式来复制Map的。本来是想做「深度clone」 的,但是这种事做不到深度clone的。对于有内嵌的对象的时候只能做到「浅clone」 。

❞

// 复制Map
HashMap<String, Object> mapClone = new HashMap<>();
mapClone.putAll(map);

所以,只有一层关系的简单Map是可以脱敏成功的,且不会改变原来的Map。但是对于有嵌套的Map对象时,就会修改嵌套Map对象中的值了。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

问题的原因是啥,如何解决,思路是啥

❝

从上面的分析中就可以得出其根本原因:没有正确地深度clone Map对象

❞

那很自然地,我们的解决思路就是找到一种合适的「深度 clone Map对象」 的方式就OK了。

然后我就问ChatGPT了,ChatGPT的回答有下面几个方法

  1. 「使用序列化和反序列化」 :通过将对象序列化为字节流,然后再将字节流反序列化为新的对象,可以实现深度克隆。需要注意被克隆的对象及其引用类型成员变量都需要实现Serializable接口。

  2. 「使用第三方库」 :除了上述两种方式,还可以使用一些第三方库,例如Apache Commons的SerializationUtils类、Google的Gson库等,它们提供了更简洁的方法来实现深度克隆。

  3. 「使用JSON序列化和反序列化」 :将对象转换为JSON字符串,然后再将JSON字符串转换为新的对象。需要使用JSON库,如Jackson、Gson等。

  4. 「使用Apache Commons的BeanUtils类」 :BeanUtils提供了一个cloneBean()方法,可以对JavaBean进行深度克隆。需要注意,被克隆的对象及其引用类型成员变量都需要实现Serializable接口。

  5. 「最佳实践是根据需求和具体情况灵活应用」 ,或者采用第三方库实现对象克隆,如 Apache Commons BeanUtils、Spring BeanUtils 等。

上面几个方式基本上可以分为3类:

  1. 「序列化和反序列化」 :JDK自带的序列化(需要实现Serializable接口);利用Gson,FastJson,Jackson等JSON序列化工具序列化后再反序列化;其他序列化框架序(如Hessain等)列化反序列化

  2. 「利用第三方库」 :第三方库直接clone对象。但都有一定的限定条件

  3. 「视情况而定」 :基本上没有一种通用的方法,可以适配是有的深度clone场景。所以ChatGPT提出了这一种不是办法的办法

根据上面的一番探索,发现还得自己敲代码造轮子。

eb3b34f916f62f49bc6d99e844aea1f4.jpeg
敲代码

❝

那其实你仔细分析后发现:我们确实不需要通用的深度clone对象的能力。我们只需要把Map类型深度clone好就行,对于其他自定义DTO,我们是不需要深度clone的。

❞

然后就是卡卡一顿猛敲,如下:常规递归操作

public class MapUtil {  
    private MapUtil() {  

    }  

    public static <K, V> Map<K, V> clone(Map<K, V> map) {  
        if (map == null || map.isEmpty()) {  
            return map;  
        }  
        Map cloneMap = new HashMap();  
        for (Map.Entry<K, V> entry : map.entrySet()) {  
            final V value = entry.getValue();  
            final K key = entry.getKey();  
            if (value instanceof Map) {  
                Map mapValue = (Map) value;  
                cloneMap.put(key, clone(mapValue));  
            } else if (value instanceof Collection) {  
                Collection collectionValue = (Collection) value;  
                cloneMap.put(key, clone(collectionValue));  
            } else {  
                cloneMap.put(key, value);  
            }  
        }  
        return cloneMap;  
    }  

    public static <E> Collection<E> clone(Collection<E> collection) {  
        if (collection == null || collection.isEmpty()) {  
            return collection;  
        }  
        Collection clonedCollection;  
        try {  
            // 有一定的风险会反射调用失败  
            clonedCollection = collection.getClass().newInstance();  
        } catch (InstantiationException | IllegalAccessException e) {  
            // simply deal with reflect exception  
            throw new RuntimeException(e);  
        }  

        for (E e : collection) {  
            if (e instanceof Collection) {  
                Collection collectionE = (Collection) e;  
                clonedCollection.add(clone(collectionE));  
            } else if (e instanceof Map) {  
                Map mapE = (Map) e;  
                clonedCollection.add(clone(mapE));  
            } else {  
                clonedCollection.add(e);  
            }  
        }  
        return clonedCollection;  
    }  
}

然后,又是一波Junit操作,嘎嘎绿灯,收拾完事。

9574e412689a99cd2d4a78009f99194e.png

行

貌似,到这我们就可以下班了。

ed3960995545805259610c7b2f44511e.jpeg
下班

但是,等等,这篇文章貌似,好像,的确,应该缺点啥吧?

这尼玛不是讲的是Guava吗,到这为止,貌似跟Guava毛关系没有啊!!!

a5b4936565058bdb4ee43ad7edca4259.jpeg
扯淡

那我们继续,容你再思考一下,到现在为止,我们的深度clone方案有什么问题。

好的,我懂的。咱看到这,图的就是一个乐,你让我思考,这不是强人所难吗

❝

思考,思考是不可能存在的。

❞

62d4107790c3ce16c1e1222574959612.jpeg
我懂

❝

那咱们就直接看问题:就是啥都好,问题就是「性能比较差!!!」

❞

如果你是一个老司机,你可能看到上面的代码就已经感觉到了,性能是相对比较低的。

❝

基本上,Map层级越深,字段越多,内嵌的集合对象元素越多,性能越差!!!

❞

至于差到什么程度,其实是可以通过 微基准测试框架「JMH」 来实际测试一下就知道了。这里我就不测试了。

❝

那我们还有什么办法来解决这个性能问题吗?

❞

如果我们还是集中于深度clone对象上做文章,去找寻性能更高的深度clone框架或者是类库的话,那么其实这个问题就已经「走偏」 了。

我们再来回顾一下我们之前的问题:

❝

我们需要对Map对象按照key来脱敏,所以我们选择了深度clone Map,然后对Map遍历按照key脱敏后序列化。

❞

❝

那其实我们最终要解决的问题是:Map对象序列化后的字符串得是按照key的脱敏规则脱敏后的字符串。

❞

所以,其实我们除了深度clone这一条路外,还有另外两条路:

  • 1.「自定义Map类型的序列化器」 :在序列化Map的时候,如果有脱敏规则则应用脱敏规则来序列化Map

  • 2.「转换Map为脱敏后的Map」 :自定义Map对象,将脱敏规则作为转换函数来把普通的Map转换为脱敏的Map

对于第一种方式,要看你使用的Json框架是否支持(一般Map类型用的都是内置的Map序列化器,不一定可以自定义)。

那第二种方式,到底应该如何玩呢. 如下:

// 脱敏转换函数
public interface MaskFunction<K, V, E> {
    /**
     * @param k key
     * @param v value
     * @return 根据key和value得到脱敏(如果有需要的话)后的值
     */
    E mask(K k, V v);
}
// 自定义MaskMap对象,经过MaskFunction函数,将普通Map转换为脱敏后的Map
public class MaskMap extends HashMap {

    private MaskFunction maskFunction;

    public MaskMap(MaskFunction maskFunction) {
        this.maskFunction = maskFunction;
    }

    @Override
    public Object get(Object key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        }
        return maskFunction.mask(key, value);
    }
    // other function to  override ...
}

如上,Map不再是「clone」 的玩法,而是「转换」 的玩法,所以,这种操作是非常「轻量级」 的。

但是这种玩法也有缺点:比较麻烦,需要override好多方法(不然就需要熟读Map序列化器来找到最小化需要override的方法,并且不太靠谱),并且全部都要是「转换」 的玩法。

终于,终于,终于,这个时候该轮到我们「Guava」 登场了。

9c9a3f4f9466e2205974bb70f552c7a5.jpeg
轮到我了

Guava登场

用过Guava的人都知道,Guava中很多好用的方法,但是我们平常用的多的也就是那几个。所以这个时候你说需要转换Map,那我们是不是可以去Guava中看看有没有现成的方案,没准有惊喜!!!

5298a03cd21d994b21bdac05ca277349.jpeg
惊喜

一看就是惊喜:

b1c422ef10288bcd0fc1c23eb9a58b00.jpeg
Maps#transformEntries(Map<K,V1>, Maps.EntryTransformer<? super K,? super V1,V2>)

❝

Returns a view of a map whose values are derived from the original map's entries. In contrast to transformValues, this method's entry-transformation logic may depend on the key as well as the value. All other properties of the transformed map, such as iteration order, are left intact.

❞

❝

返回一个Map的试图,其中它的值是从原来map中entry派生出来的。相较于transformValues方法,这个基于entry的转换逻辑是既依赖于key又依赖于value。变换后的映射的所有其他属性(例如迭代顺序)均保持不变

❞

这不正是我们上面需要实现的 MaskMap吗!!!

除此之外,我们还需要支持,对集合类型的脱敏「转换」 ,再去看一下呢,又是惊喜!!!

9a643c69512ac160eb45b8e02e891cf7.jpeg

到这,基本上,我们已经在Gauva中找到了我们所需要的全部元素了。下面就是简单集合脱敏规则使用下就好了

public class MaskEntryTransformer implements Maps.EntryTransformer<Object, Object, Object> {
    private static final Maps.EntryTransformer<Object, Object, Object> MASK_ENTRY_TRANSFORMER = new MaskEntryTransformer();

    private MaskEntryTransformer() {

    }

    public static Maps.EntryTransformer<Object, Object, Object> getInstance() {
        return MASK_ENTRY_TRANSFORMER;
    }

    @Override
    public Object transformEntry(Object objectKey, Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Map) {
            Map valueMap = (Map) value;
            return Maps.transformEntries(valueMap, this);
        }
        final Maps.EntryTransformer<Object, Object, Object> thisFinalMaskEntryTransformer = this;
        if (value instanceof Collection) {
            Collection valueCollection = (Collection) value;
            if (valueCollection.isEmpty()) {
                return valueCollection;
            }
            return Collections2.transform(valueCollection, new Function<Object, Object>() {
                @Override
                public Object apply(Object input) {
                    if (input == null) {
                        return null;
                    }
                    if (input instanceof Map) {
                        Map inputValueMap = (Map) input;
                        return Maps.transformEntries(inputValueMap, thisFinalMaskEntryTransformer);
                    }
                    if (input instanceof Collection) {
                        Collection inputValueCollection = (Collection) input;
                        return Collections2.transform(inputValueCollection, this);
                    }
                    if (!(objectKey instanceof String)) {
                        return input;
                    }
                    final String key = (String) objectKey;
                    return transformPrimitiveType(key, input);
                }
            });
        }
        if (!(objectKey instanceof String)) {
            return value;
        }
        final String key = (String) objectKey;
        return transformPrimitiveType(key, value);
    }

    /**
     * 按照脱敏规则脱敏基本数据类型
     *
     * @param key
     * @param value
     * @return
     */
    private Object transformPrimitiveType(final String key, final Object value) {
        // ...
    }
}

那脱敏的地方只要转换一下Map就可以了,如下:

public class DataMask {
    /**
     * 将需要脱敏的字段先进行脱敏操作,最后转成json格式
     * @param object 需要序列化的对象
     * @return 脱敏后的json格式
     */
    public static String toJSONString(Object object) {
        if (object == null) {
                return null;
        }
        try {
                if (object instanceof Map) {
                        Map maskMap = Maps.transformEntries((Map) object, MaskEntryTransformer.getInstance());
                        return JsonUtil.toJSONString(maskMap);
                }

                return JsonUtil.toJSONString(object, jsonFilter);
        } catch (Exception e) {
                return object.toString();
        }
    }
}

一点疑问

我们调用了transformEntries方法,是可以根据key(可以找到脱敏规则)和value(可以判断类型)来转换的。但是Map中的有些API实际上可能只有value(比如values())或者只有key(比如get()方法)的,那这种EntryTransformer是如何生效的?

❝

你是不是有那么一点好奇呢?

❞

我们就不去想了,直接看代码:

  • 对于get()方,只有key参数:

但是比较容易通过key拿到对应的value,然后把key和value传给转换函数就可以了

public V2 get(Object key) {  
    V1 value = fromMap.get(key);  
    return (value != null || fromMap.containsKey(key))  
    ? transformer.transformEntry((K) key, value)  
    : null;  
}

❝

这里的实现其实有一个细节,不知道大家注意没有。

就是这一个判断:(value != null || fromMap.containsKey(key)) ,我们上面实现的时候直接没有考虑到值为null的情况(但value为null,但是有对应的key的时候还是应该要调用转换函数的)

❞

  • 对于values()方,只返回值

public Collection<V2> values() {  
    return new Values<K, V2>(this);  
}

也是一样,返回一个转换后的 Collection,其中Collection中的值也是经过 transformer转换的。

而对于迭代器也是一样的,都有对应的实现类把转换逻辑放进去了。

Guava闲聊

Guava的Veiw思想

其实上面的这种 Veiw的基本思想,在Guava中有非常多的场景应用的,如

  • com.google.common.base.Splitter#split

  • com.google.common.collect.Lists#partition

  • com.google.common.collect.Sets#difference

  • ...other

❝

这种 Veiw的基本思想,其实就是「懒加载」 的思想:「在调用的时候不实际做转换,而是在实际使用的时候会做转换」

❞

比如:com.google.common.base.Splitter#split在调用的时候实际上并不是立刻马上去分隔字符串,而是返回一个Iterable的View对象。只是在实际迭代这个Iterable的View对象时才会实际去切分字符串。

在比如com.google.common.collect.Sets#difference在调用的时候并不会立刻马上去做两个集合的差操作,而是返回一个 SetView的View对象,只有在实际使用这个View对象的API的时候才会真正做差操作。

❝

注意点:这种思想大部分场景下,会是性能友好型的,但是也有例外。我们要分清楚场景。

比如,分割字符串的方法com.google.common.base.Splitter#split,如果调用完之后不做任何操作,或者只会遍历一次(大部分场景都是只遍历一次),那么这其实就是最好的方法。但是如果调用完之后,还需要遍历很多次,那么这种场景下,性能可能不是最好的。

❞

❝

所以,对于我们来讲,如果Guava工具包中没有我们需要的View的方法,那么我们可以自己按照这个思想来造一个。如果已经有了,要区分场景使用.

❞

简单聊一下Guava API的兼容性

升级过Guava版本的同学可能深有体会,升级Guava是一件比较头疼的事情。因为「Gauva API的兼容性是做得很差」 的。(当然这里主要还是因为大部分同学没有认真阅读官方的文档导致的)

因为,官网首页就直接提醒了Guava API的兼容性原则:

d44cb997721432226aba65b813266060.jpeg

具体请参考:Guava官网https://guava.dev/。

这对于我们做基础组件的同学来讲,是一件尤其需要注意的事情。因为一旦使用了前后不兼容的API,那么使用组件的应用很可能因为API不兼容,导致无法运行的问题。

❝

所以对于做组件的同学,对于Guava的使用一定要慎重:能不用就不用,必须要用的话一定不能使用以@Beta注解标注的方法或者类。

❞

总结

Gauva确实是一个保藏库,当你要造轮子的时候不妨先看看Gauva中有没有现成的轮子。每看一次,也许就多一次惊喜。

这一次是Map对象脱敏场景遇上了Guava的 Maps#transformEntries(Map<K,V1>, .Maps.EntryTransformer<? super K,? super V1,V2>) ,那么你又有什么场景偶遇了Guava呢?,可以在评论区聊一下。


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,“长按”或“扫描”下方二维码噢:

db1e9e7c6fff69569fccd4bfa410df1b.png

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

98a4e44669f0f9f549d7ca4dd888db93.png

a9cf1070b3601fe429254007c99dc7b6.png6e9cb10b79dbb5c2042bf5b754e712dc.png4f2ed8ceb9a999ac78046abc3bff93e5.png3e112185c6721cf238cee0d2fdacc586.png

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
【日志脱敏】 java日志脱敏 阅读详情

相关推荐

时频分析之STFT:短时傅里叶变换的原理与代码实现(非调用Matlab API)

STFT function [STFT, f, t] = mystft(x, win, hop, nfft, fs) % function: [STFT, f, t] = stft(x, win, hop, nfft, fs) % % Input: % x - signal in the time domain % win - analysis window function % hop - hop size % nfft - numb

frostime的博客 13万+

日志里的敏感信息还在打明文?3 种日志脱敏方案任你选

背景我们打的日志中经常包含姓名、手机号、银行卡号等敏感信息,如果不做任何处理,就会以明文的形式展示在日志中,存在安全风险。像下面这样:我们需要一种能自动帮我们脱敏的工具,效果如下:方案1 - 基于 logback我们得先搞清楚消息内容是在哪里处理的,也就是配置文件中这个占位符的内容:对应到源码是这里 ch.qos.logback.classic.PatternLayout :这里可以看出来都是通...

JiuQianWan的博客 1万+

从DIY桌面CNC到工业机器人:手把手教你用LinuxCNC搭建自己的第一台数控系统(基于Xenomai实时补丁)

本文详细介绍了如何使用LinuxCNC和Xenomai实时补丁搭建高性价比的数控系统,从硬件选型到实时系统构建,再到HAL硬件抽象层实战和G代码加工,手把手教你打造从DIY桌面CNC到工业机器人的完整解决方案。

weixin_28727975的博客 288

日志脱敏-参考

参考

m0_48333563的博客 3099

复用浏览器之跳过扫码登录

复用浏览器 1、通过命令启动浏览器:chrome --remote-debugging-port=9222(前提是退出所有的chrome浏览器) 2、启动浏览器之后,在地址栏输入127.0.01:9222,成功复用如下图所示:

qq_43911915的博客 2033

零中频接收机实战:DC Offset的5种常见解决方案与调试技巧

本文针对零中频接收机中DC Offset这一核心挑战,深入剖析了其产生机理与对信号解调的严重影响。文章从实战角度出发,系统性地介绍了五种解决方案,包括硬件预防、数字域校正、I/Q不平衡联合校正等,并分享了关键的实验室调试与产线测试技巧,旨在帮助射频工程师有效提升接收机性能。

weixin_29234509的博客 397

信息安全无小事,手把手教你日志脱敏

场景 我们开发的程序迟早有一天都会上线到生产环境运行,但是没有人能保证自己的代码100%不出BUG(别抬扛,真没BUG是代码写的少) 当我们线上出BUG之后,最常见的定位问题方法就是排查日志文件,所以我们一般都会在开发程序时,在适当的位置输出一些日志信息。 并且有一些日志并不是只打印一些业务字段,可能会将整个对象输出到日志中。比如这样: log.info("客户信息:{}",JSON.toJSONString(customer)); 从代码的角度来说,不太严格的话,如果这个customer对象不是特别大的

小黑说Java 4315

倍福--手动加入第三方伺服的配置

客户在没有硬件情况下,希望手动配置硬件,添加第三方驱动器,本文介绍其操作流程 目 录 软硬件版本 3 1.1. 倍福Beckhoff 3 1.1.1. 控制器硬件 3 1.1.2. 控制软件 3 操作步骤 3 2.1. 添加电机XML文件 3 2.2. 创建EtherCAT 3 2.3. 添加新建项 3 2.4. 选择型号 4 2.5. 关联NC轴 5 软硬件版本 1.1. 倍福Beckhoff 1.1.1. 控制器硬件 TwinCAT控制制器,PC或者EPC,包括: 嵌入式控制器:CX5020-

weixin_41883890的博客 5325

Guava骚操作,10分钟搞定日志脱敏需求!

因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享点击关注#互联网架构师公众号,领取架构师全套资料 都在这里0、2T架构师学习资料干货分上一篇:2T架构师学习资料干货分享大家好,我是互联网架构师!❝「Guava」之于「Javaer」,如同「Excel」之于「办公达人」。都非常好用,但实际上大部分人只用到了其「1%不到」的功能。❞日志脱敏到底是个啥「敏感信息脱敏」实际上是隶属于「...

emprere的博客 264

图神经网络实战(2)——图论基础

图论 (Graph theory) 是数学的一个基本分支,涉及对图研究。图是复杂数据结构的可视化表示,有助于理解不同实体之间的关系。图论提供了大量建模和分析现实问题的工具,如交通系统、社交网络和互联网等。在本节中,将介绍图论的基本原理,主要涉及三个方面:图属性、图概念和图算法。

盼小辉丶的博客 1万+

【深度学习新浪潮】ISP芯片算法技术简介及关键技术分析

ISP(Image Signal Processor)芯片作为现代影像系统的核心组件,负责对图像传感器输出的原始信号进行后期处理。ISP的主要功能包括线性纠正、噪声去除、坏点修复、色彩校正以及白平衡调整等,这些处理步骤对于提高图像质量和视觉效果至关重要。随着科技的进步,ISP芯片不仅提升了图像的清晰度和色彩准确性,还通过高效的算法增强了拍摄体验,尤其是在低光环境下的表现。

专注:太空算力、AI infra、异构调度、大模型成本工程 1223

10分钟搞定日志脱敏,Guava又来个骚操作!

Gauva确实是一个保藏库,当你要造轮子的时候不妨先看看Gauva中有没有现成的轮子。每看一次,也许就多一次惊喜。这一次是Map对象脱敏场景遇上了Guava的super K,?,那么你又有什么场景偶遇了Guava呢?,可以在评论区聊一下。

IT_1024的博客 461

机械式激光雷达实现原理以及特性总结

本文介绍了机械式激光雷达的技术原理和核心特性。该技术通过机械旋转结构实现360°三维扫描,采用飞行时间法测距并生成点云数据。核心优势包括全向扫描、长距高精度和成熟度高,但存在体积大、成本高、可靠性受限等局限。典型应用于自动驾驶测试车、测绘建模等领域。与固态激光雷达相比,机械式在视场角和探测距离方面有优势,但在体积、成本和可靠性方面处于劣势。该技术目前主要应用于对成本不敏感的专业场景。

start_up_go的博客 262

微软全力拥抱 Java !

往期热门文章: 1、巧用 Redis,实现微博 Feed 流功能! 2、知乎高赞:为什么别选计算机专业? 3、Guava骚操作,10分钟搞定日志脱敏需求! 4、公司弃用 Nginx,选择这款工具! 5、项目自从用了接口请求合并,效率直接加倍!文章来源:【公众号:优快云】前言虽然一直以来,微软的不少产品在很多层面都有使用 Java 语言,但近日其官网上的一则「Microsoft is commi...

Java后端技术 188

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

巧用 Redis,实现微博 Feed 流功能!

往期热门文章: 1、知乎高赞:为什么别选计算机专业? 2、Guava骚操作,10分钟搞定日志脱敏需求! 3、公司弃用 Nginx,选择这款工具! 4、项目自从用了接口请求合并,效率直接加倍! 5、记一次CPU飙升问题排查背景最近接到一个需求,用一句话来说就是:展示关注人发布的动态,这个涉及到 feed 流系统的设计。本文主要介绍一个一般企业可用的 Feed 流解决方案。相关概念下面先介绍一下关于...

Java后端技术 285

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

编写 if 时尽量不要带 else

往期热门文章: 1、为什么 MyBatis 源码中,没有我那种 if···else 2、微软全力拥抱 Java ! 3、巧用 Redis,实现微博 Feed 流功能! 4、知乎高赞:为什么别选计算机专业? 5、Guava骚操作,10分钟搞定日志脱敏需求!注:本文并不肯定或者否定哪一种写法,仅仅为大家提供一些其他的编码思路或者一些值得借鉴的点子,希望大家能在公众号的每一篇文章中都能有所收获,同时欢...

Java后端技术 177

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

为什么 MyBatis 源码中,没有我那种 if···else

往期热门文章: 1、微软全力拥抱 Java ! 2、巧用 Redis,实现微博 Feed 流功能! 3、知乎高赞:为什么别选计算机专业? 4、Guava骚操作,10分钟搞定日志脱敏需求! 5、公司弃用 Nginx,选择这款工具!在MyBatis的两万多行的框架源码中,使用了大量的设计模式对工程架构中的复杂场景进行解耦,这些设计模式的巧妙使用是整个框架的精华。经过整理,大概有以下设计模式,如图1所...

Java后端技术 147

为什么很多程序员讨厌低代码?

往期热门文章: 1、人肉运维100次后,终究还是翻车了! 2、公司用了 3 年多的多账号统一登录方案,万能通用,稳的一批! 3、99%的时间里使用的14个git命令 4、通过 Arthas Trace 命令将接口性能优化十倍 5、公司用了 3 年多的多账号统一登录方案,万能通用,稳的一批!低代码不是什么新鲜事物,低代码是一种只需用很少甚至不需要代码即可快速开发系统,并将其快速配置和部署的技术和工...

Java后端技术 120
上一篇: Redis创始人开源最小聊天服务器,仅200行代码
下一篇: 医生都是越老越吃香,为啥程序员却不是?
公众号-芋道源码
博客等级 码龄10年 1627粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值