资讯专栏INFORMATION COLUMN

追踪解析Gson源码(1)

sewerganger / 1541人阅读

摘要:版本版本简介是谷歌开源的解析工具。其实几款产品的差别都很细微,有谷歌的信仰加成,所以在这里进行一次源码分析。至于和会在后续进行追踪。

零 前期准备 0 FBI WARNING

文章异常啰嗦且绕弯。

1 版本

Gson 版本 : gson 2.8.5

IDE : idea 2018.3

2 Gson 简介

Gson 是谷歌开源的 java json 解析工具。市场上同类的开源产品还有 Fastjson、Jackson、json-lib等。

其实几款产品的差别都很细微,Gson 有谷歌的信仰加成,所以在这里进行一次源码分析。

3 Bean Demo
package ioc;

/**
 * java bean
 */
public class Person {

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
4 main方法
import com.google.gson.Gson;

public class JsonTest {

    public static void main(String[] args){
        //示例 json 字符串
        String json =
                "{" +
                "   "name": "zhangsan"," +
                "   "age": 11" +
                "}";

        //初始化解析器
        Gson gson = new Gson();

        //json to bean
        Person person = gson.fromJson(json,Person.class);
        System.out.println(person.getName());
        System.out.println(person.getAge());

        //bean to json
        String json2 = gson.toJson(person);
        System.out.println(json2);
    }
}
一 初始化 Gson 解析器 1 Gson 构造方法

该 part 的起点:

Gson gson = new Gson();

追踪 Gson 的无参构造器:

//Gson.class
public Gson() {
    this(Excluder.DEFAULT, FieldNamingPolicy.IDENTITY,
        Collections.>emptyMap(), DEFAULT_SERIALIZE_NULLS,
        DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML,
        DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES,
        LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT,
        Collections.emptyList(), Collections.emptyList(),
        Collections.emptyList());
}

继续追踪:

//Gson.class
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) {
    
    //排除器,在序列化对象的时候会根据使用者设置的规则排除一些数据
    //排除策略需要使用者自行实现 ExclusionStrategy 接口来制定
    this.excluder = excluder;

    //fieldNamingStrategy 负责命名规则的确定(比如 大驼峰命名、小驼峰命名、下划线命名 等)
    //选择不同的 fieldNamingStrategy 会在输出 json 字符串的时候把字段名称转成不同的命名形式
    //此处的值可以直接选择 FieldNamingPolicy 枚举类中的已经存储的策略,也可以自行实现 FieldNamingStrategy 接口
    //此处默认为 FieldNamingPolicy.IDENTITY,即不改变
    this.fieldNamingStrategy = fieldNamingStrategy;

    //instanceCreators 是一个用来存储实现了 InstanceCreator 接口的对象的 map
    //每一个 InstanceCreator 的实现类用来反射获取一种特定类型的 bean 对象
    //InstanceCreator 在 Gson 中没有实现类,使用者可以自行定制
    //此处为空 map
    this.instanceCreators = instanceCreators;

    //ConstructorConstructor 用来统一调度 instanceCreators
    this.constructorConstructor = new ConstructorConstructor(instanceCreators);

    //serializeNulls 是一个 boolean 类型的对象,用以表示是否支持空对象的序列化
    //此处传入的是 DEFAULT_SERIALIZE_NULLS,值为 false,是一个定义在 Gson 中的常量
    this.serializeNulls = serializeNulls;

    //将 Map 序列化的过程中,会存在一个问题,即 Map 的 key 值是一个复杂对象(java bean 等)
    //如果 complexMapKeySerialization 设置为 false,则直接调用对象的 toString() 方法获取字符串
    //设置为 true 的情况下会去尝试解析此对象,一般情况下要配合特定的 TypeAdapter 使用
    //默认为 false
    this.complexMapKeySerialization = complexMapKeySerialization;

    //是否要生成不可执行的 json
    //默认为 false
    this.generateNonExecutableJson = generateNonExecutableGson;

    //是否对 html 进行编码,即对部分符号进行转义(=、<、> 等)
    //默认为 true
    this.htmlSafe = htmlSafe;

    //在输出的时候格式化 json
    //默认为 false
    this.prettyPrinting = prettyPrinting;

    //设置 json 的自定义标准
    //默认 false ,即为 json 标准的数据格式
    this.lenient = lenient;

    //用于支持 float 类型的特殊值,比如 Infinity(无穷大) 或 -Infinity(负无穷大) 等
    //默认为 false
    this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues;

    //设置对 long 类型的变量,是解析成字符串还是解析为 long 类型
    //默认为解析成 long 类型
    this.longSerializationPolicy = longSerializationPolicy;

    //以下三行用于设置日期格式和时间格式
    //datePattern 是日期和时间的字符串格式表达,在此处为 null
    //dateStyle 与 timeStyle 为日期和时间格式的编码
    //以 int 常量形式存放在 java.text.DateFormat 中
    //此处均为默认值
    //需要注意的是默认情况下 Gson 的日期解析不太符合国人的习惯
    this.datePattern = datePattern;
    this.dateStyle = dateStyle;
    this.timeStyle = timeStyle;

    //此处为空
    this.builderFactories = builderFactories;

    //此处为空
    this.builderHierarchyFactories = builderHierarchyFactories;

    //TypeAdapter 是一个接口,用于序列化和反序列化某种特定的类型
    //TypeAdapterFactory 是 TypeAdapter 的包装类
    List factories = new ArrayList();

    //TypeAdapters 是 TypeAdapter 和 TypeAdapterFactory 的通用工具类
    //处理 JsonElement 类型对象的 TypeAdapterFactory
    //JsonElement 是 Gson 工具包中的一个类
    factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);

    //处理 Object 类型对象的 TypeAdapterFactory
    factories.add(ObjectTypeAdapter.FACTORY);

    //excluder 是一个省略了类型的 TypeAdapterFactory
    //根据官方注释,excluder 需要先于所有使用者自定义的 TypeAdapterFactory 去执行
    factories.add(excluder);

    //使用者自定义的 TypeAdapterFactory
    factories.addAll(factoriesToBeAdded);

    //处理 String 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.STRING_FACTORY);

    //处理 Integer / int 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.INTEGER_FACTORY);

    //处理 Boolean / boolean 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.BOOLEAN_FACTORY);

    //处理 Byte / byte 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.BYTE_FACTORY);

    //处理 Short / short 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.SHORT_FACTORY);

    //处理 Long / long 类型对象的 TypeAdapterFactory
    TypeAdapter longAdapter = longAdapter(longSerializationPolicy);
    factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter));

    //处理 Double / double 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.newFactory(double.class, Double.class,
            doubleAdapter(serializeSpecialFloatingPointValues)));

    //处理 Float / float 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.newFactory(float.class, Float.class,
            floatAdapter(serializeSpecialFloatingPointValues)));

    //处理 Number 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.NUMBER_FACTORY);

    //处理 AtomicInteger 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY);

    //处理 AtomicBoolean 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY);

    //处理 AtomicBoolean 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter)));

    //处理 AtomicLongArray 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.newFactory(AtomicLongArray.class, atomicLongArrayAdapter(longAdapter)));

    //处理 AtomicIntegerArray 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.ATOMIC_INTEGER_ARRAY_FACTORY);

    //处理 Character / char 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.CHARACTER_FACTORY);

    //处理 StringBuilder 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.STRING_BUILDER_FACTORY);

    //处理 StringBuffer 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.STRING_BUFFER_FACTORY);

    //处理 BigDecimal 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));

    //处理 BigInteger 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));

    //处理 URL 类型对象的 TypeAdapterFactory
    //java.net.URL
    factories.add(TypeAdapters.URL_FACTORY);

    //处理 URI 类型对象的 TypeAdapterFactory
    //java.net.URI
    factories.add(TypeAdapters.URI_FACTORY);

    //处理 UUID 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.UUID_FACTORY);

    //处理 Currency 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.CURRENCY_FACTORY);

    //处理 Locale 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.LOCALE_FACTORY);

    //处理 InetAddress 类型对象的 TypeAdapterFactory
    //java.net.InetAddress
    factories.add(TypeAdapters.INET_ADDRESS_FACTORY);

    //处理 BitSet 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.BIT_SET_FACTORY);

    //处理 Date 类型对象的 TypeAdapterFactory
    //java.util.Date
    factories.add(DateTypeAdapter.FACTORY);

    //处理 Calendar 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.CALENDAR_FACTORY);

    //处理 Time 类型对象的 TypeAdapterFactory
    factories.add(TimeTypeAdapter.FACTORY);

    //处理 Date 类型对象的 TypeAdapterFactory
    //java.sql.Date
    factories.add(SqlDateTypeAdapter.FACTORY);

    //处理 Timestamp 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.TIMESTAMP_FACTORY);

    //处理 Array 类型对象的 TypeAdapterFactory
    factories.add(ArrayTypeAdapter.FACTORY);

    //处理 Class 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.CLASS_FACTORY);

    //处理 Collection 类型对象的 TypeAdapterFactory
    factories.add(new CollectionTypeAdapterFactory(constructorConstructor));

    //处理 Map 类型对象的 TypeAdapterFactory
    //会受到 complexMapKeySerialization 的影响
    factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));

    //处理 JsonAdapter 类型对象的 TypeAdapterFactory
    //JsonAdapter 是一个 Gson 中的注解
    this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
    factories.add(jsonAdapterFactory);

    //处理 Enum 类型对象的 TypeAdapterFactory
    factories.add(TypeAdapters.ENUM_FACTORY);

    //反射分解对象的 TypeAdapterFactory
    factories.add(new ReflectiveTypeAdapterFactory(
        constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));

    this.factories = Collections.unmodifiableList(factories);
}

