设计模式笔记(1)——从Gson看工厂模式

前言

“设计模式”这四个字,对于使用面向对象语言进行软件开发的程序员来说绝不会陌生,不过对于很多经验尚浅的而言也仅仅是知道而已,如果仅仅是一股脑地在需求与编码中忙碌,很可能都说不出几种设计模式,更别谈在实际的开发中使用了。学习设计模式可以说是开发者的晋级之路,熟练掌握设计模式能很大程度地提升开发者的设计思想和封装思想,加深对面向对象设计的理解、提高编码效率与质量。最近有了个写写与设计模式相关的博客的想法,算是对自己的一个总结,也为大家分享一些经验,最近可能主要就是谈谈最常用的工厂模式、建造者模式和观察者模式。我不喜欢为了写一个主题而去凭空捏造出一个场景,所以写一篇文章绝对是围绕一些实际使用场景谈起,比如这篇文章是我在阅读Gson源码以及对Http请求进行封装时得来的灵感,将会围绕Gson中使用的工厂模式来探讨这种模式的设计思想、优势和用法,而建造者模式将会围绕Glide谈起、观察者模式将会围绕RxJava谈起。由于工作繁忙、为人懒散,我且慢慢写,诸位且慢慢看。

基础知识

网上可以搜到很多关于工厂模式的文章,不过基本上都只是简单介绍,一些例子也都只是没有半点意义的捏造场景,很多人看完这些文章大概也都只是知道了这么个东西,在实际开发中依然难以使用。很常见的一种介绍:

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

这句话能够得到的信息主要就是:这是一种创建型模式,我们可以用这种模式来对对象的创建进行封装,对于一些有关联的对象我们可能并不想在主要的逻辑中对它们挨个进行创建,而是将他们的创建过程封装在一个工具类中,在需要用到时直接从这个工具类中去取对象,以简化业务逻辑的代码。这个工具类就好比一个工厂,而我们要使用的对象就好比这个工厂生产出的产品,把我们程序中业务逻辑的代码看做客户,其实客户并不关心产品是如何制造出来的,客户关心的应该是对这些产品的使用,举个例子,我们把水果刀看做一个对象,切水果是它的属性,我们需要一把水果刀是需要它能切水果的这个属性,而绝对不是去了解这把水果刀被制造、被打磨的过程,所以工厂模式其实是将业务与过程的抽离,是面向对象编程的一种具体体现,这也是它为何会被称为“创建对象的最佳方式”。

在进入实际场景之前,我们还是要看个简单的例子,好对工厂模式有个基本的理解,因为后面的内容绝不轻松,有个基本的了解肯定是极其必要的。(高能预警:我举例子的水平很烂,如果你在别处看过一些基本的例子的话可以跳过这里)

我们要画出矩形、圆形、三角形三个图形,我们可以定义三个类,Rectangle、Triangle、Circle,在这三个类中分别定义draw()方法来画图:

public class Rectangle {

    public void draw() {

    }

}

public class Triangle {

    public void draw() {

    }

}

public class Circle {

    public void draw() {

    }

}

然后分别创建它们的实例来调用draw()方法……这也太TM的low了吧。当然,这是初学者的方式,我们来稍作进阶。可以看到他们都有draw()方法,我们可以将它抽象出来,定义一个接口Shape:

public interface Shape {

    void draw();

}

然后Rectangle、Triangle、Circle分别实现这个接口,在使用时就可以这样:

Shape rectangle = new Rectangle();

rectangle.draw();

...

其实还是挺low的对吧?我们不妨跳出这个创建对象的思维——难道我们不能在使用的时候直接把这个对象拿出来么!!!当然可以,让我们来创建一个制作形状的工厂:

public class ShapeFactory {

  public Shape getShape(String shapeType){

      if(shapeType == null){

        return null;

      }       

      if(shapeType.equalsIgnoreCase("CIRCLE")){

        return new Circle();

      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){

        return new Rectangle();

      } else if(shapeType.equalsIgnoreCase("Triangle")){

        return new Triangle();

      }

      return null;

  }

}

接下来我们只需要创建ShapeFactory的对象factory,factory.getShape("Rectangle").draw()即可画矩形,其他情况也是一样。

例子到这里就结束了,看起来并不难,但是实际使用往往会更复杂。

关于GSON

其实关于工厂模式的基本概念和用法也就上面所说的那样,很简单,不过凭空捏造的场景显然是抽象的,所以这一节我们来看看GSON库是如何使用工厂模式的。这一节将会跟踪GSON序列化与反序列化的源码,探究其中工厂模式的使用,读起来可能会有些费劲,甚至有很多偏离主题的东西,不过我觉得这些是有意义的,GSON这个Google出品的官方库在设计上真的极为巧妙,需要大家慢慢理解消化。这一节很多东西都有参考https://blog.csdn.net/chunqiuwei/article/category/5881669这一系列文章,大家也可以去看看。

GSON这个开源库大家肯定都不陌生,算是目前使用最为广泛的Json解析的库之一。在2.0之后的版本中引入了流式处理,代码高效、优雅、稳定。一般的Gson使用如下:

