Gson User Guide (译)

原文:https://sites.google.com/site/gson/gson-user-guide

Overview

Gson是一个Java库,用来将Java Objects转换为JSON的表示方法,也可以用来将一个JSON字符串转换为相等的JAVA object。GSON是一个开源代码,其代码主页为http://code.google.com/p/google-gson.

即使已经存在的对象,我们没有源代码的情况下,Gson依然可以工作。

Goals for Gson

  • 提供一种比较易用的,像toString()或者构造函数那样的机制来转换Java和JSON。
  • 允许已经存在的不能修改的对象也可以和JSON之间相互转换。
  • 允许自定义对象的表示方法。
  • 允许任意复杂的对象。
  • 生成紧凑简洁而又可读性好的JSON输出。

Gson Performance and Scalability

 

这里有一些桌面环境(dual opteron, 8GB RAM, 64-bit Ubuntu)获得的指标。我们可以使用类PerformanceTest来重新测试。

  • Strings: Deserialized strings of over 25MB without any problems (seedisabled_testStringDeserializationPerformancemethod in PerformanceTest)
  • Large collections:
    • Serialized a collection of 1.4 million objects (seedisabled_testLargeCollectionSerializationmethod in PerformanceTest)
    • Deserialized a collection of 87,000 objects (seedisabled_testLargeCollectionDeserializationin PerformanceTest)
  • Gson 1.4 raised the deserialization limit for byte arrays and collection to over 11MB from 80KB.

Note: Delete the disabled_ prefix to run these tests. We use this prefix to prevent running these tests every time we run junit tests.

Gson Users

Gson was originally created for use inside Google where it is currently used in a number of projects. It is now used by a number of public projects and companies. See details here.

Using Gson

最直接的使用方法就是直接通过new Gson()来构建一个Gson。同时也可以通过GsonBuilder来创建一个Gson实例,其中包括各种设置比如version control或者其他的。

Gson实例在调用Json的操作的时候,并不会保存任何的状态,所以我们可以自由的复用同一个对象来实现多种Json的序列化和反序列化操作。

Primitives Examples

 

(Serialization)
Gson gson = new Gson();
gson.toJson(1);            ==> prints 1
gson.toJson("abcd");       ==> prints "abcd"
gson.toJson(new Long(10)); ==> prints 10
int[] values = { 1 };
gson.toJson(values);       ==> prints [1]

(Deserialization)
int one = gson.fromJson("1", int.class);
Integer one = gson.fromJson("1", Integer.class);
Long one = gson.fromJson("1", Long.class);
Boolean false = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String anotherStr = gson.fromJson("[\"abc\"]", String.class);

Object Examples

class BagOfPrimitives {
  private int value1 = 1;
  private String value2 = "abc";
  private transient int value3 = 3;
  BagOfPrimitives() {
    // no-args constructor
  }
}

(Serialization)
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);  
==> json is {"value1":1,"value2":"abc"}

注意:我们不能序列化那种环形引用的对象,因为那样会导致无限的递归。

(Deserialization)
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);   
==> obj2 is just like obj

Finer Points with Objects

  • 使用private修饰的成员变量是允许的并且是推荐的。
  • 并不需要任何的注释来标示一个成员变量是否需要序列化或者反序列化。默认情况下,当前类中的所有的成员变量,包括从父类中继承过来的成员变量都会默认包含。
  • 如果一个成员变量使用transient来修饰的话,那么这个类默认情况下是被JSON序列化和反序列化所忽略的。
  • Gson的实现能够正确的处理nulls
    • 当序列化的时候,null值会被跳过,而不会输出。
    • 当反序列化的时候,JSON结果中确实的entry会被置为null
  • 如果一个成员变量是synthetic,那么在JSON的序列化或者反序列化中是忽略的。(这个是神马?
  • 成员变量对应着外部类中的内部类,匿名类或者局部类的时候,是被忽略的,并不会包含在序列化和反序列化之中。

Nested Classes (including Inner Classes)

Gson可以很轻松的序列化静态嵌套类。

Gson也可以反序列化静态嵌套类。然而,Gson不能自动反序列化纯粹的内部类,这是因为即使他们的无参构造函数也需要一个引用指向外部类,而这个外部类在这个时候不是可用的。我们可以通过将这个内部类设置为静态的或者通过自己提供一个InstanceCreator来解决。这里有一个例子。

public class A { 
  public String a; 

  class B { 

    public String b; 

    public B() {
      // No args constructor for B
    }
  } 
}

可以注意到,上面的类B不能在默认情况下被Gson实例化的。

Gson不能反序列化{“b”:”abc”}为一个B的实例,是因为B是一个内部类。如果这是一个静态类,那么是可以的,另外一种解决办法是为B写一个自定义的instance creator。

public class InstanceCreatorForB implements InstanceCreator<A.B> {
  private final A a;
  public InstanceCreatorForB(A a)  {
    this.a = a;
  }
  public A.B createInstance(Type type) {
    return a.new B();
  }
}

上面的方法是可行的,但是不推荐使用。

Array Examples

Gson gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};

