Android Gson注解驱动的转换规则原理(9)

码字不易,请大佬们点点关注,谢谢~

一、Gson注解概述

1.1 注解在Gson中的作用

在Android开发中,Gson作为主流的JSON处理库,通过注解机制提供了灵活的对象序列化和反序列化规则。注解允许开发者直接在Java类的字段上声明JSON转换规则,而无需编写额外的适配器代码。这种方式简化了开发流程,提高了代码的可读性和可维护性。

Gson提供的核心注解包括:

  • @SerializedName:指定JSON字段名称
  • @Expose:控制字段的序列化和反序列化
  • @Since@Until:版本控制
  • @JsonAdapter:指定自定义TypeAdapter

这些注解通过与Gson的反射机制结合,实现了基于注解的转换规则。

1.2 注解处理的基本流程

Gson处理注解的基本流程如下:

  1. 反射获取字段信息:Gson通过Java反射机制获取类的所有字段
  2. 注解解析:检查每个字段上的注解信息
  3. 规则应用:根据注解信息修改默认的序列化和反序列化行为
  4. 生成JSON:根据应用的规则生成或解析JSON数据

这个流程在Gson内部通过多个组件协同完成,包括FieldNamingStrategyExclusionStrategy和反射适配器等。

二、@SerializedName注解的原理

2.1 @SerializedName注解的定义与作用

@SerializedName是Gson中最常用的注解之一,用于指定Java字段在JSON中对应的名称。其定义如下:

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SerializedName {
    /**
     * 指定JSON字段的名称
     */
    String value();
    
    /**
     * 备选名称,用于反序列化时匹配多个可能的JSON字段名
     */
    String[] alternate() default {};
}

通过@SerializedName注解,开发者可以解决Java字段名与JSON字段名不一致的问题,同时支持反序列化时的多个备选名称。

2.2 @SerializedName在序列化中的处理流程

在序列化过程中,Gson通过以下步骤处理@SerializedName注解:

  1. 字段扫描:通过反射获取类的所有字段
  2. 注解检查 :检查每个字段是否存在@SerializedName注解
  3. 名称确定:如果存在注解,则使用注解指定的名称作为JSON字段名
  4. 写入JSON:使用确定的名称写入JSON数据

源码分析如下:

java 复制代码
// ReflectiveTypeAdapterFactory类中的关键方法
private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
    Map<String, BoundField> result = new LinkedHashMap<>();
    if (raw.isInterface()) {
        return result;
    }
    
    Type declaredType = type.getType();
    while (raw != Object.class) {
        Field[] fields = raw.getDeclaredFields();
        for (Field field : fields) {
            // 检查字段是否应该被序列化或反序列化
            boolean serialize = excludeField(field, true);
            boolean deserialize = excludeField(field, false);
            
            if (!serialize && !deserialize) {
                continue;
            }
            
            // 设置字段可访问
            field.setAccessible(true);
            
            // 获取字段的类型
            Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
            
            // 处理SerializedName注解
            List<String> fieldNames = getFieldNames(field);
            
            // 为字段创建BoundField
            BoundField boundField = createBoundField(
                context, field, fieldNames,
                TypeToken.get(fieldType),
                serialize,
                deserialize
            );
            
            // 将BoundField添加到结果中
            for (String name : fieldNames) {
                BoundField previous = result.put(name, boundField);
                if (previous != null) {
                    throw new IllegalArgumentException(
                        "Duplicate field name '" + name + "'. Found on " +
                        previous.field + " and " + field);
                }
            }
        }
        
        // 处理父类字段
        type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
        raw = type.getRawType();
    }
    
    return result;
}

// 获取字段名称的方法
private List<String> getFieldNames(Field field) {
    SerializedName annotation = field.getAnnotation(SerializedName.class);
    if (annotation == null) {
        // 如果没有注解,使用字段名
        return Collections.singletonList(fieldNamingPolicy.translateName(field));
    }
    
    // 获取主名称
    String serializedName = annotation.value();
    // 获取备选名称
    String[] alternates = annotation.alternate();
    
    // 构建名称列表
    List<String> fieldNames = new ArrayList<>(1 + alternates.length);
    fieldNames.add(serializedName);
    Collections.addAll(fieldNames, alternates);
    
    return fieldNames;
}

2.3 @SerializedName在反序列化中的处理流程

在反序列化过程中,Gson通过以下步骤处理@SerializedName注解:

  1. JSON解析:解析JSON数据,获取字段名称
  2. 名称匹配 :将JSON字段名称与Java类字段的@SerializedName注解值进行匹配
  3. 字段赋值:如果匹配成功,则将JSON值赋给对应的Java字段

