Android Gson 字段过滤与命名策略实现原理剖析(19)

5 阅读6分钟

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

一、字段过滤与命名策略概述

1.1 核心功能与应用场景

Gson的字段过滤与命名策略是数据序列化和反序列化过程中的重要机制,主要解决以下问题:

  • 字段过滤:控制哪些字段应该被序列化或反序列化,常用于敏感数据保护、接口数据裁剪等场景。
  • 命名策略:实现Java字段名与JSON字段名之间的映射转换,支持如蛇形命名(snake_case)、大驼峰命名(PascalCase)等多种格式。

1.2 关键组件与实现层次

这两个功能主要由以下组件协同实现:

  • ExclusionStrategy:字段过滤的核心接口,允许自定义过滤规则。
  • FieldNamingStrategy:命名策略的核心接口,定义字段名转换规则。
  • Annotation:通过@Expose@SerializedName等注解实现细粒度控制。
  • ReflectiveTypeAdapterFactory:反射序列化的核心工厂,集成了字段过滤和命名逻辑。

二、字段过滤机制原理

2.1 内置过滤策略

Gson提供了多种内置过滤方式:

2.1.1 @Expose注解

@Expose注解允许精确控制字段的序列化和反序列化:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expose {
    // 序列化时是否包含该字段,默认为true
    boolean serialize() default true;
    // 反序列化时是否包含该字段,默认为true
    boolean deserialize() default true;
}

使用示例:

public class User {
    @Expose
    private String username;
    
    @Expose(serialize = false, deserialize = false)
    private String password; // 不参与序列化和反序列化
    
    private String email; // 默认参与序列化和反序列化
}
2.1.2 transient关键字

Java原生的transient关键字也会被Gson识别,标记为transient的字段不会被序列化:

public class User {
    private String username;
    private transient String token; // 不会被序列化
}
2.1.3 Modifier过滤

Gson默认会过滤掉statictransient修饰的字段:

// ReflectiveTypeAdapterFactory.java
private boolean excludeField(Field field, boolean serialize) {
    // 获取字段修饰符
    int modifiers = field.getModifiers();
    // 过滤掉static和transient字段
    if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) {
        return true;
    }
    
    // 处理@Expose注解
    if (exposeAnnotationPolicy != null) {
        Expose expose = field.getAnnotation(Expose.class);
        if (expose == null) {
            return exposeAnnotationPolicy == AnnotationPolicy.EXPOSE;
        }
        if (serialize ? !expose.serialize() : !expose.deserialize()) {
            return true;
        }
    }
    
    // 应用自定义排除策略
    if (excludeField(field, serialize, exclusionStrategies)) {
        return true;
    }
    
    return false;
}

2.2 自定义排除策略

开发者可以通过实现ExclusionStrategy接口创建自定义过滤规则:

public interface ExclusionStrategy {
    // 序列化时是否排除该类型
    boolean shouldSkipClass(Class<?> clazz);
    
    // 序列化时是否排除该字段
    boolean shouldSkipField(FieldAttributes f);
}

注册自定义策略:

Gson gson = new GsonBuilder()
    .addSerializationExclusionStrategy(new CustomExclusionStrategy())
    .addDeserializationExclusionStrategy(new CustomExclusionStrategy())
    .create();

2.3 排除策略的应用流程

在字段绑定阶段,Gson会应用排除策略:

// ReflectiveTypeAdapterFactory.java
private Map<String, BoundField> getBoundFields(
    Gson context, TypeToken<?> type, Class<?> raw) {
    Map<String, BoundField> result = new LinkedHashMap<>();
    if (raw.isInterface()) {
        return result;
    }
    
    // 获取父类的TypeToken
    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;
            }
            
            // 字段绑定逻辑...
        }
        
        // 处理父类
        type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
        raw = type.getRawType();
    }
    return result;
}

三、命名策略实现原理

3.1 内置命名策略

Gson提供了多种内置命名策略:

3.1.1 IDENTITY(默认策略)

直接使用Java字段名作为JSON字段名:

public class IdentityNamingPolicy implements FieldNamingStrategy {
    @Override
    public String translateName(Field f) {
        return f.getName();
    }
}
3.1.2 LOWER_CASE_WITH_UNDERSCORES(蛇形命名)

将Java字段名转换为小写并使用下划线分隔:

public class LowerCaseWithUnderscoresNamingPolicy implements FieldNamingStrategy {
    @Override
    public String translateName(Field f) {
        return separateCamelCase(f.getName(), "_").toLowerCase();
    }
    
    private String separateCamelCase(String name, String separator) {
        StringBuilder translation = new StringBuilder();
        for (int i = 0; i < name.length(); i++) {
            char character = name.charAt(i);
            if (Character.isUpperCase(character) && translation.length() != 0) {
                translation.append(separator);
            }
            translation.append(character);
        }
        return translation.toString();
    }
}
3.1.3 UPPER_CAMEL_CASE(大驼峰命名)

将Java字段名转换为大驼峰格式:

public class UpperCamelCaseNamingPolicy implements FieldNamingStrategy {
    @Override
    public String translateName(Field f) {
        return modifyString(f.getName());
    }
    
    private String modifyString(String fieldName) {
        StringBuilder modified = new StringBuilder();
        boolean nextUpperCase = false;
        for (int i = 0; i < fieldName.length(); i++) {
            char currentChar = fieldName.charAt(i);
            if (currentChar == '_') {
                nextUpperCase = true;
            } else if (nextUpperCase) {
                modified.append(Character.toUpperCase(currentChar));
                nextUpperCase = false;
            } else {
                modified.append(Character.toLowerCase(currentChar));
            }
        }
        return capitalizeFirstLetter(modified.toString());
    }
    