(Serialization)
gson.toJson(ints);     ==> prints [1,2,3,4,5]
gson.toJson(strings);  ==> prints ["abc", "def", "ghi"]

(Deserialization)
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class); 
==> ints2 will be same as ints

Gson也支持多维数组,数组元素可以是任何复杂类型。

Collections Examples

Gson gson = new Gson();
Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);

(Serialization)
String json = gson.toJson(ints); ==> json is [1,2,3,4,5]

(Deserialization)
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
ints2 is same as ints

需要注意到是,我们定义了Collection的类型,这在Java中是必须的。

Collections Limitations

  • 可以序列化任意复杂的对象,但是无法从其中反序列化。
    • 这是因为用户没有办法来识别序列化的结果中的类型type
  • 当反序列化的时候,集合必须是一个指定的泛型类型。

Serializing and Deserializing Generic Types

 

我们调用toJson(obj)方法的时候,Gson会调用obj.getClass()来获取要序列化的成员变量的信息。同样的我们也可以通过传递MyClass.class对象给fromJson(json, MyClass.class)方法。如果该对象是一个非泛型类的话,这样做是很有效的。但是,如果这个类是一个泛型类,那么使用这种方法泛型的信息会丢失(这是因为Java泛型的擦除机制)。这里有一个例子来说明这一点。

class Foo<T> {
  T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // May not serialize foo.value correctly

gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar

上面的代码中,无法将数值解析为Bar的类型,这是因为Gson调用list.getClass()来获取类的信息,但是这个方法却返回一个类的信息Foo.class。这意味着Gson没有办法知道Foo<Bar>的类型信息,而不仅仅是Foo。

为了解决这个问题,我们可以通过为泛型指定准确的参数化类型来实现,通过使用TypeToker类型来实现。

Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);

gson.fromJson(json, fooType);

这种机制来获取fooType,实际上是定义了一个匿名的嵌套类,其中包括getType()方法,可以返回完整的参数化类型。

Serializing and Deserializing Collection with Objects of Arbitrary Types

有个时候,我们的JSON数组可能包含复杂的混合类型。比如:

['hello',5,{name:'GREETINGS',source:'guest'}]

这等同于包含以下的集合:

Collection collection = new ArrayList();
collection.add("hello");
collection.add(5);
collection.add(new Event("GREETINGS", "guest"));

此处Event的定义为:

class Event {
  private String name;
  private String source;
  private Event(String name, String source) {
    this.name = name;
    this.source = source;
  }
}

我们可以在不需要做任何修改的情况下实现,只要使用toJson(collection)方法即可。

但是当我们需要反序列化的时候,fromJson(json, Collection.class)却无法工作,因为Gson没有办法知道如何map输入的类型。Gson要求我们为fromJson提供一个制定参数的集合类型。所有我们有三种办法:

  • Option 1:使用Gson的parser API(低等级的straming parser流解析器,或者DOM parser JsonParser)来解析数组元素,然后使用Gson.fromJson()来解析每一个数组元素。这是一种推荐方法,这里有一个例子。
/*
 * 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.
 */
package com.google.gson.extras.examples.rawcollections;

import java.util.ArrayList;
import java.util.Collection;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;

public class RawCollectionsExample {
  static class Event {
    private String name;
    private String source;
    private Event(String name, String source) {
      this.name = name;
      this.source = source;
    }
    @Override
    public String toString() {
      return String.format("(name=%s, source=%s)", name, source);
    }
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  public static void main(String[] args) {
    Gson gson = new Gson();
    Collection collection = new ArrayList();
    collection.add("hello");
    collection.add(5);
    collection.add(new Event("GREETINGS", "guest"));
    String json = gson.toJson(collection);
    System.out.println("Using Gson.toJson() on a raw collection: " + json);
    JsonParser parser = new JsonParser();
    JsonArray array = parser.parse(json).getAsJsonArray();
    String message = gson.fromJson(array.get(0), String.class);
    int number = gson.fromJson(array.get(1), int.class);
    Event event = gson.fromJson(array.get(2), Event.class);
    System.out.printf("Using Gson.fromJson() to get: %s, %d, %s", message, number, event);
  }
}
  • Option 2:对Collection.class来注册一个类型的adapter。adapter能够注意到数组中的每一个元素并且能够完美的将其映射到合适的对象上去。这种的办法的缺点在于这种方法会弄糟其他的集合的类型的反序列化。
  • Option 3:为MyCollectionMemberType注册一个类型的adapter,并且使用fromJson的Collection<MyCollectionMemberType>签名方法。这种方法仅仅在数组作为顶层元素,并且我们能够将保存数组的类型更改为Collection<MyCollectionMemberType>的时候有效。

Built-in Serializers and Deserializers

Gson has built-in serializers and deserializers for commonly used classes whose default representation may be inappropriate.
Here is a list of such classes:

  1. java.net.URL to match it with strings like “http://code.google.com/p/google-gson/”.
  2. java.net.URI to match it with strings like “/p/google-gson/”.

You can also find source-code for some commonly used classes such as JodaTime at this page.

DateTime

  private static class DateTimeTypeConverter
      implements JsonSerializer<DateTime>, JsonDeserializer<DateTime> {
    @Override
    public JsonElement serialize(DateTime src, Type srcType, JsonSerializationContext context) {
      return new JsonPrimitive(src.toString());
    }

    @Override
    public DateTime deserialize(JsonElement json, Type type, JsonDeserializationContext context)
        throws JsonParseException {
      try {
        return new DateTime(json.getAsString());
      } catch (IllegalArgumentException e) {
        // May be it came in formatted as a java.util.Date, so try that
        Date date = context.deserialize(json, Date.class);
        return new DateTime(date);
      }
    }
  }
Instant

  private static class InstantTypeConverter
      implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
    @Override
    public JsonElement serialize(Instant src, Type srcType, JsonSerializationContext context) {
      return new JsonPrimitive(src.getMillis());
    }

    @Override
    public Instant deserialize(JsonElement json, Type type, JsonDeserializationContext context)
    throws JsonParseException {
      return new Instant(json.getAsLong());
    }
  }

Custom Serialization and Deserialization

 

有些时候,默认的实现并不是我们所需要的效果。这在处理一些库的类的时候经常碰到,比如(DateTime等等)。Gson允许我们注册我们自己的序列化和反序列化的实现。主要分为两部分来完成:

  • Json Serialiers:需要为类定义一个自定义的序列化实现。
  • Json Deserializers:需要为类定义一个自定义的反序列化实现。
  • Instance Creators:如果存在无餐构造函数的话,这个并不是必须的。
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(MyType2.class, new MyTypeAdapter());
gson.registerTypeAdapter(MyType.class, new MySerializer());
gson.registerTypeAdapter(MyType.class, new MyDeserializer());
gson.registerTypeAdapter(MyType.class, new MyInstanceCreator());

registerTypeAdapter会检查adapter实现的一个或者多个接口并且为其全部注册。

Writing a Serializer

private class DateTimeSerializer implements JsonSerializer<DateTime> {
  public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) {
    return new JsonPrimitive(src.toString());
  }
}

当调用toJson()的时候对调用。

Writing a Deserializer

private class DateTimeDeserializer implements JsonDeserializer<DateTime> {
  public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException {
    return new DateTime(json.getAsJsonPrimitive().getAsString());
  }
}

Finer points with Serializers and Deserializers 可以优化的点

经常情况下,我们会想要注册一个单独的handler来处理raw类型所对应的所有的泛型

  • 比如,假设我们有一个“Id”的类来标示Id和用来传输。
  • Id<T>类来说,对于所有的泛型都有着同样的序列化。
    • 需要写出id的数值。
  • 反序列化也很相近,但并不完全一样。
    • 需要调用new Id(Class<T>, String)来返回一个Id<T>的实例。

Gson支持注册一个单独的handler来处理这些情况。我们也可以为每一个泛型都注册一个handler(比如Id<RequiresSepcialHandling>需要单独的处理)。Type参数对于toJson和fromJson中便于我们对于同样的raw类型来处理所有的泛型类型。

Writing an Instance Creator

 

当反序列化一个对象的时候,Gson需要创建一个该类的默认的实例。一个书写良好的用于序列化和反序列化的类应该含有无参构造函数

  • 并不需要关心这个无参构造函数是public还是private的

通常境况下,Instance Creator在我们处理没有无参构造函数的时候是必须的。

Instance Creator Example

private class MoneyInstanceCreator implements InstanceCreator<Money> {
  public Money createInstance(Type type) {
    return new Money("1000000", CurrencyCode.USD);
  }
}