Gson gson = new Gson();

// Serialize

String s = gson.toJson(xxx);

// Deserialize

Xxx xxx = gson.fromJson(s, Xxx.class);

更好的方式是使用GsonBuilder来创建Gson对象,并为其添加TypeAdapter。

先从最基本的使用入手,让我们看看直接new Gson()之后调用fromJson()方法后到底发生了什么、Gson又是如何返回给我们反序列化结果的。

追踪源码可以发现,调用Gson的无参构造器其实是调用了另一个有参构造器,这里面的内容相当之多:

Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingStrategy,

      final Map> instanceCreators, boolean serializeNulls,

      boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,

      boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,

      LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,

      int timeStyle, List builderFactories,

      List builderHierarchyFactories,

      List factoriesToBeAdded) {

    ...

    List factories = new ArrayList();

    // built-in type adapters that cannot be overridden

    factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);

    factories.add(ObjectTypeAdapter.FACTORY);

    // the excluder must precede all adapters that handle user-defined types

    factories.add(excluder);

    // users' type adapters

    factories.addAll(factoriesToBeAdded);

    // type adapters for basic platform types

    factories.add(TypeAdapters.STRING_FACTORY);

    factories.add(TypeAdapters.INTEGER_FACTORY);

    factories.add(TypeAdapters.BOOLEAN_FACTORY);

    factories.add(TypeAdapters.BYTE_FACTORY);

    factories.add(TypeAdapters.SHORT_FACTORY);

    TypeAdapter longAdapter = longAdapter(longSerializationPolicy);

    factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter));

    factories.add(TypeAdapters.newFactory(double.class, Double.class,

            doubleAdapter(serializeSpecialFloatingPointValues)));

    factories.add(TypeAdapters.newFactory(float.class, Float.class,

            floatAdapter(serializeSpecialFloatingPointValues)));

    factories.add(TypeAdapters.NUMBER_FACTORY);

    factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY);

    factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY);

    factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter)));

    factories.add(TypeAdapters.newFactory(AtomicLongArray.class, atomicLongArrayAdapter(longAdapter)));

    factories.add(TypeAdapters.ATOMIC_INTEGER_ARRAY_FACTORY);

    factories.add(TypeAdapters.CHARACTER_FACTORY);

    factories.add(TypeAdapters.STRING_BUILDER_FACTORY);

    factories.add(TypeAdapters.STRING_BUFFER_FACTORY);

    factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));

    factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));

    factories.add(TypeAdapters.URL_FACTORY);

    factories.add(TypeAdapters.URI_FACTORY);

    factories.add(TypeAdapters.UUID_FACTORY);

    factories.add(TypeAdapters.CURRENCY_FACTORY);

    factories.add(TypeAdapters.LOCALE_FACTORY);

    factories.add(TypeAdapters.INET_ADDRESS_FACTORY);

    factories.add(TypeAdapters.BIT_SET_FACTORY);

    factories.add(DateTypeAdapter.FACTORY);

    factories.add(TypeAdapters.CALENDAR_FACTORY);

    factories.add(TimeTypeAdapter.FACTORY);

    factories.add(SqlDateTypeAdapter.FACTORY);

    factories.add(TypeAdapters.TIMESTAMP_FACTORY);

    factories.add(ArrayTypeAdapter.FACTORY);

    factories.add(TypeAdapters.CLASS_FACTORY);

    // type adapters for composite and user-defined types

    factories.add(new CollectionTypeAdapterFactory(constructorConstructor));

    factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));

    this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);

    factories.add(jsonAdapterFactory);

    factories.add(TypeAdapters.ENUM_FACTORY);

    factories.add(new ReflectiveTypeAdapterFactory(

        constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));

    this.factories = Collections.unmodifiableList(factories);

  }

因为看起来太费劲,我省略了一些代码,希望大家把注意力放在这个factories上。可以看到this.factories是一个TypeAdapterFactory的集合,而Gson进行序列化和反序列化其实都是依赖于TypeAdapterFactory,用户也可以自定义TypeAdapterFactory放入factories中实现特别的需求,这个稍后再提。Gson的构造器主要工作就是初始化了一大批预置的TypeAdapterFactory并将它们存入factories,以供后续的序列化/反序列化使用。

接下来我们可以看看反序列化过程了,调用很简单:A a = gson.fromJson(jsonStr, A.class),进入fromJson()方法,发现其实Gson中一层一层地调用了很多个fromJson的重载方法,最终我们看看fromJson(JsonReader reader, Type typeOfT):

  public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {

    boolean isEmpty = true;

    boolean oldLenient = reader.isLenient();

    reader.setLenient(true);

    try {

      reader.peek();

      isEmpty = false;

      TypeToken typeToken = (TypeToken) TypeToken.get(typeOfT);

      TypeAdapter typeAdapter = getAdapter(typeToken);

      T object = typeAdapter.read(reader);

      return object;

    } catch (EOFException e) {

      /*

      * For compatibility with JSON 1.5 and earlier, we return null for empty

      * documents instead of throwing.

      */

      if (isEmpty) {

        return null;

      }

      throw new JsonSyntaxException(e);

    } catch (IllegalStateException e) {

      throw new JsonSyntaxException(e);

    } catch (IOException e) {

      // TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxException

      throw new JsonSyntaxException(e);

    } catch (AssertionError e) {

      throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);

    } finally {

      reader.setLenient(oldLenient);

    }

  }