源码分析如下:

java 复制代码
// ReflectiveTypeAdapterFactory.Adapter类中的read方法
@Override
public T read(JsonReader in) throws IOException {
    if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return null;
    }
    
    // 创建实例
    T instance = constructorConstructor.get(typeToken).construct();
    
    try {
        // 开始解析JSON对象
        in.beginObject();
        while (in.hasNext()) {
            // 读取JSON字段名称
            String name = in.nextName();
            
            // 查找匹配的BoundField
            BoundField field = boundFields.get(name);
            if (field == null || !field.deserialized) {
                // 如果没有找到或不可反序列化,则跳过
                in.skipValue();
                continue;
            }
            
            // 读取字段值
            field.read(in, instance);
        }
    } catch (IllegalStateException e) {
        throw new JsonSyntaxException(e);
    } catch (IllegalAccessException e) {
        throw new AssertionError(e);
    } finally {
        // 结束解析JSON对象
        in.endObject();
    }
    
    return instance;
}

2.4 备选名称(alternate)的处理机制

@SerializedNamealternate属性允许指定多个备选名称,用于处理JSON字段名称不一致的情况。在反序列化时,Gson会尝试所有可能的名称进行匹配。

java 复制代码
// 处理备选名称的逻辑
List<String> fieldNames = getFieldNames(field);
for (String name : fieldNames) {
    BoundField previous = result.put(name, boundField);
    if (previous != null) {
        throw new IllegalArgumentException("Duplicate field name");
    }
}

在上述代码中,getFieldNames方法返回的列表包含主名称和所有备选名称。这些名称都会被添加到boundFields映射中,确保在反序列化时能够匹配到任何一个名称。

三、@Expose注解的原理

3.1 @Expose注解的定义与作用

@Expose注解用于控制字段是否应该被序列化或反序列化。其定义如下:

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expose {
    /**
     * 指定该字段是否应该被序列化,默认为true
     */
    boolean serialize() default true;
    
    /**
     * 指定该字段是否应该被反序列化,默认为true
     */
    boolean deserialize() default true;
}

通过@Expose注解,开发者可以精细控制哪些字段应该参与序列化和反序列化过程。

3.2 @Expose注解的处理机制

要使用@Expose注解,必须在创建Gson实例时启用excludeFieldsWithoutExposeAnnotation()选项:

java 复制代码
Gson gson = new GsonBuilder()
    .excludeFieldsWithoutExposeAnnotation()
    .create();

当启用该选项后,Gson会检查每个字段是否有@Expose注解,并根据注解的值决定是否序列化或反序列化该字段。

源码分析如下:

java 复制代码
// Excluder类中的关键方法
@Override
public boolean excludeField(Field field, boolean serialize) {
    // 检查是否启用了@Expose注解
    if (serialize && serializeExclusionStrategy != null
        && serializeExclusionStrategy.shouldSkipField(field)) {
        return true;
    }
    
    if (!serialize && deserializeExclusionStrategy != null
        && deserializeExclusionStrategy.shouldSkipField(field)) {
        return true;
    }
    
    // 检查是否需要排除没有@Expose注解的字段
    if (requireExpose) {
        Expose expose = field.getAnnotation(Expose.class);
        if (expose == null) {
            return true;
        }
        return serialize ? !expose.serialize() : !expose.deserialize();
    }
    
    // 其他排除规则...
    
    return false;
}

3.3 @Expose与默认行为的对比

当不使用@Expose注解时,Gson默认会序列化和反序列化所有非静态、非瞬态的字段。而启用@Expose注解后,只有明确标注了@Expose的字段才会被处理。

例如:

java 复制代码
class User {
    @Expose
    private String name;
    
    private int age; // 没有@Expose注解
    
    @Expose(serialize = false, deserialize = true)
    private String password;
    
    // 构造方法和getter/setter省略
}

使用以下方式创建Gson实例:

java 复制代码
Gson gson = new GsonBuilder()
    .excludeFieldsWithoutExposeAnnotation()
    .create();

在这个例子中:

  • name字段会被序列化和反序列化
  • age字段不会被处理,因为没有@Expose注解
  • password字段只会被反序列化,不会被序列化

四、@Since和@Until注解的原理

4.1 版本控制注解的定义与作用