从这里可以看出,Gson 的初始化时期提供了非常丰富的设置选项。

2 GsonBuilder

设置 Gson 的这些选项需要通过 GsonBuilder :

Gson gson = new GsonBuilder()

    //以下方法均为设置 excluder
    //设置版本号
    .setVersion(1)
    //设置忽略某种修饰词修饰的变量
    //此处忽略 protected 修饰的变量
    .excludeFieldsWithModifiers(Modifier.PROTECTED)
    //设置使用 Expose 注解,用于忽略某个字段
    //默认情况下是不使用 Expose 注解的
    .excludeFieldsWithoutExposeAnnotation()
    //设置不序列化内部类
    .disableInnerClassSerialization()
    //批量添加序列化时使用的排除策略
    //此方法为不定参方法
    .setExclusionStrategies(exclusionStrategy)
    //添加一个序列化时使用的排除策略
    .addSerializationExclusionStrategy(exclusionStrategy)
    //添加一个反序列化时使用的排除策略
    .addDeserializationExclusionStrategy(exclusionStrategy)

    //本质上以下三个方法均为设置 TypeAdapter
    .registerTypeAdapter(String.class, TypeAdapters.STRING)
    .registerTypeAdapterFactory(TypeAdapters.STRING_FACTORY)
    .registerTypeHierarchyAdapter(String.class, TypeAdapters.STRING)

    //设置 dateStyle、datePattern、timeStyle
    .setDateFormat("yyyy-MM-dd HH:mm:ss")
    .setDateFormat(DateFormat.DATE_FIELD)
    .setDateFormat(DateFormat.DATE_FIELD,DateFormat.AM_PM_FIELD)

    //以下两个方法本质上是一样的,均为设置 fieldNamingPolicy 属性
    .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
    .setFieldNamingStrategy(FieldNamingPolicy.IDENTITY)

    //设置 complexMapKeySerialization = true
    .enableComplexMapKeySerialization()

    //设置 longSerializationPolicy = LongSerializationPolicy.STRING
    //即 long 类型的数据在序列化的时候会转成 String
    .setLongSerializationPolicy(LongSerializationPolicy.STRING)

    //设置 serializeNulls = true
    .serializeNulls()

    //设置 prettyPrinting = true
    .setPrettyPrinting()

    //设置 generateNonExecutableJson = true
    .generateNonExecutableJson()

    //设置 lenient = true
    .setLenient()

    //设置 escapeHtmlChars = false
    .disableHtmlEscaping()

    //设置 serializeSpecialFloatingPointValues = true
    .serializeSpecialFloatingPointValues()

    //创建解析器对象
    .create();