上面的代码不难理解,在进行了一系列的操作之后,最终以return object将反序列化后的对象传递给上层——一次反序列化到这里也就基本结束了——才怪。我们关注的重点应该是这三句:

TypeToken typeToken = (TypeToken) TypeToken.get(typeOfT);

TypeAdapter typeAdapter = getAdapter(typeToken);

T object = typeAdapter.read(reader);

最终反序列化的对象是通过TypeAdapter的read方法获取的,而这个TypeAdapter又是通过getAdapter得到,那我们当然要看看getAdapter中有何玄机:

public TypeAdapter getAdapter(TypeToken type) {

    TypeAdapter cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);

    if (cached != null) {

      return (TypeAdapter) cached;

    }

    Map, FutureTypeAdapter> threadCalls = calls.get();

    boolean requiresThreadLocalCleanup = false;

    if (threadCalls == null) {

      threadCalls = new HashMap, FutureTypeAdapter>();

      calls.set(threadCalls);

      requiresThreadLocalCleanup = true;

    }

    // the key and value type parameters always agree

    FutureTypeAdapter ongoingCall = (FutureTypeAdapter) threadCalls.get(type);

    if (ongoingCall != null) {

      return ongoingCall;

    }

    try {

      FutureTypeAdapter call = new FutureTypeAdapter();

      threadCalls.put(type, call);

      for (TypeAdapterFactory factory : factories) {

        TypeAdapter candidate = factory.create(this, type);

        if (candidate != null) {

          call.setDelegate(candidate);

          typeTokenCache.put(type, candidate);

          return candidate;

        }

      }

      throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);

    } finally {

      threadCalls.remove(type);

      if (requiresThreadLocalCleanup) {

        calls.remove();

      }

    }

  }

typeTokenCache是一个存放TypeAdapter的Map集合,以TypeToken作为key,可以理解为在应用运行过程中所用过的TypeAdapter都会作为缓存放在这个对象中,每次执行getAdapter方法的时候都会先尝试从缓存中获取TypeAdapter以提高效率,如果没有获取到则继续往下执行,我们省略掉中间的代码直接来到这一段:

for (TypeAdapterFactory factory : factories) {

        TypeAdapter candidate = factory.create(this, type);

        if (candidate != null) {

          call.setDelegate(candidate);

          typeTokenCache.put(type, candidate);

          return candidate;

        }

      }

我们在这里又与factories相遇了,我们已经知道,在没有用户自定义TypeAdapter的情况下,这个集合中存放着一些预置的TypeAdapterFactory,在这一步中,将会遍历factories,以寻找到可以用来为目标类型进行反序列化操作的TypeAdapter,而这个过程是通过调用TypeAdapterFactory的create方法实现的。看到这里大家应该有个大致的概念了:Gson反序列化的大致流程,是在创建Gson实例之后,预置了一些默认的TypeAdapterFactory到factories中,在调用到反序列化时,从factories中取出合适的TypeAdapterFactory并将其转换为TypeAdapter的实例,调用其中的read方法,完成反序列化。实际过程当然还要复杂得多,这一点下面会简单探究一下,不过这里我们可以先回到本文的主题,工厂模式的应用。这个过程是一个典型的工厂模式的使用,Gson创建了很多不同的TypeAdapterFactory,它们都可以被转换为TypeAdapter,而要完成反序列化操作只需要获取合适的TypeAdapter实例即可,调用者并不关心这个TypeAdapter是怎么创建出来的,而只是想要做到我传给你一个TypeToken、你就去给我创建一个我需要的TypeAdapter即可;而对于工厂来说,这些TypeAdapter都是具有共性的,只需要用TypeToken作为标识,就可以创建出相应的产品。

其实到这里这一节就可以告一段落了,不过我还想把Gson解析的过程讲得更深入一些,这个过程的设计真的相当巧妙,熟悉这个过程,我相信会对我们这些开发人员的能力带来很大的提升。当然,如果你对这没太大兴趣,跳到下一节即可。

前面已经说到Gson利用工厂模式为需要序列化/反序列化的类型创建出对应的TypeAdapter,但是这个创建的具体过程其实是相当曲折的,这个过程也是决定序列化/反序列化的效率的关键因素。由Gson的有参构造器可以清楚地知道,Gson预置的TypeAdapterFactory基本都是为基本类型的解析所准备的,当我们传入的类型是我们自定义的实体类时,Gson又是怎么处理的呢?

下面先看看STRING_FACTORY:

public static final TypeAdapterFactory STRING_FACTORY = newFactory(String.class, STRING);