    private String capitalizeFirstLetter(String s) {
        if (s.length() == 0) return s;
        char first = s.charAt(0);
        if (Character.isUpperCase(first)) return s;
        else return Character.toUpperCase(first) + s.substring(1);
    }
}

3.2 @SerializedName注解

@SerializedName注解允许为字段指定自定义JSON名称:

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

使用示例:

public class User {
    @SerializedName("user_name")
    private String username;
    
    @SerializedName(value = "reg_time", alternate = {"registration_time"})
    private Date registerTime;
}

3.3 命名策略的应用流程

在字段绑定阶段,Gson会应用命名策略:

// ReflectiveTypeAdapterFactory.java
private Map<String, BoundField> getBoundFields(
    Gson context, TypeToken<?> type, Class<?> raw) {
    Map<String, BoundField> result = new LinkedHashMap<>();
    // ...
    
    for (Field field : fields) {
        // ...
        
        // 获取字段的JSON名称
        List<String> fieldNames = getFieldNames(field);
        
        // 创建BoundField并绑定到名称
        BoundField previous = null;
        for (String name : fieldNames) {
            previous = result.put(name, boundField);
            if (previous != null) {
                throw new IllegalArgumentException(
                    "Duplicate field name: " + name + " for type " + declaredType);
            }
        }
    }
    
    return result;
}

private List<String> getFieldNames(Field field) {
    // 检查是否存在@SerializedName注解
    SerializedName annotation = field.getAnnotation(SerializedName.class);
    if (annotation == null) {
        // 应用命名策略
        String name = fieldNamingPolicy.translateName(field);
        return Collections.singletonList(name);
    }
    
    // 处理@SerializedName注解的主名称和备选名称
    String serializedName = annotation.value();
    String[] alternates = annotation.alternate();
    if (alternates.length == 0) {
        return Collections.singletonList(serializedName);
    }
    
    List<String> fieldNames = new ArrayList<>(alternates.length + 1);
    fieldNames.add(serializedName);
    for (String alternate : alternates) {
        fieldNames.add(alternate);
    }
    return fieldNames;
}

四、字段过滤与命名策略的协同工作

4.1 配置与使用示例

以下示例展示如何同时配置字段过滤和命名策略:

Gson gson = new GsonBuilder()
    // 设置命名策略为蛇形命名
    .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
    // 添加序列化排除策略
    .addSerializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipClass(Class<?> clazz) {
            return false;
        }
        
        @Override
        public boolean shouldSkipField(FieldAttributes f) {
            // 排除以"internal"开头的字段
            return f.getName().startsWith("internal");
        }
    })
    .create();

// 序列化对象
User user = new User("john_doe", "password123", "john@example.com");
String json = gson.toJson(user);
// 输出: {"user_name":"john_doe","email":"john@example.com"}

4.2 优先级规则

当多种过滤和命名规则同时存在时,优先级如下:

  1. @SerializedName注解:最高优先级,直接指定JSON名称
  2. @Expose注解:控制字段是否参与序列化/反序列化
  3. 自定义ExclusionStrategy:自定义过滤规则
  4. 命名策略:默认命名转换规则
  5. transient关键字:Java原生字段排除机制

五、高级应用与扩展

5.1 基于注解的高级过滤

可以通过自定义注解实现更复杂的过滤逻辑:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SensitiveData {
    boolean expose() default false;
}

// 自定义排除策略
class SensitiveDataExclusionStrategy implements ExclusionStrategy {
    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return clazz.getAnnotation(SensitiveData.class) != null;
    }
    
    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(SensitiveData.class) != null;
    }
}

5.2 动态命名策略

通过实现FieldNamingStrategy接口,可以创建动态命名策略:

public class DynamicNamingStrategy implements FieldNamingStrategy {
    private final Map<Class<?>, String> prefixMap;
    
    public DynamicNamingStrategy(Map<Class<?>, String> prefixMap) {
        this.prefixMap = prefixMap;
    }
    
    @Override
    public String translateName(Field f) {
        Class<?> declaringClass = f.getDeclaringClass();
        String prefix = prefixMap.getOrDefault(declaringClass, "");
        return prefix + f.getName();
    }
}

六、性能考量

6.1 反射开销

字段过滤和命名策略都依赖反射机制,可能带来性能开销:

  • 字段访问:通过Field.setAccessible(true)绕过访问检查
  • 注解解析:运行时解析字段上的注解
  • 命名转换:动态转换字段名称

6.2 优化建议

  1. 缓存策略:对于频繁使用的命名策略,缓存转换结果
  2. 减少反射:尽量使用编译时生成的代码替代反射
  3. 优先使用注解@SerializedName@Expose比自定义策略效率更高
  4. 避免复杂逻辑:自定义策略中的复杂逻辑会增加处理时间

七、总结与展望

7.1 核心机制总结

Gson的字段过滤和命名策略通过以下机制实现:

  • 排除策略链:多种排除策略按优先级组合应用
  • 命名转换:通过策略模式实现灵活的名称映射
  • 注解驱动:提供@SerializedName@Expose等注解简化配置
  • 反射机制:通过反射获取字段信息并应用过滤和命名规则

7.2 未来发展方向

  1. Kotlin支持增强:更好地支持Kotlin的属性和注解
  2. 编译时代码生成:减少运行时反射开销
  3. 更灵活的配置方式:支持更丰富的声明式配置
  4. 与数据验证集成:结合字段过滤实现数据验证功能
  5. 性能优化:进一步减少反射调用,提高处理效率

通过不断优化和扩展,Gson将继续为开发者提供高效、灵活的JSON处理解决方案,满足日益复杂的应用需求。