3 Excluder

在上述设置中,有一大块是关于排除器 excluder 的设置。excluder 的实现逻辑依赖 ExclusionStrategy 的自定义实现类,追踪一下 ExclusionStrategy 接口:

//实现 
public interface ExclusionStrategy {

    //设置忽略的变量,需要传入 FieldAttributes
    //FieldAttributes 是在 Gson 中定义的 Field 的包装类
    public boolean shouldSkipField(FieldAttributes f);

    //设置要忽略的 class
    public boolean shouldSkipClass(Class clazz);
}

fieldNamingPolicy 则需要 FieldNamingStrategy 的自定义实现类,追踪一下 FieldNamingStrategy 接口:

public interface FieldNamingStrategy {

  //在这个方法中实现自定义的命名规则
  public String translateName(Field f);
}

FieldNamingPolicy 是一个实现了 FieldNamingStrategy 接口的枚举类,其中实现了多套 translateName(...) 方法可供选择。

二 TypeAdapter 和 TypeAdapterFactory

在 Gson 中封装了不同类型的读写的业务组装类是各个 TypeAdapter(适配器),这里先来关注一下其具体实现。

1 父类与接口

先来看一下 TypeAdapterFactory 接口:

public interface TypeAdapterFactory {

  //只有一个方法,用于根据解析器和变量类型来创建 TypeAdapter
   TypeAdapter create(Gson gson, TypeToken type);
}