public static TypeAdapterFactory newFactory(

    final Class type, final TypeAdapter typeAdapter) {

  return new TypeAdapterFactory() {

    @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal

    @Override public TypeAdapter create(Gson gson, TypeToken typeToken) {

      return typeToken.getRawType() == type ? (TypeAdapter) typeAdapter : null;

    }

    @Override public String toString() {

      return "Factory[type=" + type.getName() + ",adapter=" + typeAdapter + "]";

    }

  };

}

STRING_FACTORY通过调用方法newFactory创建,重写了TypeAdapterFactory的create方法,其实很多预置的TypeAdapterFactory都是以这样的方式创建的。当create方法被调用时,Gson会做一次判断:我们要解析的目标类型是否是String类型,如果是,则返回用来解析String类型的TypeAdapter,否则直接返回null。处理String类型的TypeAdapter如下:

  public static final TypeAdapter STRING = new TypeAdapter() {

    @Override

    public String read(JsonReader in) throws IOException {

      JsonToken peek = in.peek();

      if (peek == JsonToken.NULL) {

        in.nextNull();

        return null;

      }

      /* coerce booleans to strings for backwards compatibility */

      if (peek == JsonToken.BOOLEAN) {

        return Boolean.toString(in.nextBoolean());

      }

      return in.nextString();

    }

    @Override

    public void write(JsonWriter out, String value) throws IOException {

      out.value(value);

    }

  };

重写了TypeAdapter的read和write方法,分别用于Deserialize和Serialize,一个简单的流式处理,不需要多说什么。

      for (TypeAdapterFactory factory : factories) {

        TypeAdapter candidate = factory.create(this, type);

        if (candidate != null) {

          call.setDelegate(candidate);

          typeTokenCache.put(type, candidate);

          return candidate;

        }

      }

如果factory.create(this, type)结果为空,则会继续循环,直到找到合适的TypeAdapter为止。接着之前说到的,当我们传入的类型是我们自定义的类型时,Gson会用哪一个TypeAdapter来进行处理呢?答案是ReflectiveTypeAdapterFactory,源码比较复杂,但是并不难懂,这里挑一些重点的地方稍作解释,从create方法讲起:

@Override public TypeAdapter create(Gson gson, final TypeToken type) {

    Class raw = type.getRawType();

    if (!Object.class.isAssignableFrom(raw)) {

      return null; // it's a primitive!

    }

    ObjectConstructor constructor = constructorConstructor.get(type);

    return new Adapter(constructor, getBoundFields(gson, type, raw));

  }

可以看到,在这个Factory的create方法中,并没有对类型做那么严格的限制,仅仅要求传入的类型是Object的子类即可,如果这一点都无法满足,那自然就没办法序列化/反序列化了,接下来的constructorConstructor.get(type)和

getBoundFields(gson, type, raw)就很复杂了,源码很多可以自己先看看,我简单地说一下,这两个方法一直跟下去,我们会发现这其实是跟反射机制相关的,constructorConstructor.get(type)返回的可以用来实例化我们需要处理的类型,而getBoundFields(gson, type, raw)则是提取这个类中的各个字段。举个例子,我们定义一个类:

public class LoginResp {

    protected final static String type = "loginInfo";

    private String userId;

    private List priv;

    private String username;

    private int group;

    // 省略getter和setter

    ...

}

可以简单地理解为constructorConstructor.get(type)将获取到一个ObjectConstructor接口的实例,而这个接口中只有一个construct()方法,通过调用construct()方法即可通过反射机制创建出LoginResp类的对象;而getBoundFields(gson, type, raw)将会通过反射机制遍历LoginResp类中的所有字段,将字段名称、类型、是否可Deserialize/Serialize保存起来,在遍历的过程中也会为每个字段调用getAdapter方法来为每个字段获取合适的TypeAdapter,在遍历结束后,每个字段都已经有了它对应的TypeAdapter。

所以,对于没有手动注册自定义TypeAdapter的Gson使用方式来说,将会经历的过程如下:

1.Gson构造器,初始化factories;

2.fromJson/toJson,遍历factories最后调用到ReflectiveTypeAdapterFactory的create方法,获取到一个TypeAdapter的实例。在这期间,ReflectiveTypeAdapterFactory中又有这样的过程:

1)判断传入类型是否为Object的子类;

2)利用反射获取传入类型的无参构造器;

3)利用反射获取传入类型每个字段的信息,并为他们遍历factories寻找可以处理这些字段的预置TypeAdapter;

3.调用TypeAdapter的write/read方法开始Serialize/Deserialize,这期间每个字段也要经历各自的read/write。

这个过程中,遍历factories、反射机制的应用都会产生耗时,对于解析复杂庞大的类型的对象是,这样的解析方式实际上并不高效。其实Gson gson = new Gson()这样的创建方式太过初级了,效率也不尽如人意,更推荐的方式是使用GsonBuilder。

GsonBuilder,自定义我们的工厂

再理一下上文的内容,Gson解析的过程可以概括为:

TypeAdapter转换为TypeAdapterFactory存入factories =>从factories中取出TypeAdapterFactory并创建TypeAdapter实例 => 调用TypeAdapter的write/read方法进行Serialize/Deserialize操作