@Since@Until注解用于基于版本号控制字段的序列化和反序列化。其定义如下:

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Since {
    /**
     * 指定字段或类的版本号
     */
    double value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Until {
    /**
     * 指定字段或类的版本截止号
     */
    double value();
}

这两个注解通常用于API版本控制,允许在不同版本的应用中选择性地序列化或反序列化某些字段。

4.2 版本控制的实现机制

Gson通过VersionExclusionStrategy类实现版本控制,该类实现了ExclusionStrategy接口。

java 复制代码
// VersionExclusionStrategy类的实现
private static final class VersionExclusionStrategy implements ExclusionStrategy {
    private final double version;
    
    public VersionExclusionStrategy(double version) {
        this.version = version;
    }
    
    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        // 检查Since注解
        Since since = f.getAnnotation(Since.class);
        if (since != null && since.value() > version) {
            return true;
        }
        
        // 检查Until注解
        Until until = f.getAnnotation(Until.class);
        if (until != null && until.value() <= version) {
            return true;
        }
        
        return false;
    }
    
    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        // 检查类上的Since注解
        Since since = clazz.getAnnotation(Since.class);
        if (since != null && since.value() > version) {
            return true;
        }
        
        // 检查类上的Until注解
        Until until = clazz.getAnnotation(Until.class);
        if (until != null && until.value() <= version) {
            return true;
        }
        
        return false;
    }
}

4.3 版本控制的使用示例

在创建Gson实例时,可以通过setVersion方法设置当前版本号:

java 复制代码
Gson gson = new GsonBuilder()
    .setVersion(2.0)
    .create();

然后在类或字段上使用@Since@Until注解:

java 复制代码
class User {
    private String name;
    
    @Since(2.0)
    private String email;
    
    @Until(1.5)
    private String oldField;
    
    // 构造方法和getter/setter省略
}

在这个例子中:

  • 当版本号设置为2.0时,email字段会被包含,而oldField字段会被排除
  • 当版本号设置为1.0时,email字段会被排除,oldField字段会被包含

五、@JsonAdapter注解的原理

5.1 @JsonAdapter注解的定义与作用

@JsonAdapter注解用于为特定类型指定自定义的TypeAdapterTypeAdapterFactoryJsonSerializer/JsonDeserializer。其定义如下:

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
public @interface JsonAdapter {
    /**
     * 指定自定义的TypeAdapter、TypeAdapterFactory或JsonSerializer/JsonDeserializer类
     */
    Class<?> value();
    
    /**
     * 指定适配器是否应该被实例化并用于子类
     */
    boolean nullSafe() default true;
}

5.2 @JsonAdapter注解的处理流程

Gson在处理@JsonAdapter注解时,会执行以下步骤:

  1. 注解检查 :在序列化或反序列化前,检查目标类型或字段上是否存在@JsonAdapter注解
  2. 适配器实例化:根据注解中指定的类,通过反射创建适配器实例
  3. 适配器应用:使用创建的适配器处理序列化或反序列化操作

源码分析如下:

java 复制代码
// TypeAdapters类中的获取适配器方法
public static TypeAdapter<?> getAdapter(TypeToken<?> type, JsonAdapter annotation) {
    if (annotation == null) {
        return null;
    }
    
    // 获取注解中指定的适配器类
    Class<?> adapterClass = annotation.value();
    
    // 检查适配器类是否为TypeAdapter类型
    if (TypeAdapter.class.isAssignableFrom(adapterClass)) {
        Class<? extends TypeAdapter<?>> typeAdapterClass = 
            (Class<? extends TypeAdapter<?>>) adapterClass;
        return newInstance(typeAdapterClass);
    }
    
    // 检查适配器类是否为TypeAdapterFactory类型
    if (TypeAdapterFactory.class.isAssignableFrom(adapterClass)) {
        Class<? extends TypeAdapterFactory> factoryClass = 
            (Class<? extends TypeAdapterFactory>) adapterClass;
        TypeAdapterFactory factory = newInstance(factoryClass);
        return factory.create(GsonBuilder.DEFAULT_GSON, type);
    }
    
    // 检查适配器类是否为JsonSerializer类型
    if (JsonSerializer.class.isAssignableFrom(adapterClass) ||
        JsonDeserializer.class.isAssignableFrom(adapterClass)) {
        
        // 处理JsonSerializer和JsonDeserializer的组合
        Type adapterType = getSupertype(adapterClass, JsonAdapter.class, Object.class);
        Type[] args = $Gson$Types.getRawType(adapterType).getTypeParameters();
        if (args.length != 1) {
            throw new IllegalArgumentException(
                "@JsonAdapter " + adapterClass.getName() + " must specify one type parameter.");
        }
        
        Type adaptsType = $Gson$Types.resolve(adapterType, adapterClass, args[0]);
        
        // 创建适配器包装器
        return new TreeTypeAdapter<>(
            (JsonSerializer<Object>) newInstance(adapterClass),
            (JsonDeserializer<Object>) newInstance(adapterClass),
            GsonBuilder.DEFAULT_GSON,
            TypeToken.get(adaptsType),
            null
        );
    }
    
    throw new IllegalArgumentException(
        "@JsonAdapter value must be TypeAdapter, TypeAdapterFactory"
        + ", JsonSerializer or JsonDeserializer reference.");
}