再来看一下 TypeAdapter 抽象类:

public abstract class TypeAdapter {

    //写入方法,主要的指挥 JsonWriter 进行业务处理
    public abstract void write(JsonWriter out, T value) throws IOException;

    //读取方法,主要是指挥 JsonReader 进行业务操作
    public abstract T read(JsonReader in) throws IOException;

    //该抽象类中还提供了其它的方法,在此例中没有用到,暂时忽略
}
2 StringTypeAdapter

关注一下下列这行代码:

factories.add(TypeAdapters.STRING_FACTORY);

factories 是一个列表,来追踪一下 TypeAdapters.STRING_FACTORY :

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

先来看一下定义在 TypeAdapters 中的 STRING 变量:

//TypeAdapters.class
public static final TypeAdapter STRING = new TypeAdapter() {

    //此方法用于反序列化时读取值
    @Override
    public String read(JsonReader in) throws IOException {

        //in.peek() 方法会获取到最新的 JsonReader 操作指令,并转换成下一个要操作的字符类型
        //在后面的 part 里还会提到
        JsonToken peek = in.peek();

        //读取到 null
        if (peek == JsonToken.NULL) { 
            in.nextNull();
            return null;
        }

        //读取到 boolean 类型的值
        if (peek == JsonToken.BOOLEAN) { 
            return Boolean.toString(in.nextBoolean());
        }

        //除了 null 和 boolean 之外,都视作 String 进行返回
        return in.nextString(); 
    }

    //此方法用于序列化时写入值
    @Override
    public void write(JsonWriter out, String value) throws IOException {
        out.value(value);
    }
};

继续追踪 newFactory(...) 方法:

//TypeAdapters.class
public static  TypeAdapterFactory newFactory(final Class type, final TypeAdapter typeAdapter) {

    return new TypeAdapterFactory() {

      @SuppressWarnings("unchecked") 
      @Override
      public  TypeAdapter create(Gson gson, TypeToken typeToken) {
          //typeToken.getRawType() 会获取到这个 TypeToken 所需要的
        return typeToken.getRawType() == type ? (TypeAdapter) typeAdapter : null;
      }

      //常规的重新 toString() 方法
      @Override
      public String toString() {
        return "Factory[type=" + type.getName() + ",adapter=" + typeAdapter + "]";
      }
    };
}

从这里可以看出,TypeAdapterFactory 本质上只是 TypeAdapter 的包装类,只是做一个类型的判定工作,如果判定为相同,就会返回传入的 TypeAdapter。

一般的类型的 TypeAdpter 的操作逻辑都比较类似,不赘述了。

2 ReflectiveTypeAdapter

对于一般的自定义类,比如使用者自定义的 java bean 等,并非 jdk 自带的基本数据类型,就都需要 ReflectiveTypeAdapter 来进行解析。

先来看一下 ReflectiveTypeAdapterFactory 的 create() 方法:

//ReflectiveTypeAdapterFactory.class
public  TypeAdapter create(Gson gson, final TypeToken type) {
    Class raw = type.getRawType();

    //要确保 type 中获取出来的 class 的类型是 Object 的子类
    //如果不是的话就代表这是基本类型,基本类型的解析不应该使用该适配器
    //所以返回 null
    if (!Object.class.isAssignableFrom(raw)) {
      return null;
    }
    
    //constructor 用于反射创建对象
    ObjectConstructor constructor = constructorConstructor.get(type);

    //Adapter 是 ReflectiveTypeAdapterFactory 的静态内部类,继承了 TypeAdapter
    return new Adapter(constructor, getBoundFields(gson, type, raw));
}