但是上面介绍的仅仅是使用Gson预置的TypeAdapter,我也说了这样的方式并不那么高效。幸好,Gson还提供了途径,让我们定制自己的TypeAdapterFactory,加入factories中。

这里先提出一个很现实的应用场景:

app在与服务端使用HTTP请求进行交互时,我们时常会要求服务端统一返回Json字符串的格式以便客户端进行统一的封装,往往会是这样的形式:

{"status": 0, "message": "请求成功", "value": {...}}

在客户端收到这样一条消息时对其进行统一的Json解析,在请求处回调获取解析好的对象。

我曾经在一个项目中看到同事的封装是这样的,定义一个BaseResponse类:

public class BaseResponse {

    protected int status;

    protected String message;

    private String value;

}

先进行一次解析,至于value这个消息主体,由于每次传来的消息中的格式不同,于是同事先将它解析为String对象,然后再传入需要的类型再次对value单独进行解析……可是,我们真的非得做两次反序列化才能彻底解析完这条消息么???肯定有更好的方法!

整理一下思路:

1、我们要一次搞定Deserialize,分两次做实在太多余而且不优雅了;

2、我们要避免Gson在解析过程中频繁遍历factories、使用反射机制,以此来提高效率;

3、我们要灵活运用Gson的流式处理,那就自定义TypeAdapter吧,可是value的需要被解析成的类型各不相同,我们难道要手动为每种类型重写TypeAdapter?太low了又不实用,工厂模式白学了吧!

面对以上三个问题,Google爸爸在Gson中给出了一个demo类:RuntimeTypeAdapterFactory.java。https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java

我们可以自定义这样的一个工厂,来为我们动态地生产出我们需要的TypeAdapter,这时我们就要利用到Gson更高级的用法:

Gson gson = new GsonBuilder()

                .registerTypeAdapterFactory(xxxTypeAdapterFactory)

                .create();

在调用了registerTypeAdapterFactory方法后,将会把我们自定义的TypeAdapterFactory添加到factories中,并且在调用create创建Gson对象的过程中将factories逆序(这个创建方式是我将会在下篇文章讲到的建造者模式的基本使用),这样我们自定义的TypeAdapterFactory将会位于factories集合靠前的地方,保证不会轻易使用到ReflectiveTypeAdapterFactory。

使用方式其实很简单,Google在RuntimeTypeAdapterFactory中提供了详尽的使用方法,直接上代码吧,当然代码是我魔改过的,并且把使用说明翻译了一下,绝对一目了然:

package com.idste.diggson;

/*

* Copyright (C) 2011 Google Inc.

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

*      http://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*/

import java.io.IOException;

import java.util.LinkedHashMap;

import java.util.Map;

import com.google.gson.Gson;

import com.google.gson.JsonElement;

import com.google.gson.JsonObject;

import com.google.gson.JsonParseException;

import com.google.gson.JsonPrimitive;

import com.google.gson.TypeAdapter;

import com.google.gson.TypeAdapterFactory;

import com.google.gson.internal.Streams;

import com.google.gson.reflect.TypeToken;

import com.google.gson.stream.JsonReader;

import com.google.gson.stream.JsonWriter;

