文章目录
1. Gson 注解介绍
Gson 是 Google 提供的一个 Java 库,用于在 Java 对象和 JSON 数据之间进行高效且灵活的相互转换。Gson 的设计理念注重简单易用,同时支持复杂数据结构的转换,包括嵌套对象、集合、泛型类型以及自定义序列化和反序列化策略。
1.1 Gson 的核心概念
数据转换:Gson 可将 Java 对象转换为 JSON 字符串,也可将 JSON 字符串反序列化为 Java 对象。
例如,下面的代码展示了如何使用 Gson 进行转换:import com.google.gson.Gson; public class GsonDemo { public static void main(String[] args) { // 创建Gson对象 Gson gson = new Gson(); // 示例 Java 对象 User user = new User("Alice", 25); // 将Java对象转换为JSON字符串 String json = gson.toJson(user); System.out.println("Serialized JSON: " + json); // 将JSON字符串转换回Java对象 User parsedUser = gson.fromJson(json, User.class); System.out.println("Deserialized User: " + parsedUser); } } class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "User{name='" + name + "', age=" + age + "}"; } }
反射机制的应用:Gson 使用 Java 的反射机制来解析对象的结构,从而自动识别类中的属性并将其映射到 JSON 的键上,这使得开发者能够在无需编写大量样板代码的情况下完成数据转换。
灵活性与扩展性:Gson 除了基本的数据转换功能外,还允许开发者自定义序列化和反序列化过程,如通过注册自定义 TypeAdapter、使用注解控制序列化行为等。
细粒度控制:
除了 @Expose,Gson 还通过其他注解(比如 @SerializedName、@Since 和 @Until)赋予开发者更多控制权来指定字段的 JSON 名称、根据版本号过滤字段等。这些注解允许开发者灵活地应对 JSON 格式变更、字段重命名以及兼容不同版本的数据交互。实用性:
对比于通过其它逻辑条件控制哪些属性参与转换的方式,使用注解能使得代码更加直观。例如,通过阅读类的定义就可以明白哪些字段是需要暴露给外部使用的,而不必额外查阅序列化配置逻辑,这就像生活中给重要物品贴上明显标记一样,让后续的维护者一目了然。
2. @Expose 注解解析
在使用 Gson 进行对象与 JSON 转换时,经常需要控制哪些属性参与序列化或反序列化。@Expose 注解正是用于这种场景下的精细化控制。
2.1 @Expose 的基本概念与含义
@Expose 注解可以标记类中的属性,表明该属性是否应参与序列化(Java 对象转 JSON)或反序列化(JSON 转 Java 对象)的过程。
- 默认情况下,即使没有使用 @Expose,所有属性都会被处理。
- 当使用 GsonBuilder 的 excludeFieldsWithoutExposeAnnotation() 方法时,只有被 @Expose 注解标记的字段才会被考虑。
- 此外,@Expose 还可以通过设置参数来分别控制序列化和反序列化,例如:
- serialize:为 true 时,表示该字段将参与序列化。
- deserialize:为 true 时,表示该字段将参与反序列化。
- 默认都是true
例如,若你想序列化时保留某个属性,而反序列化时忽略它,只需设置如下:
@Expose(serialize = true, deserialize = false)
private String someField;
2.2 实际使用示例与代码详解
下面的示例演示了如何利用 @Expose 来有选择性地控制字段参与转换的情况:
// 导入所需的包
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose;
public class ExposeDemo {
public static void main(String[] args) {
// 创建一个仅处理被 @Expose 标记字段的 Gson 实例
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
// 初始化测试对象
User user = new User("Alice", "SuperSecret");
// 对象转换为 JSON 字符串
String json = gson.toJson(user);
System.out.println("Serialized JSON: " + json);
// 模拟从 JSON 字符串还原 Java 对象
User deserializedUser = gson.fromJson("{\"username\":\"Bob\",\"password\":\"Hidden\"}", User.class);
System.out.println("Deserialized User: " + deserializedUser);
}
}
// User 类中,只有 username 会参与序列化与反序列化,而 password 根据注解设置被忽略或部分忽略
class User {
// 使用 @Expose 表示该字段将参与序列化与反序列化
@Expose
private String username;
// 标记:仅反序列化时忽略该字段(或者你也可以选择 serialize = false, deserialize = true)
@Expose(serialize = false, deserialize = false)
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "User{username='" + username + "', password='" + password + "'}";
}
}
【代码解析】
- 我们在 User 类中使用 @Expose 标记了 username 和 password 字段。
- 通过 GsonBuilder 的 excludeFieldsWithoutExposeAnnotation() 方法,Gson 只会处理被 @Expose 注解的字段。
- 同时,通过设定 password 的 serialize 与 deserialize 参数,明确控制了该字段在序列化和反序列化中的行为,确保进行 JSON 转换时可根据需求忽略敏感信息。
2.3 @Expose 与反射原理的关系解析
Gson 在运行时通过 Java 的反射机制访问对象的字段信息。@Expose 注解的检查也是在这一过程中进行的:
- 当你调用 toJson() 或 fromJson() 方法时,Gson 会利用反射遍历对象的所有字段。
- 如果你使用了 excludeFieldsWithoutExposeAnnotation() 配置,则在遍历过程中,Gson 会检查每个字段是否使用了 @Expose 注解,并依据注解中的 serialize 或 deserialize 参数决定是否处理该字段。
- 这种利用反射和注解的结合方式类似于我们生活中“按标签分类”的方法,比如在食品超市中,只有贴有“有机”标签的产品才会被分配到特定货架上。
- 通过这种机制,开发者可以无需编写大量冗余代码,而是通过简单的注解标记,实现对敏感信息过滤或特定字段处理的定制化控制。
3. 其他常用 Gson 注解对比
在 Gson 中,除了 @Expose 之外,还有其他一些有用的注解可以帮助开发者在数据转换过程中获得更细粒度的控制。以下将详细介绍 @SerializedName、@Since 与 @Until 这几种常用注解,并讨论它们的使用场景以及综合使用时的注意事项。
3.1 @SerializedName 注解详解
@SerializedName 注解用于指定 Java 对象的属性与 JSON 字段之间的映射关系,这在以下场景中尤为有用:
- 当 JSON 数据中的字段名称与 Java 对象中的变量名称不一致时。
- 当需要支持多语言或者需要对字段名进行重构而又不影响已有 JSON 格式时。
例如,假设你的服务器返回的 JSON 数据字段为 “user_name”,而你希望在 Java 类中使用更符合命名规范的 “username”,你可以这么写:
// 导入所需包
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
public class SerializedNameDemo {
public static void main(String[] args) {
// 创建 Gson 实例
Gson gson = new GsonBuilder().create();
// JSON 数据中的字段为 "user_name"
String json = "{\"user_name\": \"Charlie\", \"age\": 30}";
// Gson 根据 @SerializedName 将 JSON 中的 "user_name" 映射到 Java 对象的 username 属性
User user = gson.fromJson(json, User.class);
System.out.println("Deserialized User: " + user);
// 将 Java 对象序列化为 JSON 时,同样使用 @SerializedName 映射关键字
String serializedJson = gson.toJson(user);
System.out.println("Serialized JSON: " + serializedJson);
}
}
class User {
@SerializedName("user_name")
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
@Override
public String toString() {
return "User{username='" + username + "', age=" + age + "}";
}
}
【要点说明】
- 通过 @SerializedName(“user_name”),JSON 中的 “user_name” 被映射到 Java 类中的 username 属性。
- 当 JSON 字段名发生变化(例如由于多语言支持或重构需求)时,仅需调整注解内容,而无需更改 Java 代码中对该属性的引用。
3.2 @Since 和 @Until 注解
这两个注解用于基于版本控制的数据过滤,使得同一模型的不同版本可以灵活地参与序列化和反序列化。
- @Since:表示该字段自某个版本以后才生效。只有当前 Gson 实例配置的版本号大于或等于该字段标记的版本时,字段才会参与处理。
- @Until:表示该字段在某个版本之前有效。只有当前配置的版本号小于该字段标记的版本时,字段才会参与处理。
【使用场景】
- 当你的数据结构随着版本升级而变化时,可以通过这两个注解对新旧字段进行管控,使得应用在处理不同版本的 JSON 数据时更加灵活。
【代码示例】
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Since;
import com.google.gson.annotations.Until;
public class VersionDemo {
public static void main(String[] args) {
// 设置 Gson 的版本号为 1.5
Gson gson = new GsonBuilder().setVersion(1.5).create();
// 数据结构中部分字段根据版本信息决定是否参与序列化/反序列化
Product product = new Product("Laptop", 999.99, "Electronics");
// 序列化时,只有版本符合要求的字段会被输出
String json = gson.toJson(product);
System.out.println("Serialized JSON: " + json);
}
}
class Product {
private String name;
private double price;
@Since(1.2) // 表示字段自版本1.2起生效。由于当前版本1.5大于1.2,所以参与序列化
private String category;
@Until(1.3) // 表示字段在版本1.3之前有效。当前版本1.5大于1.3,所以该字段将被排除
private String deprecatedField;
public Product(String name, double price, String category) {
this.name = name;
this.price = price;
this.category = category;
this.deprecatedField = "obsolete";
}
}
【示例说明】
- 在上述示例中,当 Gson 的版本设置为 1.5 时:
- 字段 category 因为 @Since(1.2) 而有效;
- 字段 deprecatedField 因为 @Until(1.3) 而被过滤,不参与序列化和反序列化。
4. 实战案例:构建可控序列化的 Java 实体
在实际项目中,我们常常需要对 Java 实体类进行精细的控制,确保只有符合条件的字段在序列化或反序列化时被处理。从业务需求出发,通过合理使用 @Expose、@SerializedName、@Since 等注解,可以高效地构建出满足要求的实体类。下面我们将通过一个完整案例来说明这一过程。
4.1 业务需求简介及对应解决方案
【业务需求】
假设我们的项目中有一个用户信息实体,其包含以下字段:
- 用户名(username):在 JSON 中需要与 “user_name” 进行映射。
- 密码(password):作为敏感信息,既不希望在序列化输出后传递,也不需要在反序列化时接收。
- 邮箱(email):正常处理且需要参与序列化和反序列化。
- 注册日期(registerDate):随着版本迭代,旧版本无需该字段,新版本中才会生效。
【解决方案】
- 使用 @SerializedName 将 JSON 字段 “user_name” 映射为实体类的 username 属性。
- 通过 @Expose 注解和 GsonBuilder 的 excludeFieldsWithoutExposeAnnotation() 方法,仅处理我们明确标注的字段。
- 对密码字段使用 @Expose(serialize = false, deserialize = false) 进行屏蔽。
- 利用 @Since 注解控制 registerDate 字段,使其只在设定版本及以上参与转换。
- 配置 Gson 实例时,通过 setVersion() 方法与 @Since 配合,实现版本兼容。
4.2 实现细节解析
【代码示例】
下面的示例展示了如何结合多个注解构建实体类,并利用 Gson 执行可控的序列化与反序列化。
// 1. 导入 Gson 包(需要在项目中引入 Gson 库,如 gson-2.8.6.jar 或通过 Maven/Gradle 依赖)
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Since;
import java.util.Date;
// 2. 构建实体类 User,结合多个注解
class User {
// 定义字段 username,与 JSON 中的 "user_name" 绑定,参与序列化和反序列化
@Expose
@SerializedName("user_name")
private String username;
// 密码字段不参与序列化和反序列化(敏感信息安全考虑)
@Expose(serialize = false, deserialize = false)
private String password;
// 邮箱字段,直接参与转换
@Expose
private String email;
// 注册日期字段:新版本生效,设定自 2.0 版本起生效
@Expose
@Since(2.0)
private Date registerDate;
// 构造函数
public User(String username, String password, String email, Date registerDate) {
this.username = username;
this.password = password;
this.email = email;
this.registerDate = registerDate;
}
@Override
public String toString() {
return "User{username='" + username + "', password='" + password +
"', email='" + email + "', registerDate=" + registerDate + "}";
}
}
public class ControlledSerializationDemo {
public static void main(String[] args) {
// 3. 配置 Gson 实例:
// - 使用 excludeFieldsWithoutExposeAnnotation() 方法,说明只有标记了 @Expose 的字段参与转换
// - 通过 setVersion() 设置版本号,此处设置为 2.5(大于2.0,则 registerDate 字段会参与转换)
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.setVersion(2.5)
.setPrettyPrinting() // 可选:格式化输出
.create();
// 4. 初始化一个 User 对象
User user = new User("JohnDoe", "pass1234", "john@example.com", new Date());
// 5. 序列化:User 对象转换为 JSON 字符串
String json = gson.toJson(user);
System.out.println("Serialized JSON:\n" + json);
// 6. 反序列化:从 JSON 恢复 User 对象
// 注意:因为密码字段不参与反序列化,所以结果中密码值依然为空(或 null)
String jsonInput = "{ \"user_name\": \"JaneDoe\", \"email\": \"jane@example.com\", \"registerDate\": \"Jun 15, 2023, 10:30:00 AM\" }";
User deserializedUser = gson.fromJson(jsonInput, User.class);
System.out.println("Deserialized User:\n" + deserializedUser);
}
}
【环境配置说明】
- 请确保已加入 Gson 库,例如通过 Maven:
com.google.code.gson
gson
2.8.6 - 或在 IDE 中引入对应的 jar 包。
- JDK 版本要求:1.8 或更高。
【关键点解析】
- 通过 @SerializedName,将 JSON 字段 “user_name” 和实体类字段 username 对应。
- @Expose 注解使我们可以通过 GsonBuilder 的 excludeFieldsWithoutExposeAnnotation() 方法精准控制字段,敏感信息(如 password)可通过设置 serialize 和 deserialize 参数屏蔽。
- @Since 配合 setVersion() 配置,实现根据版本信息过滤数据;在版本 2.5 中,registerDate 字段生效,而在低版本中(例如 1.5)则会被排除。