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处理解决方案。

相关推荐
whysqwhw7 分钟前
OkHttp框架的全面深入架构分析
android
你过来啊你8 分钟前
Android App冷启动流程详解
android
FairyDiana20 分钟前
【JavaScript】一篇文章,带你拿捏JS中的类型判断
javascript·面试
然我20 分钟前
打工人必看!Flex “驯服” 前端面试,让代码像整理工位一样顺
前端·面试·html
泓博1 小时前
KMP(Kotlin Multiplatform)改造(Android/iOS)老项目
android·ios·kotlin
移动开发者1号1 小时前
使用Baseline Profile提升Android应用启动速度的终极指南
android·kotlin
崎岖Qiu1 小时前
【Spring篇08】:理解自动装配,从spring.factories到.imports剖析
java·spring boot·后端·spring·面试·java-ee
移动开发者1号1 小时前
解析 Android Doze 模式与唤醒对齐
android·kotlin
菠萝加点糖3 小时前
Kotlin Data包含ByteArray类型
android·开发语言·kotlin
心平愈三千疾8 小时前
通俗理解JVM细节-面试篇
java·jvm·数据库·面试