fastjson2 Mixin 机制详解:给任意类「注入」序列化注解

📅 发布时间:2026/7/11 5:48:31 👁️ 浏览次数:
fastjson2 Mixin 机制详解:给任意类「注入」序列化注解
1. Mixin 是什么Mixin 是 fastjson2 的「注解外部注入」机制把A 类mixin上的注解当作 B 类目标类的注解来用运行时织入不改目标类源码。一句话让一个你没动过的类表现得像被你加了注解一样。// mixin一个空壳类只承载注解staticclassExternalUserMixin{// 字段/方法体无所谓fastjson2 只读注解}// 注册把 ExternalUserMixin 的注解贴到不可改的 ExternalUser 上JSON.mixIn(ExternalUser.class,ExternalUserMixin.class);// 此后 ExternalUser 的序列化/反序列化就像自带了 mixin 上的注解JSON.parseObject(json,ExternalUser.class);灵感来自 Jackson 的JsonMixInfastjson2 在 API 和语义上与之兼容。2. 为什么需要它fastjson2 的序列化/反序列化行为是注解驱动的——JSONType、JSONField、JSONCreator决定一个类怎么被读写。但现实里有大量类你改不了源码来源例子JDK 类java.time.*、java.util.UUID、java.awt.Point第三方库agentscope 的GenerateOptions、AWS SDK 的各种XxxRequestLombok 生成物Builder生成的 Builder 类不在源码里无法标注已交付/冻结的类不允许改动、或改动成本太高Mixin 让你在一个你能改的空壳类上写注解再注册到目标类上从而在不碰目标源码的前提下定制它的序列化行为。3. API3.1 核心 API// 全局注册最常用JSON.mixIn(Class?target,Class?mixin);// 查询当前注册的 mixinObjectReaderProviderproviderJSONFactory.getDefaultObjectReaderProvider();Class?mixinprovider.getMixIn(target);// 清理所有 mixin 注册全局provider.cleanupMixIn();// 单个注册等价于 JSON.mixIn 的底层provider.mixIn(target,mixin);签名2.0.62 实测JSON.mixIn(Class?, Class?) // static ObjectReaderProvider.mixIn(Class, Class) // instance ObjectReaderProvider.getMixIn(Class) : Class ObjectReaderProvider.cleanupMixIn() // 清空 mixInCache3.2 注册时机ObjectReader 是按类缓存的。这里有个好消息mixIn在写入mixInCache的同时会主动把目标类从两份 reader 缓存cache、cacheFieldBased里移除因此即便目标类此前已被反序列化过注册 mixin 后下一次读取就会按新 mixin 重建 reader无需手动清缓存或重启JSON.parseObject(json,ExternalUser.class);// 此时走默认行为JSON.mixIn(ExternalUser.class,ExternalUserMixin.class);// 注册即作废上面缓存的 readerJSON.parseObject(json,ExternalUser.class);// 这次按 mixin 行为执行唯一边界情况如果目标类是另一个已缓存的 reader 的内嵌字段类型例如宿主类Holder有个ExternalUser字段Holder的 reader 构建时已内联了旧的ExternalUserreader那个外层 reader持有旧引用、不会被本次注册波及。所以生产上仍建议在应用启动阶段集中注册规避这类时序细节见 6.1。3.3 全局状态警示JSON.mixIn操作的是全局ObjectReaderProvider影响整个 JVM 的 fastjson2。这带来两点注意作用域是整个 JVM所有使用 fastjson2 的代码都受影响包括其他模块、其他线程。测试隔离在测试里注册 mixin 后要用cleanupMixIn()还原避免污染其它用例见mixinRescueUnmodifiableBuilder用例的finally块。4. Mixin 能做什么远不止 builderMixin 是一个通用注解注入机制——你在 mixin 上放什么注解目标类就得到什么行为。下面列出常见用途。核心认知mixin 的价值不在某个单一场景而在于「注解与类解耦」这个能力本身。4.1 给不可改的 builder 类注入 builder 支持一个外部、加不了注解的 builder-only 不可变类全final字段、仅 builder 构造、无无参构造、无 setter原本直接反序列化会 NPE。先看这个改不了的目标类长什么样等价于 agentscope 的GenerateOptions// 目标类外部类加不了任何 fastjson2 注解staticclassExternalUser{privatefinalStringname;privatefinalintage;ExternalUser(Builderbuilder){this.namebuilder.name;this.agebuilder.age;}publicStringgetName(){returnname;}publicintgetAge(){returnage;}publicstaticBuilderbuilder(){returnnewBuilder();}/** Lombok 风格 fluent builder方法名即字段名、无 with 前缀、无 JSONField。 */publicstaticclassBuilder{privateStringname;privateintage;publicBuildername(Stringname){this.namename;returnthis;}publicBuilderage(intage){this.ageage;returnthis;}publicExternalUserbuild(){returnnewExternalUser(this);}}}直接JSON.parseObject(json, ExternalUser.class)会走默认的无参构造路径、反射时传入nullbuilder →构造器内builder.xxx触发 NPE。用 mixin 注入JSONType(builder...)即可救活已实测JSONType(builderExternalUser.Builder.class,deserializeFeaturesJSONReader.Feature.SupportSmartMatch)staticclassExternalUserMixin{}// 空壳只承载注解JSON.mixIn(ExternalUser.class,ExternalUserMixin.class);ExternalUseruserJSON.parseObject(json,ExternalUser.class);// ✓ 不再 NPE注解里两个要点builder ExternalUser.Builder.class让反序列化改走 builder 路径而非默认坏掉的无参构造路径deserializeFeatures SupportSmartMatch因为 Builder 的 fluent 方法name()/age()无with前缀必须类级开 SmartMatch 才会被识别为字段。SmartMatch必须写在类级——运行时 vararg 传同样的 feature无效builder 的字段发现发生在 ObjectReader 构建期只读类级注解。这是 mixin 最「惊艳」的用法但只是它能力的一个切面。4.2 重命名字段第三方类字段叫userName你的 JSON 契约要求用name。改不了第三方类用 mixinstaticclassUserMixin{JSONField(namename)publicStringgetUserName(){returnnull;}// 序列化userName → 输出 nameJSONField(namename)publicvoidsetUserName(Stringname){}// 反序列化name → 填入 userName}JSON.mixIn(ThirdPartyUser.class,UserMixin.class);mixin 本身没有字段、只靠方法签名把注解投射到目标类的对应成员注解标在 getter 上管序列化字段名标在 setter 上管反序列化字段名故改名要getter 和 setter 都标否则只单向生效。注这是 mixin 场景的写法。fastjson2 对自有类默认是字段反射驱动的直接标字段即可不必绕 getter/setter。4.3 忽略字段第三方类里有敏感/内部字段如password、internalId不想进 JSONJSONType(ignores{password,internalId})staticclassUserMixin{}JSON.mixIn(ThirdPartyUser.class,UserMixin.class);也可用字段级JSONField(serialize false)/(deserialize false)做单向忽略。4.4 自定义序列化器 / 反序列化器给一个无法加JSONType(serializer...)的类挂上自定义 codecJSONType(serializerMoneySerializer.class,deserializerMoneyDeserializer.class)staticclassMoneyMixin{}JSON.mixIn(Money.class,MoneyMixin.class);适合金额分↔元转换、枚举的扩展码、复数等需要特殊读写逻辑又改不了目标类的场景。4.5 指定反序列化构造器JSONCreator一个只有全参构造、没有无参构造、又不是 builder 的不可变类典型如各种值对象可以用 mixin 标注哪个构造器用于反序列化并用JSONField(name...)把 JSON 字段绑到构造器参数staticclassAddressMixin{JSONCreatorpublicAddressMixin(JSONField(nameaddress)Stringaddress){}}JSON.mixIn(Address.class,AddressMixin.class);构造器参数靠JSONField(name...)与 JSON 字段对应mixin 的构造器签名需与目标类的对应构造器一致fastjson2 按签名把注解投射过去。本节为用法示意尚未像 4.1 那样补实测用例。4.6 统一命名策略第三方类字段是 camelCase你想统一输出 snake_caseJSONType(namingPropertyNamingStrategy.SnakeCase)staticclassUserMixin{}JSON.mixIn(ThirdPartyUser.class,UserMixin.class);// userName → user_name, createdAt → created_at4.7 字段排序与格式JSONType(orders{id,name,createTime})// 输出字段顺序staticclassUserMixin{}JSONType(formatyyyyMMdd)// 日期格式类级staticclassEventMixin{}4.8 用途小结用途mixin 上的注解解决的问题救活 builder-only 不可变类JSONType(builder...)直接反序列化 NPE重命名字段JSONField(name...)JSON 契约与类字段名不一致忽略字段JSONType(ignores...)/JSONField(serialize...)敏感/内部字段不外泄自定义 codecJSONType(serializer/deserializer...)特殊读写逻辑指定构造器JSONCreatorJSONField(name...)无无参构造的不可变值对象命名策略JSONType(naming...)camelCase ↔ snake_case字段排序/格式JSONType(orders...)/format控制输出5. 设计精髓注解与类解耦理解 mixin 的关键是认识到一个常被忽略的事实注解不一定要写在被注解的类上。传统写法里JSONField贴在字段上、JSONType贴在类上——注解和类绑死。这带来一个隐含限制「只有类的作者能决定它的序列化行为」。第三方类作者没加注解你就只能接受 fastjson2 的默认行为。Mixin 打破了这个限制把注解的两重身份拆开身份传统Mixin 后定义注解写在哪写在目标类上写在 mixin 空壳类上绑定注解和谁关联编译期固定运行时通过mixIn关联由此带来两个工程价值开闭原则对目标类第三方/冻结的序列化行为做扩展而不修改它。关注点分离领域类保持纯粹只建模业务序列化策略集中在 mixin 里。注意「绑定」不等于「多套配置并行」mixInCache是「每个目标类一条记录」的全局 map后注册会覆盖前者见 3.3 的全局性警示做不到「A 模块 camelCase、B 模块 snake_case」同时并存。要按环境隔离需自建独立的JSONFactory/ObjectReaderProvider已超出本文的JSON.mixIn范畴。和「字段级JSONField(deserializeUsing...)手写 reader」的区别手写 reader 是字段级指定一个完整的ObjectReader强耦合在宿主类的字段上mixin 是类级注入注解由 fastjson2 原生路径执行宿主类无感。两者可互补复杂场景用 reader 精细控制常规定制用 mixin 声明式解决。6. 注意事项与最佳实践全局状态JSON.mixIn影响整个 JVM。生产环境建议在应用启动阶段集中注册规避「目标类作为外层 reader 内嵌字段」这类时序细节见 3.2。注册时机mixIn会自动作废目标类已缓存的 reader故对目标类本身无严格先后要求但仍建议集中在启动阶段注册原因见 3.2 的边界情况。getter/setter 对齐字段改名等配置mixin 靠方法签名投射序列化管 getter、反序列化管 setter两边都要标。测试清理测试中注册后用cleanupMixIn()还原避免污染其他用例。mixin 类本身不参与序列化它是纯注解载体字段/方法体无关紧要方法签名要和目标类对应fastjson2 靠签名匹配把注解「投射」到目标类的对应成员上。可替代性能直接在目标类加注解时优先直接加mixin 是「改不了才用」的手段不要为了用 mixin 而 mixin直接注解更直观、无全局状态。END