/**

* 这个类用于适配运行时类型与声明的类型不一致的值。当一个字段的类型与Gson在反序列化时创建的字段

* 类型不一样时,使用这个类就很有必要了。举个例子,让我们来考虑下这种情况:

*

<pre style="box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; margin-top: 0px; margin-bottom: 20px; overflow: auto; word-wrap: normal; word-break: break-all; white-space: pre; padding: 10px; border: 1px solid rgb(217, 217, 217); border-radius: 0px; line-height: 20px; background-color: rgb(40, 44, 52); color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">   {@code</pre>

*  abstract class Shape {

*    int x;

*    int y;

*  }

*  class Circle extends Shape {

*    int radius;

*  }

*  class Rectangle extends Shape {

*    int width;

*    int height;

*  }

*  class Diamond extends Shape {

*    int width;

*    int height;

*  }

*  class Drawing {

*    Shape bottomShape;

*    Shape topShape;

*  }

* }

*

没有额外的类型信息的话,下面这段序列化的Json字符串就显得有些模棱两可。这里的bottomShape字段

* 到底是指Rectangle类型还是指Diamond类型呢?

<pre style="box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; margin-top: 0px; margin-bottom: 20px; overflow: auto; word-wrap: normal; word-break: break-all; white-space: pre; padding: 10px; border: 1px solid rgb(217, 217, 217); border-radius: 0px; line-height: 20px; background-color: rgb(40, 44, 52); color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">   {@code</pre>

*  {

*    "bottomShape": {

*      "width": 10,

*      "height": 5,

*      "x": 0,

*      "y": 0

*    },

*    "topShape": {

*      "radius": 2,

*      "x": 4,

*      "y": 1

*    }

*  }}

* 这个类通过为序列化的Json字段添加类型信息以及在Json字符串被反序列化时授予该信息来处理此

* 问题:

<pre style="box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; margin-top: 0px; margin-bottom: 20px; overflow: auto; word-wrap: normal; word-break: break-all; white-space: pre; padding: 10px; border: 1px solid rgb(217, 217, 217); border-radius: 0px; line-height: 20px; background-color: rgb(40, 44, 52); color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">   {@code</pre>

*  {

*    "bottomShape": {

*      "type": "Diamond",

*      "width": 10,

*      "height": 5,

*      "x": 0,

*      "y": 0

*    },

*    "topShape": {

*      "type": "Circle",

*      "radius": 2,

*      "x": 4,

*      "y": 1

*    }

*  }}

* 字段名称({@code "type"})和字段值({@code "Rectangle"})都是可配置的。

*

*

### 注册类型

* 通过传入一个基本的类型和类型字段名到{@link #of}方法来创建一个{@code RuntimeTypeAdapterFactory}

* 如果你没有提供一个显式的类型字段名称,那么{@code "type"}将会被使用。

<pre style="box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; margin-top: 0px; margin-bottom: 20px; overflow: auto; word-wrap: normal; word-break: break-all; white-space: pre; padding: 10px; border: 1px solid rgb(217, 217, 217); border-radius: 0px; line-height: 20px; background-color: rgb(40, 44, 52); color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">   {@code</pre>

*  RuntimeTypeAdapterFactory shapeAdapterFactory

*      = RuntimeTypeAdapterFactory.of(Shape.class, "type");

* }

* 接下来注册所有的子类型。每一个子类型都必须显式注册,以此来保护应用免于被注入攻击。如果你没有显式的

* 定义类型的标签,那么类型的标签将会默认为这个类的名称。

*

<pre style="box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; margin-top: 0px; margin-bottom: 20px; overflow: auto; word-wrap: normal; word-break: break-all; white-space: pre; padding: 10px; border: 1px solid rgb(217, 217, 217); border-radius: 0px; line-height: 20px; background-color: rgb(40, 44, 52); color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">   {@code</pre>

*  shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");

*  shapeAdapter.registerSubtype(Circle.class, "Circle");

*  shapeAdapter.registerSubtype(Diamond.class, "Diamond");

* }

* 最后,在应用的GsonBuilder中注册TypeAdapterFactory。

*

<pre style="box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; margin-top: 0px; margin-bottom: 20px; overflow: auto; word-wrap: normal; word-break: break-all; white-space: pre; padding: 10px; border: 1px solid rgb(217, 217, 217); border-radius: 0px; line-height: 20px; background-color: rgb(40, 44, 52); color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">   {@code</pre>

*  Gson gson = new GsonBuilder()

*      .registerTypeAdapterFactory(shapeAdapterFactory)

*      .create();

* }

* 参照 {@code GsonBuilder}, 这个接口支持链式调用:

<pre style="box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; margin-top: 0px; margin-bottom: 20px; overflow: auto; word-wrap: normal; word-break: break-all; white-space: pre; padding: 10px; border: 1px solid rgb(217, 217, 217); border-radius: 0px; line-height: 20px; background-color: rgb(40, 44, 52); color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">   {@code</pre>

*  RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)

*      .registerSubtype(Rectangle.class)

*      .registerSubtype(Circle.class)

*      .registerSubtype(Diamond.class);

* }

*/

public final class HttpResponseTypeAdapterFactory implements TypeAdapterFactory {

    private final Class baseType;

    private final String typeFieldName;

    private final Map> labelToSubtype = new LinkedHashMap>();

    private final Map, String> subtypeToLabel = new LinkedHashMap, String>();

    private HttpResponseTypeAdapterFactory(Class baseType, String typeFieldName) {

        if (typeFieldName == null || baseType == null) {

            throw new NullPointerException();

        }

        this.baseType = baseType;

        this.typeFieldName = typeFieldName;

    }

    /**

    * Creates a new runtime type adapter using for {@code baseType} using {@code

    * typeFieldName} as the type field name. Type field names are case sensitive.

    */

    public static HttpResponseTypeAdapterFactory of(Class baseType, String typeFieldName) {

        return new HttpResponseTypeAdapterFactory(baseType, typeFieldName);

    }

    /**

    * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as

    * the type field name.

    */

    public static HttpResponseTypeAdapterFactory of(Class baseType) {

        return new HttpResponseTypeAdapterFactory(baseType, "type");

    }

    /**

    * Registers {@code type} identified by {@code label}. Labels are case

    * sensitive.

    *

    * @throws IllegalArgumentException if either {@code type} or {@code label}

    *                                  have already been registered on this type adapter.

    */

    public HttpResponseTypeAdapterFactory registerSubtype(Class type, String label) {

        if (type == null || label == null) {

            throw new NullPointerException();

        }

        if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {

            throw new IllegalArgumentException("types and labels must be unique");

        }

        labelToSubtype.put(label, type);

        subtypeToLabel.put(type, label);

        return this;

    }

    /**

    * Registers {@code type} identified by its {@link Class#getSimpleName simple

    * name}. Labels are case sensitive.

    *

    * @throws IllegalArgumentException if either {@code type} or its simple name

    *                                  have already been registered on this type adapter.

    */

