【網上的都不靠譜?仍是得改源碼】用Javasisst的字節碼插樁技術,完全解決Gson轉Map時,Int變成double問題

1、探究起因

首先申明一下,咱們要解決的問題有兩個:java

  • Json串轉Map時,int變double問題
  • Json串轉對象時,對象屬性中的Map,int變double問題

而後,咱們來了解一下,Gson實現Json反序列化的源碼:json

  1. Gson內部會維護一個類型適配器集合,裏面大概有十多個內置的TypeAdapter。涵蓋了八大基本類型的TypeAdapter,而且還有一個ObjectTypeAdapter。同時Gson支持自定義TypeAdapter,能夠在內置的適配器集合中添加新的類型適配器
  2. 在具體的Json數據反序列化時,首先會根據傳入的對象Class,來獲取對應的TypeAdapter,而後根據獲取的TypeAdapter實現Json到對象的轉換。
  3. 所以,在反序列化時,int(Integer)、string等對象屬性能匹配到對應的TypeAdapter,進行正確的反序列化。可是若是對象屬性爲Map時(或者自己就是Json串轉Map),將默認由ObjectTypeAdapter類來完成數據的解析。
  4. ObjectTypeAdapter的核心代碼:
@Override public Object read(JsonReader in) throws IOException {
    JsonToken token = in.peek();
    switch (token) {
    case BEGIN_ARRAY:
      List<Object> list = new ArrayList<Object>();
      in.beginArray();
      while (in.hasNext()) {
        list.add(read(in));
      }
      in.endArray();
      return list;
 
    case BEGIN_OBJECT:
      Map<String, Object> map = new LinkedTreeMap<String, Object>();
      in.beginObject();
      while (in.hasNext()) {
        map.put(in.nextName(), read(in));
      }
      in.endObject();
      return map;
 
    case STRING:
      return in.nextString();
 
    case NUMBER:
      return in.nextDouble();
 
    case BOOLEAN:
      return in.nextBoolean();
 
    case NULL:
      in.nextNull();
      return null;
 
    default:
      throw new IllegalStateException();
    }
  }
複製代碼

上面能夠看到,針對全部的Number類型,均使用了nextDouble()來返回了一個Double對象,這也就是問題的根源。ide

2、網上的「半」解決方案

網羅了網上的解決方案,無非就如下幾種。性能

2.1 自定義一個適配TreeMap的TypeAdapter

從新添加一個自定義的TypeAdapter,解決實現Json串轉Map。注意它解決了Json串轉Map問題,可是未能解決Json串轉對象問題測試

Gson gson = new GsonBuilder().registerTypeAdapter(new TypeToken<TreeMap<String, Object>>(){}.getType(), 
        new JsonDeserializer<TreeMap<String, Object>>() {
            @Override
            public TreeMap<String, Object> deserialize( JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

                TreeMap<String, Object> treeMap = new TreeMap<>();
                JsonObject jsonObject = json.getAsJsonObject();
                Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
                for (Map.Entry<String, JsonElement> entry : entrySet) {
                    treeMap.put(entry.getKey(), entry.getValue());
                }
                return treeMap;
            }
        }).create();
複製代碼

2.2 自定義一個適配指定類的TypeAdapter

從新添加一個自定義的TypeAdapter,解決實現Json串轉指定對象。注意它僅僅解決了Json串轉指定對象問題,可是未能解決Json串轉Map問題ui

而且經測試,如下代碼使用時會報錯,緣由不明……this

public final class MyTypeAdapter extends TypeAdapter<Object> {