5.3 @JsonAdapter注解的使用示例

以下是一个使用@JsonAdapter注解的示例:

java 复制代码
// 自定义Date类型适配器
@JsonAdapter(DateTypeAdapter.class)
class MyDate {
    private Date date;
    
    public MyDate(Date date) {
        this.date = date;
    }
    
    public Date getDate() {
        return date;
    }
}

// 自定义Date类型适配器
class DateTypeAdapter extends TypeAdapter<Date> {
    private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    
    @Override
    public void write(JsonWriter out, Date value) throws IOException {
        if (value == null) {
            out.nullValue();
            return;
        }
        out.value(dateFormat.format(value));
    }
    
    @Override
    public Date read(JsonReader in) throws IOException {
        if (in.peek() == JsonToken.NULL) {
            in.nextNull();
            return null;
        }
        String dateStr = in.nextString();
        try {
            return dateFormat.parse(dateStr);
        } catch (ParseException e) {
            throw new JsonSyntaxException(e);
        }
    }
}

在这个例子中,@JsonAdapter注解为MyDate类指定了自定义的DateTypeAdapter,使得该类在序列化和反序列化时使用特定的日期格式。

六、Gson注解处理的核心组件

6.1 Excluder类

Excluder类是Gson中处理字段排除的核心组件,它实现了ExclusionStrategy接口,并根据注解和配置决定哪些字段应该被排除。

java 复制代码
public final class Excluder implements ExclusionStrategy, Cloneable {
    // 各种排除配置
    private double version = -1;
    private int modifiers = Modifier.TRANSIENT | Modifier.STATIC;
    private boolean serializeInnerClasses = true;
    private boolean requireExpose;
    
    // 序列化和反序列化的排除策略
    private final List<ExclusionStrategy> serializationStrategies = new ArrayList<>();
    private final List<ExclusionStrategy> deserializationStrategies = new ArrayList<>();
    
    // 检查字段是否应该被排除
    @Override
    public boolean excludeField(Field field, boolean serialize) {
        // 检查字段修饰符
        if ((field.getModifiers() & modifiers) != 0) {
            return true;
        }
        
        // 检查是否为内部类
        if (!serializeInnerClasses && isInnerClass(field.getType())) {
            return true;
        }
        
        // 检查是否为匿名类或本地类
        if (isAnonymousOrLocal(field.getType())) {
            return true;
        }
        
        // 检查版本控制注解
        if (version != -1) {
            Since since = field.getAnnotation(Since.class);
            if (since != null && since.value() > version) {
                return true;
            }
            Until until = field.getAnnotation(Until.class);
            if (until != null && until.value() <= version) {
                return true;
            }
        }
        
        // 检查@Expose注解
        if (requireExpose) {
            Expose expose = field.getAnnotation(Expose.class);
            if (expose == null) {
                return true;
            }
            if (serialize ? !expose.serialize() : !expose.deserialize()) {
                return true;
            }
        }
        
        // 应用注册的排除策略
        List<ExclusionStrategy> list = serialize
            ? serializationStrategies
            : deserializationStrategies;
        for (ExclusionStrategy exclusionStrategy : list) {
            if (exclusionStrategy.shouldSkipField(fieldAttributes(field))) {
                return true;
            }
        }
        
        return false;
    }
    
    // 其他方法...
}

6.2 ReflectiveTypeAdapterFactory类

ReflectiveTypeAdapterFactory是Gson中基于反射实现序列化和反序列化的核心工厂类。它负责处理注解并创建相应的适配器。

java 复制代码
public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
    private final ConstructorConstructor constructorConstructor;
    private final FieldNamingStrategy fieldNamingPolicy;
    private final Excluder excluder;
    private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
    
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        Class<? super T> raw = type.getRawType();
        
        // 检查是否为接口或抽象类
        if (!Object.class.isAssignableFrom(raw)) {
            return null;
        }
        
        // 处理@JsonAdapter注解
        JsonAdapter annotation = type.getRawType().getAnnotation(JsonAdapter.class);
        if (annotation != null) {
            return jsonAdapterFactory.create(gson, type, annotation);
        }
        
        // 创建反射适配器
        return new Adapter<>(
            constructorConstructor.get(type),
            getBoundFields(gson, type, raw)
        );
    }
    
    // 获取绑定字段的方法
    private Map<String, BoundField> getBoundFields(
        Gson context, TypeToken<?> type, Class<?> raw) {
        // 实现逻辑...
    }
    
    // 适配器实现类
    static final class Adapter<T> extends TypeAdapter<T> {
        private final ObjectConstructor<T> constructor;
        private final Map<String, BoundField> boundFields;
        
        // 构造方法和读写方法...
    }
    
    // 绑定字段内部类
    abstract static class BoundField {
        // 字段信息和读写方法...
    }
}

