<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java日志脫敏,使用Guava 10 分鐘搞定

          共 28907字,需瀏覽 58分鐘

           ·

          2024-04-10 19:16

          來(lái)源:juejin.cn/post/7274626136328257588

          推薦:https ://t.zsxq.com/17aSsutv6

          Guava 之于 Javaer ,如同 Excel 之于辦公達(dá)人。

          都非常好用,但實(shí)際上大部分人只用到了其1%不到的功能。

          日志脫敏到底是個(gè)啥

          敏感信息脫敏實(shí)際上是隸屬于安全領(lǐng)域的一個(gè)子領(lǐng)域,而日志脫敏又是敏感信息脫敏的一個(gè)子領(lǐng)域。

          好了,打住,不閑聊這些有的沒(méi)的,直接開整:到底什么是日志脫敏?

          未脫敏之前

          如下有一個(gè)關(guān)于個(gè)人信息的類

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

          在日志脫敏之前,我們一般會(huì)這樣直接打印日志

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

          然后打印完之后,日志大概是這樣

                
                個(gè)人信息:{"id":1,"name":"張無(wú)忌","phone":"17709141590","account":"14669037943256249"}

          那如果是這種敏感信息打印到日志中的話,安全問(wèn)題是非常大的。研發(fā)人員或者其他可以訪問(wèn)這些敏感日志的人就可能會(huì)故意或者不小心泄露用戶的個(gè)人信息,甚至干些啥壞事。

          3480c11ca55c42aa0c0a0b1711273de1.webp

          脫敏后

          那日志脫敏最后要達(dá)到什么效果呢?

          如下:需要把敏感字段中的一部分字符使用特殊符號(hào)替換掉(這里我們用*來(lái)做特殊符號(hào))

                
                個(gè)人信息:{"id":1,"name":"**忌","phone":"177******90","account":"146*********49"}

          所以,很自然的,我們就寫了個(gè)脫敏組件,在每一個(gè)字段上用注解來(lái)標(biāo)識(shí)每一個(gè)字段是什么類型的敏感字段,需要怎么脫敏。

          比如,對(duì)于上面的個(gè)人信息,在打印日志的時(shí)候需要研發(fā)人員做兩件事:

          1. 使用脫敏組件提供的注解來(lái)標(biāo)識(shí)敏感字段
                
                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 ...
          }

          2.使用脫敏組件先脫敏,再打印日志

          如下,先使用脫敏組件提供的工具類脫敏個(gè)人信息,然后再打印日志

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

          具體的使用和實(shí)現(xiàn)原理可以參考《唯品會(huì)脫敏說(shuō)明》,https://github.com/vipshop/vjtools/blob/master/vjkit/docs/data_masking.md

          日志脫敏遇到了什么問(wèn)題

          到這,還只是一般脫敏組件提供的功能范疇。也就是說(shuō),到這你基本上都可以在github上搜索到一些現(xiàn)成的解決方案。

          4fb0ebfdcd98d86f14ffedaffea567d3.webp

          但是到了企業(yè)里面,就不是說(shuō)到這就已經(jīng)結(jié)束了。

          到了企業(yè)里面,你得滿足客戶(也就是研發(fā)人員)奇奇怪怪(也許只是一開始你覺(jué)得是奇奇怪怪,但是實(shí)際上很合理)的需求。

          比如,我們就有研發(fā)人員提出:需要按照Map中的Key來(lái)配置脫敏規(guī)則

          啥意思呢?簡(jiǎn)單來(lái)說(shuō),如果我有一個(gè)Map類型的數(shù)據(jù),如下

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

          那么在配置文件中指定好對(duì)應(yīng)的key的脫敏規(guī)則后就可以把Map中的敏感數(shù)據(jù)也脫敏。

          大概配置文件如下:

                
                #指定Map中的key為name,name,account的value的脫敏規(guī)則分別是Name,Account,Phone
          Name=name
          Account=account
          Phone=phone

          那先不管需求是否合理吧,反正客戶就是上帝,滿足再說(shuō)。

          然后,我們就開始實(shí)現(xiàn)了。

          基本思路:復(fù)制Map,然后遍歷復(fù)制后的Map,找到Key有對(duì)應(yīng)脫敏規(guī)則的value,按照脫敏規(guī)則脫敏,最后使用Json框架序列化脫敏后的Map。

                
                public class DataMask{
            // other method...
           /**
            * 將需要脫敏的字段先進(jìn)行脫敏操作,最后轉(zhuǎn)成json格式
            * @param object 需要序列化的對(duì)象
            * @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();
            // 復(fù)制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 {
                          // 根據(jù)key從脫敏規(guī)則中獲取脫敏規(guī)則,然后脫敏
                          Object maskVal = maskString(entry.getKey(), val);
                          if (maskVal != null) {
                              map.put(entry.getKey(), maskVal);
                          } else {
                              map.put(entry.getKey(), val);
                          }
                      }
                  }

                  return map;
              }
          }

          可以說(shuō),在整體思路上,沒(méi)啥毛病,但是往往魔鬼就在細(xì)節(jié)中

          9ac7a5d520e0f9199bd4f7cf6b7c7cfe.webp

          看到這,也許有些大神,直接從代碼中已經(jīng)看出問(wèn)題了。不急,我們還是悠著點(diǎn)來(lái),給你10分鐘思量一下先。

          3218f4028305ff45c83ba8da3e055bc7.webp
          ?

          1分鐘

          ?

          2分鐘

          ?

          n分鐘

          8d05c0793ba7455eee4ed246929d0a4b.webp

          好的,我知道的,你肯定是不會(huì)思考的。

          我們直接看問(wèn)題。有使用我們這個(gè)組件的研發(fā)人員找過(guò)說(shuō):我c a o,你們把我的業(yè)務(wù)對(duì)象Map中的值修改掉了

          我們本來(lái)想直接回懟:你們是不是姿勢(shì)不對(duì),不會(huì)用啊。但是本著嚴(yán)(zhi)謹(jǐn)(qian)的(bei)原(da)則(nian)(guo),還是問(wèn)了句,你在本地復(fù)現(xiàn)了嗎?

          結(jié)果,還真被打臉了。人家既有截圖,又給我們發(fā)了可復(fù)現(xiàn)的代碼。真是打臉打到家了。

          5cf34972d595a17d10f508cb02e9a358.webp

          那到底是啥問(wèn)題呢?按道理,我們肯定是測(cè)試過(guò)的,正常情況下不會(huì)有問(wèn)題。那到底是什么場(chǎng)景下有問(wèn)題呢?

          我們發(fā)現(xiàn):有嵌套類型的Map的時(shí)候就會(huì)有問(wèn)題

          測(cè)試程序如下:

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

              Map<String,Object> innerMap = new HashMap();  
              innerMap.put("name","張無(wú)忌的女兒");  
              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對(duì)象:"+personMap);
          }

          輸出結(jié)果如下:

                
                脫敏后:{"name":"**忌","id":1,"phone":"177*****590","daughter":{"phone":"188*****567","name":"****女兒","id":2,"account":"1***************8"},"account":"1***************9"}
          脫敏后的原始Map對(duì)象:{phone=17709141590, name=張無(wú)忌, id=1, daughter={phone=188*****567, name=****女兒, id=2, account=1***************8}, account=14669037943256249}

          我們發(fā)現(xiàn),脫敏時(shí)是成功的,但是卻把原始對(duì)象中的內(nèi)嵌innerMap對(duì)象中的值修改了

          fc0338d1bf1a9a7224cff38b703ef7bf.webp
          ?

          要知道,作為脫敏組件,你可以有點(diǎn)小bug,你也可以原始簡(jiǎn)單粗暴,甚至你都可以脫敏失敗(本該脫敏的卻沒(méi)有脫敏),但是你千萬(wàn)不能修改業(yè)務(wù)中使用的對(duì)象啊。

          ?

          雖然問(wèn)題很大,但是我們還是要沉著應(yīng)對(duì)問(wèn)題,仔細(xì)分析問(wèn)題。做到泰山崩于前而色不變,才是打工人的正確處事方式。不然只會(huì)越急越慌,越慌越不冷靜,最后買1送3(修1個(gè)bug,新引入3個(gè)bug)

          簡(jiǎn)單debug,加上看源代碼,其實(shí)這個(gè)問(wèn)題還是比較容易發(fā)現(xiàn)的,主要問(wèn)題就是在復(fù)制Map對(duì)象的姿勢(shì)不對(duì)

          ?

          如下,我們是使用這樣的方式來(lái)復(fù)制Map的。本來(lái)是想做深度clone的,但是這種事做不到深度clone的。對(duì)于有內(nèi)嵌的對(duì)象的時(shí)候只能做到淺clone

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

          所以,只有一層關(guān)系的簡(jiǎn)單Map是可以脫敏成功的,且不會(huì)改變?cè)瓉?lái)的Map。但是對(duì)于有嵌套的Map對(duì)象時(shí),就會(huì)修改嵌套Map對(duì)象中的值了。

          問(wèn)題的原因是啥,如何解決,思路是啥

          ?

          從上面的分析中就可以得出其根本原因:沒(méi)有正確地深度clone Map對(duì)象

          那很自然地,我們的解決思路就是找到一種合適的深度 clone Map對(duì)象的方式就OK了。

          然后我就問(wèn)ChatGPT了,ChatGPT的回答有下面幾個(gè)方法

          1. 使用序列化和反序列化:通過(guò)將對(duì)象序列化為字節(jié)流,然后再將字節(jié)流反序列化為新的對(duì)象,可以實(shí)現(xiàn)深度克隆。需要注意被克隆的對(duì)象及其引用類型成員變量都需要實(shí)現(xiàn)Serializable接口。
          2. 使用第三方庫(kù):除了上述兩種方式,還可以使用一些第三方庫(kù),例如Apache Commons的SerializationUtils類、Google的Gson庫(kù)等,它們提供了更簡(jiǎn)潔的方法來(lái)實(shí)現(xiàn)深度克隆。
          3. 使用JSON序列化和反序列化:將對(duì)象轉(zhuǎn)換為JSON字符串,然后再將JSON字符串轉(zhuǎn)換為新的對(duì)象。需要使用JSON庫(kù),如Jackson、Gson等。
          4. 使用Apache Commons的BeanUtils類:BeanUtils提供了一個(gè)cloneBean()方法,可以對(duì)JavaBean進(jìn)行深度克隆。需要注意,被克隆的對(duì)象及其引用類型成員變量都需要實(shí)現(xiàn)Serializable接口。
          5. 最佳實(shí)踐是根據(jù)需求和具體情況靈活應(yīng)用,或者采用第三方庫(kù)實(shí)現(xiàn)對(duì)象克隆,如 Apache Commons BeanUtils、Spring BeanUtils 等。

          上面幾個(gè)方式基本上可以分為3類:

          1. 序列化和反序列化:JDK自帶的序列化(需要實(shí)現(xiàn)Serializable接口);利用Gson,FastJson,Jackson等JSON序列化工具序列化后再反序列化;其他序列化框架序(如Hessain等)列化反序列化
          2. 利用第三方庫(kù):第三方庫(kù)直接clone對(duì)象。但都有一定的限定條件
          3. 視情況而定:基本上沒(méi)有一種通用的方法,可以適配是有的深度clone場(chǎng)景。所以ChatGPT提出了這一種不是辦法的辦法

          根據(jù)上面的一番探索,發(fā)現(xiàn)還得自己敲代碼造輪子。

          a1a424cb33fcb7d9fff13382a514d55c.webp
          ?

          那其實(shí)你仔細(xì)分析后發(fā)現(xiàn):我們確實(shí)不需要通用的深度clone對(duì)象的能力。我們只需要把Map類型深度clone好就行,對(duì)于其他自定義DTO,我們是不需要深度clone的。

          然后就是卡卡一頓猛敲,如下:常規(guī)遞歸操作

                
                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 {  
                      // 有一定的風(fēng)險(xiǎn)會(huì)反射調(diào)用失敗  
                      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操作,嘎嘎綠燈,收拾完事。

          fec3fc86fc869621a170126a034206c8.webp

          貌似,到這我們就可以下班了。

          4b69bf0123541a481f76c9b3bae2cbb2.webp

          但是,等等,這篇文章貌似,好像,的確,應(yīng)該缺點(diǎn)啥吧?

          這尼瑪不是講的是Guava嗎,到這為止,貌似跟Guava毛關(guān)系沒(méi)有啊!!!

          3b5911e0c10e669de2c4a538c2828649.webp

          那我們繼續(xù),容你再思考一下,到現(xiàn)在為止,我們的深度clone方案有什么問(wèn)題。

          好的,我懂的。咱看到這,圖的就是一個(gè)樂(lè),你讓我思考,這不是強(qiáng)人所難嗎

          ?

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

          78b2b2c21c4fd2a0dd0e466cdf044830.webp
          ?

          那咱們就直接看問(wèn)題:就是啥都好,問(wèn)題就是性能比較差!!!

          如果你是一個(gè)老司機(jī),你可能看到上面的代碼就已經(jīng)感覺(jué)到了,性能是相對(duì)比較低的。

          ?

          基本上,Map層級(jí)越深,字段越多,內(nèi)嵌的集合對(duì)象元素越多,性能越差!!!

          至于差到什么程度,其實(shí)是可以通過(guò) 微基準(zhǔn)測(cè)試框架JMH來(lái)實(shí)際測(cè)試一下就知道了。這里我就不測(cè)試了。

          ?

          那我們還有什么辦法來(lái)解決這個(gè)性能問(wèn)題嗎?

          如果我們還是集中于深度clone對(duì)象上做文章,去找尋性能更高的深度clone框架或者是類庫(kù)的話,那么其實(shí)這個(gè)問(wèn)題就已經(jīng)走偏了。

          我們?cè)賮?lái)回顧一下我們之前的問(wèn)題:

          ?

          我們需要對(duì)Map對(duì)象按照key來(lái)脫敏,所以我們選擇了深度clone Map,然后對(duì)Map遍歷按照key脫敏后序列化。

          ?

          那其實(shí)我們最終要解決的問(wèn)題是:Map對(duì)象序列化后的字符串得是按照key的脫敏規(guī)則脫敏后的字符串。

          所以,其實(shí)我們除了深度clone這一條路外,還有另外兩條路:

          • 1.自定義Map類型的序列化器:在序列化Map的時(shí)候,如果有脫敏規(guī)則則應(yīng)用脫敏規(guī)則來(lái)序列化Map
          • 2.轉(zhuǎn)換Map為脫敏后的Map:自定義Map對(duì)象,將脫敏規(guī)則作為轉(zhuǎn)換函數(shù)來(lái)把普通的Map轉(zhuǎn)換為脫敏的Map

          對(duì)于第一種方式,要看你使用的Json框架是否支持(一般Map類型用的都是內(nèi)置的Map序列化器,不一定可以自定義)。

          那第二種方式,到底應(yīng)該如何玩呢. 如下:

                
                // 脫敏轉(zhuǎn)換函數(shù)
          public interface MaskFunction<KVE{
              /**
               * @param k key
               * @param v value
               * @return 根據(jù)key和value得到脫敏(如果有需要的話)后的值
               */

              mask(K k, V v);
          }
          // 自定義MaskMap對(duì)象,經(jīng)過(guò)MaskFunction函數(shù),將普通Map轉(zhuǎn)換為脫敏后的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的玩法,而是轉(zhuǎn)換的玩法,所以,這種操作是非常輕量級(jí)的。

          但是這種玩法也有缺點(diǎn):比較麻煩,需要override好多方法(不然就需要熟讀Map序列化器來(lái)找到最小化需要override的方法,并且不太靠譜),并且全部都要是轉(zhuǎn)換的玩法。

          終于,終于,終于,這個(gè)時(shí)候該輪到我們Guava登場(chǎng)了。

          b8badbfd408a6082060c14f8e9873c05.webp

          Guava登場(chǎng)

          用過(guò)Guava的人都知道,Guava中很多好用的方法,但是我們平常用的多的也就是那幾個(gè)。所以這個(gè)時(shí)候你說(shuō)需要轉(zhuǎn)換Map,那我們是不是可以去Guava中看看有沒(méi)有現(xiàn)成的方案,沒(méi)準(zhǔn)有驚喜!!!

          7bd45b426c3eaa1d47a6785cf0e4f1d8.webp

          一看就是驚喜:

          e7c85f239f77b4598ee07472ff8da63c.webp

          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.

          ?

          返回一個(gè)Map的試圖,其中它的值是從原來(lái)map中entry派生出來(lái)的。相較于transformValues方法,這個(gè)基于entry的轉(zhuǎn)換邏輯是既依賴于key又依賴于value。變換后的映射的所有其他屬性(例如迭代順序)均保持不變

          這不正是我們上面需要實(shí)現(xiàn)的 MaskMap嗎!!!

          除此之外,我們還需要支持,對(duì)集合類型的脫敏轉(zhuǎn)換,再去看一下呢,又是驚喜!!!

          de0014e3ca2845bc45d0fb86282494e0.webp

          到這,基本上,我們已經(jīng)在Gauva中找到了我們所需要的全部元素了。下面就是簡(jiǎn)單集合脫敏規(guī)則使用下就好了

                
                public class MaskEntryTransformer implements Maps.EntryTransformer<ObjectObjectObject{
              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);
              }


              /**
               * 按照脫敏規(guī)則脫敏基本數(shù)據(jù)類型
               *
               * @param key
               * @param value
               * @return
               */

              private Object transformPrimitiveType(final String key, final Object value) {
                  // ...
              }
          }

          那脫敏的地方只要轉(zhuǎn)換一下Map就可以了,如下:

                
                public class DataMask {
           /**
            * 將需要脫敏的字段先進(jìn)行脫敏操作,最后轉(zhuǎn)成json格式
            * @param object 需要序列化的對(duì)象
            * @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();
            }
           }

          }

          一點(diǎn)疑問(wèn)

          我們調(diào)用了transformEntries方法,是可以根據(jù)key(可以找到脫敏規(guī)則)和value(可以判斷類型)來(lái)轉(zhuǎn)換的。但是Map中的有些API實(shí)際上可能只有value(比如values())或者只有key(比如get()方法)的,那這種EntryTransformer是如何生效的?

          ?

          你是不是有那么一點(diǎn)好奇呢?

          我們就不去想了,直接看代碼:

          • 對(duì)于get()方,只有key參數(shù):

          但是比較容易通過(guò)key拿到對(duì)應(yīng)的value,然后把key和value傳給轉(zhuǎn)換函數(shù)就可以了

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

          這里的實(shí)現(xiàn)其實(shí)有一個(gè)細(xì)節(jié),不知道大家注意沒(méi)有。

          就是這一個(gè)判斷:(value != null || fromMap.containsKey(key)) ,我們上面實(shí)現(xiàn)的時(shí)候直接沒(méi)有考慮到值為null的情況(但value為null,但是有對(duì)應(yīng)的key的時(shí)候還是應(yīng)該要調(diào)用轉(zhuǎn)換函數(shù)的)

          • 對(duì)于values()方,只返回值
                
                public Collection<V2> values() {  
              return new Values<K, V2>(this);  
          }

          也是一樣,返回一個(gè)轉(zhuǎn)換后的 Collection,其中Collection中的值也是經(jīng)過(guò) transformer轉(zhuǎn)換的。

          而對(duì)于迭代器也是一樣的,都有對(duì)應(yīng)的實(shí)現(xiàn)類把轉(zhuǎn)換邏輯放進(jìn)去了。

          Guava閑聊

          Guava的Veiw思想

          其實(shí)上面的這種 Veiw的基本思想,在Guava中有非常多的場(chǎng)景應(yīng)用的,如

          • com.google.common.base.Splitter#split
          • com.google.common.collect.Lists#partition
          • com.google.common.collect.Sets#difference
          • ...other
          ?

          這種 Veiw的基本思想,其實(shí)就是懶加載的思想:在調(diào)用的時(shí)候不實(shí)際做轉(zhuǎn)換,而是在實(shí)際使用的時(shí)候會(huì)做轉(zhuǎn)換

          比如:com.google.common.base.Splitter#split在調(diào)用的時(shí)候?qū)嶋H上并不是立刻馬上去分隔字符串,而是返回一個(gè)IterableView對(duì)象。只是在實(shí)際迭代這個(gè)IterableView對(duì)象時(shí)才會(huì)實(shí)際去切分字符串。

          在比如com.google.common.collect.Sets#difference在調(diào)用的時(shí)候并不會(huì)立刻馬上去做兩個(gè)集合的差操作,而是返回一個(gè) SetViewView對(duì)象,只有在實(shí)際使用這個(gè)View對(duì)象的API的時(shí)候才會(huì)真正做差操作。

          ?

          注意點(diǎn):這種思想大部分場(chǎng)景下,會(huì)是性能友好型的,但是也有例外。我們要分清楚場(chǎng)景。

          比如,分割字符串的方法com.google.common.base.Splitter#split,如果調(diào)用完之后不做任何操作,或者只會(huì)遍歷一次(大部分場(chǎng)景都是只遍歷一次),那么這其實(shí)就是最好的方法。但是如果調(diào)用完之后,還需要遍歷很多次,那么這種場(chǎng)景下,性能可能不是最好的。

          ?

          所以,對(duì)于我們來(lái)講,如果Guava工具包中沒(méi)有我們需要的View的方法,那么我們可以自己按照這個(gè)思想來(lái)造一個(gè)。如果已經(jīng)有了,要區(qū)分場(chǎng)景使用.

          簡(jiǎn)單聊一下Guava API的兼容性

          升級(jí)過(guò)Guava版本的同學(xué)可能深有體會(huì),升級(jí)Guava是一件比較頭疼的事情。因?yàn)?strong style="color:rgb(53,148,247);">Gauva API的兼容性是做得很差的。(當(dāng)然這里主要還是因?yàn)榇蟛糠滞瑢W(xué)沒(méi)有認(rèn)真閱讀官方的文檔導(dǎo)致的)

          因?yàn)椋倬W(wǎng)首頁(yè)就直接提醒了Guava API的兼容性原則:

          35113d5ab028354588dde92e432ffe6f.webp

          具體請(qǐng)參考 Guava 官網(wǎng)https://guava.dev/

          這對(duì)于我們做基礎(chǔ)組件的同學(xué)來(lái)講,是一件尤其需要注意的事情。因?yàn)橐坏┦褂昧饲昂蟛患嫒莸腁PI,那么使用組件的應(yīng)用很可能因?yàn)锳PI不兼容,導(dǎo)致無(wú)法運(yùn)行的問(wèn)題。

          ?

          所以對(duì)于做組件的同學(xué),對(duì)于Guava的使用一定要慎重:能不用就不用,必須要用的話一定不能使用以@Beta注解標(biāo)注的方法或者類。

          總結(jié)

          ?

          Gauva確實(shí)是一個(gè)保藏庫(kù),當(dāng)你要造輪子的時(shí)候不妨先看看Gauva中有沒(méi)有現(xiàn)成的輪子。每看一次,也許就多一次驚喜。

          ?

          這一次是Map對(duì)象脫敏場(chǎng)景遇上了Guava的 Maps#transformEntries(Map<K,V1>, .Maps.EntryTransformer<? super K,? super V1,V2>) ,那么你又有什么場(chǎng)景偶遇了Guava呢?,可以在評(píng)論區(qū)聊一下。

          瀏覽 47
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人网站在线免费观看视频 | 人人干人人模 | 日本A片免费观看 | 日本五十路熟女视频 | 国产做a爱一级毛片 |