  public static final FACTORY(Class clazz) {
    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
      if (type.getRawType() == clazz) {
        return (TypeAdapter<T>) new ObjectTypeAdapter(gson);
      }
      return null;
    }
  };

  private final Gson gson;

  ObjectTypeAdapter(Gson gson) {
    this.gson = gson;
  }

  @Override public Object read(JsonReader in) throws IOException {
    JsonToken token = in.peek();
    switch (token) {
    case BEGIN_ARRAY:
      List<Object> list = new ArrayList<Object>();
      in.beginArray();
      while (in.hasNext()) {
        list.add(read(in));
      }
      in.endArray();
      return list;

    case BEGIN_OBJECT:
      Map<String, Object> map = new LinkedTreeMap<String, Object>();
      in.beginObject();
      while (in.hasNext()) {
        map.put(in.nextName(), read(in));
      }
      in.endObject();
      return map;

    case STRING:
      return in.nextString();

    case NUMBER:
      Double tmp = in.nextDouble();
      if (tmp.longValue() = tmp.doubleValue)
        return Long.valueOf(tmp.longValue());
      return tmp;
      
    case BOOLEAN:
      return in.nextBoolean();

    case NULL:
      in.nextNull();
      return null;

    default:
      throw new IllegalStateException();
    }
  }

  @SuppressWarnings("unchecked")
  @Override public void write(JsonWriter out, Object value) throws IOException {
    if (value == null) {
      out.nullValue();
      return;
    }

    TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
    if (typeAdapter instanceof ObjectTypeAdapter) {
      out.beginObject();
      out.endObject();
      return;
    }

    typeAdapter.write(out, value);
  }
}

//使用
Gson gson = new GsonBuilder().registerTypeAdapterFactory(MyTypeAdaptor.FACTORY(Person.class)).create();
複製代碼

3、完全的解決方案

咱們知道,還有一種完全的解決方案,那就是修改源代碼。可是修改源代碼是一件痛苦的事情:google

  • 須要解決各類依賴環境問題
  • 有些沒有源碼包的還須要反編譯成Java文件
  • 從新打包,從新打包有時不那麼順利,可能出現各類JavaDoc問題之類的……
  • 各類麻煩,誰用誰知道……

所以,咱們嘗試用Javasisst進行字節碼插樁!spa

3.1 Javasisst入門

簡單入門使用,看這篇簡書就好:www.jianshu.com/p/b9b3ff0e1…code

簡單概括就是,讀取原class文件,修改類、方法、屬性等,而後從新生成class字節碼文件

咱們使用一個叫作insertAt()的方法,按行號來插入代碼段(若是行號表包含在類文件中),將編譯後的代碼插入到指定行號位置。

注意:行號是源文件jar包中相關位置的行號。

3.2 方法步驟

下載好gson-2.7.jargson-2.7-sources.jar這兩個文件。 而後從gson-2.7-sources.jar中找到要修改的相關類的具體行號位置:

com.google.gson.internal.bind.ObjectTypeAdapter

注意:行號應是78,而不是79!

而後書寫插樁代碼:

/** * @Description: javasisst插樁 * @Author localhost01.cn * @Date: Created in 22:29 2019-03-27 */
public class Main {
    public static void main(String[] args) throws Exception {

        // 1.獲得反編譯的池
        ClassPool pool = ClassPool.getDefault();
		// 2.導入須要用到的包
        pool.importPackage("com.google.gson.stream");
        pool.importPackage("java.io");
        pool.importPackage("java.util");
        pool.importPackage("java.lang");
        pool.importPackage("com.google.gson.internal");

        // 3.取得須要反編譯的jar文件
        pool.insertClassPath("D:\\gson-2.7.jar");

        // 4.取得須要反編譯要修改的類,注意是全路徑
        CtClass cc = pool.get("com.google.gson.internal.bind.ObjectTypeAdapter");

        // 5.取得須要修改的方法
        CtMethod method = cc.getDeclaredMethod("read");

        method.insertAt(78,   "if (true){\n"
        					+ " Double tmp = Double.valueOf(in.nextDouble());\n"
                			+ " if (tmp.longValue() == tmp.doubleValue()) {\n"
                			+ " return Long.valueOf( tmp.longValue());\n" 				
                			+ " } else {\n"
                			+ " return tmp;\n" 
                			+ " }\n"
                			+ "}");

        // 6.寫入
        cc.writeFile();  //這兒也能夠傳入一個參數,指定新class要輸出的位置
        
        System.out.println("alright!");
    }
}
複製代碼

OK,把生成的ObjectTypeAdapter.class文件替換到gson-2.7.jar包的相關位置便可。

到這兒就結束了!

你覺得還很複雜?

相關文章
相關標籤/搜索