6.3 FieldNamingStrategy接口

FieldNamingStrategy接口定义了Java字段名到JSON字段名的映射策略。Gson提供了多种内置策略,并允许自定义策略。

java 复制代码
public interface FieldNamingStrategy {
    /**
     * 将Java字段名转换为JSON字段名
     */
    public String translateName(Field f);
}

Gson内置的实现包括:

  • FieldNamingPolicy.IDENTITY:使用原始字段名
  • FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES:将字段名转换为小写并使用下划线分隔
  • FieldNamingPolicy.LOWER_CASE_WITH_DASHES:将字段名转换为小写并使用连字符分隔
  • FieldNamingPolicy.UPPER_CAMEL_CASE:将字段名转换为大驼峰格式

七、注解驱动转换的性能考量

7.1 反射开销

基于注解的转换机制依赖Java反射,这会带来一定的性能开销。反射操作(如获取字段、调用方法)比直接代码执行慢得多,尤其是在处理大量对象时。

7.2 优化建议

为了减少注解驱动转换的性能开销,可以考虑以下优化建议:

  1. 使用自定义TypeAdapter :对于性能敏感的场景,直接实现TypeAdapter可以避免反射开销。

  2. 缓存反射结果:在自定义适配器中缓存反射获取的字段和方法信息,避免重复反射操作。

  3. 批量处理:尽量批量处理数据,减少对象创建和反射调用的次数。

  4. 使用ProGuard/R8:通过代码混淆工具移除未使用的字段和方法,减少反射查找范围。

  5. 避免过度使用注解:只在必要的地方使用注解,避免不必要的注解处理开销。

八、总结与展望

8.1 注解驱动转换的优势

Gson的注解驱动转换机制提供了以下优势:

  1. 简化开发:通过注解直接声明转换规则,无需编写额外的适配器代码。

  2. 灵活性:提供多种注解类型,满足不同的转换需求。

  3. 可维护性:注解直接与Java类关联,使代码更易于理解和维护。

  4. 扩展性:可以通过自定义注解和适配器进一步扩展Gson的功能。

8.2 未来发展方向

随着Java和Android技术的发展,Gson的注解驱动转换机制可能会在以下方向发展:

  1. Kotlin集成:更好地支持Kotlin语言特性,如数据类、协程等。

  2. 性能优化:减少反射开销,提高注解处理效率。

  3. 编译时处理:引入编译时注解处理,生成优化的转换代码。

  4. 增强注解功能:提供更多注解选项,支持更复杂的转换规则。

  5. 与其他框架集成:更紧密地集成其他Android框架,如Room、Retrofit等。

通过不断优化和扩展,Gson的注解驱动转换机制将继续为Android开发者提供高效、灵活的JSON处理解决方案。

相关推荐
刘龙超6 小时前
如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(一)基础搭建
android jetpack
小悟空8 小时前
[AI 生成] Flink 面试题
大数据·面试·flink
Jackilina_Stone10 小时前
【faiss】用于高效相似性搜索和聚类的C++库 | 源码详解与编译安装
android·linux·c++·编译·faiss
Sherry00710 小时前
CSS Grid 交互式指南(译)(下)
css·面试
一只毛驴10 小时前
浏览器中的事件冒泡,事件捕获,事件委托
前端·面试
一只叫煤球的猫10 小时前
你真的处理好 null 了吗?——11种常见但容易被忽视的空值处理方式
java·后端·面试
棒棒AIT10 小时前
mac 苹果电脑 Intel 芯片(Mac X86) 安卓虚拟机 Android模拟器 的救命稻草(下载安装指南)
android·游戏·macos·安卓·mac
KarrySmile10 小时前
Day04–链表–24. 两两交换链表中的节点,19. 删除链表的倒数第 N 个结点,面试题 02.07. 链表相交,142. 环形链表 II
算法·链表·面试·双指针法·虚拟头结点·环形链表
fishwheel11 小时前
Android:Reverse 实战 part 2 番外 IDA python
android·python·安全
一只毛驴11 小时前
谈谈浏览器的DOM事件-从0级到2级
前端·面试