    public HttpResponseTypeAdapterFactory registerSubtype(Class type) {

        return registerSubtype(type, type.getSimpleName());

    }

    public TypeAdapter create(Gson gson, TypeToken type) {

        if (null ==type || !baseType.isAssignableFrom(type.getRawType())) {

            return null;

        }

        final Map> labelToDelegate

                = new LinkedHashMap>();

        final Map, TypeAdapter> subtypeToDelegate

                = new LinkedHashMap, TypeAdapter>();

        for (Map.Entry> entry : labelToSubtype.entrySet()) {

            TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));

            labelToDelegate.put(entry.getKey(), delegate);

            subtypeToDelegate.put(entry.getValue(), delegate);

        }

        return new TypeAdapter() {

            @Override

            public R read(JsonReader in) throws IOException {

                JsonElement jsonElement = Streams.parse(in);

                JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);

                if (labelJsonElement == null) {

                    throw new JsonParseException("cannot deserialize " + baseType

                            + " because it does not define a field named " + typeFieldName);

                }

                String label = labelJsonElement.getAsString();

                @SuppressWarnings("unchecked") // registration requires that subtype extends T

                        TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label);

                if (delegate == null) {

                    throw new JsonParseException("cannot deserialize " + baseType + " subtype named "

                            + label + "; did you forget to register a subtype?");

                }

                return delegate.fromJsonTree(jsonElement);

            }

            @Override

            public void write(JsonWriter out, R value) throws IOException {

                Class srcType = value.getClass();

                String label = subtypeToLabel.get(srcType);

                @SuppressWarnings("unchecked") // registration requires that subtype extends T

                        TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType);

                if (delegate == null) {

                    throw new JsonParseException("cannot serialize " + srcType.getName()

                            + "; did you forget to register a subtype?");

                }

                JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();

                if (jsonObject.has(typeFieldName)) {

                    throw new JsonParseException("cannot serialize " + srcType.getName()

                            + " because it already defines a field named " + typeFieldName);

                }

                JsonObject clone = new JsonObject();

                clone.add(typeFieldName, new JsonPrimitive(label));

                for (Map.Entry e : jsonObject.entrySet()) {

                    clone.add(e.getKey(), e.getValue());

                }

                Streams.write(clone, out);

            }

        }.nullSafe();

    }

}

中文注释即是使用说明,这里贴上我的小Demo的其他代码:

首先是几个类:

value字段对应的ResponseValue类:

package com.idste.diggson;

/**

* Author iDste-Zhangzy

* Date 2018/6/22

* Description

*/

public class ResponseValue {

    protected final static String type = "ResponseValue";

}

用来解析整条消息的BaseResponse类:

package com.idste.diggson;

/**

* Author iDste-Zhangzy

* Date 2018/6/22

* Description

*/

public class BaseResponse {

    protected final static String type = "BaseResponse";

    protected int status;

    protected String message;

    private ResponseValue value;

}

value字段实际解析的类LoginResp和DeviceResp:

package com.idste.diggson;

import java.util.List;

/**

* Author iDste-Zhangzy

* Date 2018/6/22

* Description

*/

public class LoginResp extends ResponseValue {

    protected final static String type = "loginInfo";

    private String userId;

    private List priv;

    private String username;

    private int group;

    ... // 省略getter和setter

}
package com.idste.diggson;

/**

* Author iDste-Zhangzy

* Date 2018/6/22

* Description

*/

public class DeviceResp extends ResponseValue {

    protected final static String type = "deviceInfo";

    private String deviceId;

    private String deviceName;

    private String description;

... // 省略getter和setter

}

然后是layout_main.xml:

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context="com.idste.diggson.MainActivity"

    android:orientation="vertical">

        android:id="@+id/tv_convert_serialize_logininfo"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Serialize LoginInfo"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintLeft_toLeftOf="parent"

        app:layout_constraintRight_toRightOf="parent"

        app:layout_constraintTop_toTopOf="parent" />

        android:id="@+id/tv_convert_deserialize_logininfo"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Deserialize LoginInfo"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintLeft_toLeftOf="parent"

        app:layout_constraintRight_toRightOf="parent"

        app:layout_constraintTop_toTopOf="parent" />

        android:id="@+id/tv_convert_serialize_deviceinfo"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Serialize DeviceInfo"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintLeft_toLeftOf="parent"

        app:layout_constraintRight_toRightOf="parent"

        app:layout_constraintTop_toTopOf="parent" />

        android:id="@+id/tv_convert_deserialize_deviceinfo"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Deserialize DeviceInfo"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintLeft_toLeftOf="parent"

        app:layout_constraintRight_toRightOf="parent"

        app:layout_constraintTop_toTopOf="parent" />

最后是MainActivity.java:

package com.idste.diggson;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

import android.widget.TextView;

import com.google.gson.Gson;