来继续追踪一下 Adapter 的主要方法:

public static final class Adapter extends TypeAdapter {

    //此处 read(...) 和 write(...) 的代码比较类似
    //主要步骤是通过反射创建出对象,并抓取其所有的变量,逐个存入

    public T read(JsonReader in) throws IOException {

        if (in.peek() == JsonToken.NULL) {
            in.nextNull();
            return null;
        }
        //调用构造器反射创建出对象
        T instance = constructor.construct();
        //以下代码是 Gson 读出字符串中的部分,并用反射填入到对象中的过程
        try {
            in.beginObject();
            while (in.hasNext()) {
                String name = in.nextName();
                //BoundField 是 Gson 对 jdk 中的 Field 类的增强
                BoundField field = boundFields.get(name);
                if (field == null || !field.deserialized) {
                    in.skipValue();
                } else {
                    field.read(in, instance);
                }
            }
        } catch (IllegalStateException e) {
            throw new JsonSyntaxException(e);
        } catch (IllegalAccessException e) {
            throw new AssertionError(e);
        }
        in.endObject();
        return instance;
    }

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

        if (value == null) {
            out.nullValue();
            return;
        }

        out.beginObject();
        //以下是 Gson 从对象中获取到数据并写成字符串的过程
        try {
            for (BoundField boundField : boundFields.values()) {
                if (boundField.writeField(value)) {
                    out.name(boundField.name);
                    boundField.write(out, value);
                }
            }
        } catch (IllegalAccessException e) {
            throw new AssertionError(e);
        }
        out.endObject();
    }
}

具体的步骤其实是对 java 反射的深度定制化应用,不展开了。

以上过程在 JsonReader 和 JsonWriter 的应用中会有类似展开。至于 JsonReader 和 JsonWriter 会在后续进行追踪。

To Be Continued ...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72824.html

相关文章

  • 追踪解析Gson源码(2)

    摘要:接上篇三和在进行的序列化和反序列化源码解析之前先了解一下其主体工具类。是中用于序列化的主体。同时为了兼顾性能做了很多有意思的设计,比如获取适配器的时候的双缓存设计,应该是为了提高解析器的复用效率,具体有待研究。 接上篇 三 JsonReader 和 JsonWriter 在进行 json 的序列化和反序列化源码解析之前先了解一下其主体工具类。 1 JsonReader JsonRead...

    shiguibiao 评论0 收藏0
  • 追踪解析 ThreadPoolExecutor 源码

    摘要:的前位数用来表示线程的数量,后面三位用来表示线程池的状态。线程池的状态有五种,分别是,根据单词就能猜出大概。并且为了考虑性能问题,线程池的设计没有使用悲观锁关键字,而是大量使用了和机制。 零 前期准备 0 FBI WARNING 文章异常啰嗦且绕弯。 1 版本 JDK 版本 : OpenJDK 11.0.1 IDE : idea 2018.3 2 ThreadPoolExecutor ...

    gaomysion 评论0 收藏0
  • Retrofit源码分析

    摘要:看下图所示,摘自网络的创建流程源码分析实例是使用建造者模式通过类进行创建的。创建了一个含有对象实例的,并返回给源码分析添加一个调用适配器工厂,用于支持服务方法返回类型注意生产的是,那么又是什么呢可以看到源代码如下所示,它是一个接口。 目录介绍 1.首先回顾Retrofit简单使用方法 2.Retrofit的创建流程源码分析 2.1 Retrofit对象调用Builder()源码解...

    zero 评论0 收藏0
  • gson-plugin深入源码分析(三)

    摘要:六修改内部类的方法这个的方法是对类型的数据进行解析,我们判断输入的数据类型不是类型,就直接跳过解析,核心是在方法中插入方法。每一个类每一个内部类每一个匿名内部类,都会生成一个独立的文件,如。 一、项目地址 项目地址:github-gson-plugin 二、ReaderTools解析 /** * Created by tangfuling on 2018/10/23. */ pu...

    oujie 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<