Type也可以对应一个泛型类型

  • Type在处理一个特别类型的泛型的时候调用构造函数的时候是很有用的。
  • 比如,如果一个Id类包含一个类如何生成Id

InstanceCreator for a Parameterized Type

 

有些时候,我们要实例化的类是一个参数化类型。通常情况下,这并不是问题,因为其本质是一个raw类型。比如:

class MyList<T> extends ArrayList<T> {
}

class MyListInstanceCreator implements InstanceCreator<MyList<?>> {
    @SuppressWarnings("unchecked")
  public MyList<?> createInstance(Type type) {
    // No need to use a parameterized list since the actual instance will have the raw type anyway.
    return new MyList();
  }
}

然后,有些时候,你需要根据具体的参数类型来创建实例,在这个时候,我们可以将类型参数递给createInstance方法,这里有个例子。

public class Id<T> {
  private final Class<T> classOfId;
  private final long value;
  public Id(Class<T> classOfId, long value) {
    this.classOfId = classOfId;
    this.value = value;
  }
}

class IdInstanceCreator implements InstanceCreator<Id<?>> {
  public Id<?> createInstance(Type type) {
    Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments();
    Type idType = typeParameters[0]; // Id has only one parameterized type T
    return Id.get((Class)idType, 0L);
  }
}

在上面的例子中,如果不传递Id的类的具体的类型,那么Id的实例就无法实例化。我们通过传递参数化类型type来实现。type对象实际上是Java的参数化类型Id<Foo>,就是说实际的实例应该是Id<Foo>。由于Id类仅仅有一个类型参数T,我们使用getActualTypeArgumentI()的下标0来找到类型Foo.class。

Compact Vs. Pretty Printing for JSON Output Format

The default JSON output that is provide by Gson is a compact JSON format.  This means that there will not be any whitespace in the output JSON structure.  Therefore, there will be no whitespace between field names and its value, object fields, and objects within arrays in the JSON output.  As well, “null” fields will be ignored in the output (NOTE: null values will still be included in collections/arrays of objects).  See theNull Object Support section for information on configure Gson to output all null values.

If you like to use the Pretty Print feature, you must configure your Gsoninstance using the GsonBuilder.  The JsonFormatter  is not exposed through our public API, so the client is unable to configure the default print settings/margins for the JSON output.  For now, we only provide a default JsonPrintFormatter that has default line length of 80 character, 2 character indentation, and 4 character right margin.

The following is an example shows how to configure a Gson instance to use the default JsonPrintFormatter instead of theJsonCompactFormatter:

Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(someObject);

Null Object Support

Gson中对于null的默认行为是忽略它。这允许了更加优化的输出格式。然后,当使用Gson反序列化,将字符串转换为Java实例的时候,客户端要小心处理这些null数值。

然后,我么可以通过设置Gson的实例来输出null:

Gson gson = new GsonBuilder().serializeNulls().create();

注意:当使用Gson来序列化nulls的时候,Gson会添加JsonNull元素到JsonElement结构体中。因此,这个类可以用来自定义序列化/反序列化。

public class Foo {
  private final String s;
  private final int i;

  public Foo() {
    this(null, 5);
  }

  public Foo(String s, int i) {
    this.s = s;
    this.i = i;
  }
}

Gson gson = new GsonBuilder().serializeNulls().create();
Foo foo = new Foo();
String json = gson.toJson(foo);
System.out.println(json);

json = gson.toJson(null);
System.out.println(json);

======== OUTPUT ========
{"s":null,"i":5}
null

Versioning Support

 

同一对象的不同版本可以使用注释@Since来维护。这个注释可以用在Classes,Fileds,以及在以后的版本中会实现Methods。为了利用这个特点,我们必须配置Gson的实例来忽略任何的filed/object,其版本大于某些版本号的时候。如果Gson没有设置任何版本号,那么Gson就会序列化/反序列化任何的fileds和classes,而忽略了版本号。

public class VersionedClass {
  @Since(1.1) private final String newerField;
  @Since(1.0) private final String newField;
  private final String field;

  public VersionedClass() {
    this.newerField = "newer";
    this.newField = "new";
    this.field = "old";
  }
}

VersionedClass versionedObject = new VersionedClass();
Gson gson = new GsonBuilder().setVersion(1.0).create();
String jsonOutput = gson.toJson(someObject);
System.out.println(jsonOutput);
System.out.println();

gson = new Gson();
jsonOutput = gson.toJson(someObject);
System.out.println(jsonOutput);

======== OUTPUT ========
{"newField":"new","field":"old"}

{"newerField":"newer","newField":"new","field":"old"}

Excluding Fields From Serialization and Deserialization

 