import com.google.gson.GsonBuilder;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView mSerializeLoginInfoTv;

    private TextView mDeserializeLoginInfoTv;

    private TextView mSerializeDeviceInfoTv;

    private TextView mDeserializeDeviceInfoTv;

    private Gson gson;

    private String usrInfo = "{" +

            "status: 0" +

            ", message: \'请求成功\'" +

            ", value: {" +

            "type: \'loginInfo\'" +

            ", userId: \'no10232121\'" +

            ", priv: [\'system\', \'device\']" +

            ", username: \'Carlos Cahng\'" +

            ", group: 12" +

            "}" +

            "}";

    private String devInfo = "{" +

            "status: 0" +

            ", message: \'请求成功\'" +

            ", value: {" +

            "type: \'deviceInfo\'" +

            ", deviceId: \'983920130120\'" +

            ", deviceName: \'测试设备1\'" +

            ", description: \'教学楼A座西侧3楼走廊\'" +

            "}" +

            "}";

    private LoginResp loginResp;

    private DeviceResp deviceResp;

    private BaseResponse resp1;

    private BaseResponse resp2;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        mSerializeLoginInfoTv = findViewById(R.id.tv_convert_serialize_logininfo);

        mDeserializeLoginInfoTv = findViewById(R.id.tv_convert_deserialize_logininfo);

        mSerializeDeviceInfoTv = findViewById(R.id.tv_convert_serialize_deviceinfo);

        mDeserializeDeviceInfoTv = findViewById(R.id.tv_convert_deserialize_deviceinfo);

        mSerializeLoginInfoTv.setOnClickListener(this);

        mDeserializeLoginInfoTv.setOnClickListener(this);

        mSerializeDeviceInfoTv.setOnClickListener(this);

        mDeserializeDeviceInfoTv.setOnClickListener(this);

        HttpResponseTypeAdapterFactory baseAdapterFactory = HttpResponseTypeAdapterFactory.of(ResponseValue.class, "type")

                .registerSubtype(LoginResp.class, "loginInfo")

                .registerSubtype(DeviceResp.class, "deviceInfo");

        gson = new GsonBuilder()

                .registerTypeAdapterFactory(baseAdapterFactory)

                .create();

//        gson = new Gson();

    }

    @Override

    public void onClick(View v) {

        switch (v.getId()) {

            case R.id.tv_convert_serialize_logininfo:

                Log.d("loginResp", "Serialize: " + gson.toJson(resp1));

                break;

            case R.id.tv_convert_deserialize_logininfo:

                resp1 = gson.fromJson(usrInfo, BaseResponse.class);

                Log.d("loginResp", "Deserialize: " + resp1);

                break;

            case R.id.tv_convert_serialize_deviceinfo:

                Log.d("deviceResp", "Serialize: " + gson.toJson(resp2));

                break;

            case R.id.tv_convert_deserialize_deviceinfo:

                resp2 = gson.fromJson(devInfo, BaseResponse.class);

                Log.d("deviceResp", "Deserialize: " + resp2);

                break;

        }

    }

}

来看看运行结果:

image
image
image

小结

这是我第一次写这么长的文章,可能由于我这个人思维比较跳跃发散吧,明明说好重点是工厂模式,结果啰嗦了一大堆带大家过了遍Gson源码。其实我是觉得这个过程挺有必要的,Gson是个很好的开源库,说它好并不是仅仅说它好用,而是从代码的角度来看,它拥有很好的设计思想,我们可以从中学习到很多很好的编码习惯和思维方式,我觉得这一点对于一个程序员的自我提升来讲是最为重要的——可能很多朋友做过很多项目写过很多代码,但是却很少去学习设计思想,于是有一天觉得自己到达了职业瓶颈却又觉得编码仿佛就是这么简单的一回事儿。我想说的是,当我们有了很多项目经验的时候,接着提升自己的最佳方式就是学习设计思想,而提升设计思想的最好方式则是阅读优秀的开源项目。可能我组织语言的能力并不是那么好,我不太擅长跟别人讲述源码,所以这篇文章讲得或许也不那么详细,更多地是理了一遍Gson的处理流程,这些开源项目有一个特点就是代码很庞大又很绕,相信在理清了流程后再次阅读代码肯定不再是什么难事了。总而言之,工厂模式相关的东西在本文中相信已经是很详细的了,大家有兴趣也可以跑跑我这个小demo,说不定会为你的编码提供什么灵感呢。

最后,谢谢捧场!我这人很懒,最近又很忙,下一篇计划写写建造者模式,至于什么时候写出来,emmmmm……

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容

  • 1.概述2.Gson的目标3.Gson的性能和扩展性4.Gson的使用者5.如何使用Gson 通过Maven来使用...
    人失格阅读 14,074评论 2 18
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,024评论 8 265
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 我想触及你的高度,可我恨自己没用。曾经我诺言,一起走过傲娇时代,而你却越走越远,再也不回头。我,长出了双下巴、小...
    不愿再将就阅读 170评论 0 0
  • 项目业务需求用到Redis订阅发布,于是去百度订阅发布,搜索内容是springBoot 的Redis订阅发布 然而...
    AltF4_小寒阅读 8,050评论 4 6