Gson支持大量的机制来排除top-level classes,fileds,filed types。以下是几种可选的机制可以用来排除filed和class。如果以下的机制不能满足,那么可以通过自定义序列化和反序列化来实现。

Java Modifier Exclusion

 

默认情况下,如果我们标示一个filed为transient,那么就会在序列化和反序列化的时候被排除。同样的,如果一个filed被标注为static,那么默认情况下也是被排除的。如果我们想要包含那些transient的成员变量,那么可以使用以下的办法。

import java.lang.reflect.Modifier;

Gson gson = new GsonBuilder()
    .excludeFieldsWithModifiers(Modifier.STATIC)
    .create();

注意:我们可以添加任意多的修饰符到“excludeFiledsWithModifiers”方法,比如:

Gson gson = new GsonBuilder()
    .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE)
    .create();

Gson’s @Expose

该特性可以用来标记特定的fileds来排除。为了使用这个注释,我们需要如下创建Gson

GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()

User Defined Exclusion Strategies

以上的方法如果不能满足的话,可以通过写我们自己的策略并添加到Gson中,更多的可以参考ExclusionStrategy JavaDoc。

以下的例子展示了如何排除那些使用了”@Foo”注释的fileds。

 @Retention(RetentionPolicy.RUNTIME)
  @Target({ElementType.FIELD})
  public @interface Foo {
    // Field tag only annotation
  }

  public class SampleObjectForTest {
    @Foo private final int annotatedField;
    private final String stringField;
    private final long longField;
    private final Class<?> clazzField;

    public SampleObjectForTest() {
      annotatedField = 5;
      stringField = "someDefaultValue";
      longField = 1234;
    }
  }

  public class MyExclusionStrategy implements ExclusionStrategy {
    private final Class<?> typeToSkip;

    private MyExclusionStrategy(Class<?> typeToSkip) {
      this.typeToSkip = typeToSkip;
    }

    public boolean shouldSkipClass(Class<?> clazz) {
      return (clazz == typeToSkip);
    }

    public boolean shouldSkipField(FieldAttributes f) {
      return f.getAnnotation(Foo.class) != null;
    }
  }

  public static void main(String[] args) {
    Gson gson = new GsonBuilder()
        .setExclusionStrategies(new MyExclusionStrategy(String.class))
        .serializeNulls()
        .create();
    SampleObjectForTest src = new SampleObjectForTest();
    String json = gson.toJson(src);
    System.out.println(json);
  }

======== OUTPUT ========
{"longField":1234}

JSON Field Naming Support

Gson支持一些预先定义的filed naming policies来实现标准的Java变量名称向Json变量名称的转化,具体的可以参考FieldNamingPolicy

也支持一个基于注释的策略允许客户端来为每一个变量定义名称。注意,基于注释的策略可能会导致“Runtime”异常。

以下是一个例子。

private class SomeObject {
  @SerializedName("custom_naming") private final String someField;
  private final String someOtherField;

  public SomeObject(String a, String b) {
    this.someField = a;
    this.someOtherField = b;
  }
}

SomeObject someObject = new SomeObject("first", "second");
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
String jsonRepresentation = gson.toJson(someObject);
System.out.println(jsonRepresentation);

 

======== OUTPUT ========
{"custom_naming":"first","SomeOtherField":"second"}

If you have a need for custom naming policy (see this discussion), you can use the @SerializedName annotation.

Sharing State Across Custom Serializers and Deserializers

有些情况下,我们可以通过自定义Serializers和Deserializers来共享一些状态 (see this discussion).我们可以通过以下3中办法来实现:

  1. 将状态保存在static的变量里面。
  2. 将serializer/deserializer声明为parent type的内部类,然后使用parent type的实例来保存状态。
  3. 使用Java的ThreadLocal

其中1,2不是线程安全的,3是线程安全。

Streaming

In addition Gson’s object model and data binding, you can use Gson to read from and write to a stream. You can also combine streaming and object model access to get the best of both approaches.

Issues in Designing Gson

See the Gson design document for a discussion of issues we faced while designing Gson. It also include a comparison of Gson with other Java libraries that can be used for Json conversion.

Future Enhancements to Gson

For the latest list of proposed enhancements or if you’d like to suggest new ones, see the Issues section under the project website.

Using Gson with Maven2

To use Gson with Maven2/3, you can use the Gson version available in Maven Central by adding the following dependency:

<dependencies>
    <!--  Gson: Java to Json conversion -->
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.3</version>
      <scope>compile</scope>
    </dependency>
</dependencies>

 

That is it, now your maven project is Gson enabled.

About: happyhls


发表评论

电子邮件地址不会被公开。 必填项已用*标注