Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mix-In Annotation实现思路 #1

Open
Omega-Ariston opened this issue Aug 20, 2019 · 3 comments
Open

Mix-In Annotation实现思路 #1

Omega-Ariston opened this issue Aug 20, 2019 · 3 comments
Assignees
Labels
enhancement New feature or request

Comments

@Omega-Ariston
Copy link
Owner

Omega-Ariston commented Aug 20, 2019

关于实现Mix-In Annotation的想法:

首先需要一个用于存储Target Class -> Mix-In Class映射关系的数据结构(类似Jackson的objectMapper),目前想到两个方案:

  1. SerializeConfigParserConfig中各自定义一个Map类型的成员变量,并提供相应的公共函数供用户向其中插入/删除映射关系。

  2. JSON类中定义一个Map类型的全局静态单例类变量,并提供相应的公共函数供用户向其中插入/删除映射关系。

这样做的好处是:

  • 不用修改JSON类中现有的静态接口方法签名
  • 采用方案1时可以通过使用SerializerConfig/ParserConfig中现有的静态单例或新建SerializerConfig/ParserConfig对象两种方式分别实现全局配置和单次配置的效果。
  • 采用方案2时无需对序列化和反序列化分别做配置。

大致思路:

在序列化时先尝试从Map中获取相应的MixIn类,如果有事先配置好的MixIn类,则优先将MixIn类中定义的注解信息写入到SerializeConfig的成员属性以及生成的SerializeBeanInfo -> ObjectSerializer中。(个人理解是序列化需要的注解信息都写入到了ObjectSerializer里)

在反序列化时先尝试从Map中获取相应的MixIn类,如果有事先配置好的MixIn类,则通过asm/反射将MixIn类中定义的注解信息写入生成的JavaBeanInfo -> ObjectDeserializer中(个人理解是反序列化需要的所有注解信息最终都写入到了ObjectDeserializer里)。

思考:是否可以通过重载TypeUtils.getAnnotations方法来统一化所有场景(获取类注解、属性注解、方法注解)的注解获取函数?这么一来,只需要在这些重载的方法内做Target类与MixIn类的注解合并即可。

使用示例:

现在有一个Java类Rectangle(Target Class),定义如下

public final class Rectangle {
    final private int w, h;
    public Rectangle(int w, int h) {
      this.w = w;
      this.h = h;
    }
    public int getW() { return w; }
    public int getH() { return h; }
    public int getSize() { return w * h; }
}

其对应的MixIn类(Mix-In Class),定义如下:

abstract class MixIn {
  MixIn(@JSONField(name="width") int w, @JSONField(name="height") int h) { }

  @JSONField(name="width") abstract int getW(); // rename property
  @JSONField(name="height") abstract int getH(); // rename property
  @JSONField(serialize=false) abstract int getSize(); // we don't need it!
  
}

使用场景如下:

方案1:
// 序列化
SerializeConfig serializeConfig = new SerializeConfig();
serializeConfig.addMixInAnnotations(Rectangle.class, MixIn.class);

Rectangle rectangle = new Rectangle(5, 10);
String str = JSON.toJSONString(rectangle, serializeConfig);

System.out.println(str); // {"width":5, "height":10}

//反序列化
ParserConfig parserConfig = new ParserConfig();
parserConfig.addMixInAnnotations(Rectangle.class, MixIn.class);

Rectangle rectangle2 = (Rectangle)JSON.parse(str, parserConfig);

System.out.println(rectangle2.getW()); // 5
System.out.println(rectangle2.getH()); // 10
方案2:
JSON.addMixInAnnotations(Rectangle.class, MixIn.class); // 只需配置一次

// 序列化
Rectangle rectangle = new Rectangle(5, 10);
String str = JSON.toJSONString(rectangle);

System.out.println(str); // {"width":5, "height":10}

//反序列化
Rectangle rectangle2 = (Rectangle)JSON.parse(str);

System.out.println(rectangle2.getW()); // 5
System.out.println(rectangle2.getH()); // 10

存疑:

  1. 关于Target Class与Mix-In Class中注解的优先级,有以下三种方案(个人倾向第1种):

    1. 舍弃Target Class中的定义,只取Mix-In Class中的定义
    2. 取二者并集,冲突的部分以Mix-In Class中的定义为准
    3. 取二者并集,冲突的部分以Target Class中的定义为准
  2. 是否需要支持父子Mix-In Class注解继承?(类似于现有的Target Class父子注解继承)

参考资料:
https://github.com/FasterXML/jackson-docs/wiki/JacksonMixInAnnotations

@Omega-Ariston Omega-Ariston added the enhancement New feature or request label Aug 21, 2019
@Omega-Ariston Omega-Ariston self-assigned this Aug 21, 2019
@Hosuke
Copy link

Hosuke commented Aug 21, 2019

对于存储方案选择,我是想集中维护Map会好一些,写两处维护成本会提高

@Omega-Ariston
Copy link
Owner Author

Omega-Ariston commented Aug 24, 2019

这两天我试着根据方案2写了一版MixInAnnotations的实现,大致思路如下:

  1. 在JSON类中维护一个全局Map对象,用于存储Target类 -> MixIn类的映射关系,并提供相应的增删改查方法。
  2. 重载TypeUtils.getAnnotations()方法,使其除了有获取Class对象的注解功能之外,额外新增了对属性和方法的注解获取功能。
  3. 修改TypeUtils.getAnnotations()中的逻辑,使其每次在获取类、对象、方法的注解时都会检查是否存在对应的Mixin注解,如果有的话,优先使用MixIn类的注解。(这个过程中会递归对Target类和MixIn的超类中对应的信息做匹配,以支持类继承结构)
  4. 将与序列化/反序列化相关函数中获取注解的行为统一到TypeUtils.getAnnotations()上(不再是直接通过field.getAnnotations()这种形式)
  5. 每次添加Target类与MixIn类的关联时,检查IdentityHashMap中是否缓存了Target类对应的Serializer/Deserializer,如果有的话,删除(避免直接从缓存中取值而导致MixIn类对beaninfo的修改不生效)。

一句话总结就是:“在类、方法、属性获取注解时先从MixIn类中获取,并将对应信息写入到序列化/反序列化所需要的beaninfo中。”
目前已经实现了前4步,并且经测试能够实现jackson官方文档中的功能。
在进行开发之前,我沿用了jackson中与MixIn相关的测试用例,在其基础上做了一些修改(由于jackson与fastjson的使用方法差异)。
现在在第5步上碰到了点问题,因为序列化和反序列化对应的IdentityHashMap是以私有的方式分别存储在SerializeConfig和ParserConfig中,想要从中移除缓存会有些困难(考虑到存储Target Class -> MixIn Class的Map位于JSON中)。

@Omega-Ariston
Copy link
Owner Author

后面证实第5步的问题不应该被考虑,因为这一步不做,只会影响到一个场景:用户先使用了序列化/反序列化功能,然后进行了mixin类的配置,之后又进行了序列化/反序列化。而按照jackson官方说法,最佳实践应该是先配置后使用,而不是使用后才配置。
参考链接 FasterXML/jackson-databind#2426

Omega-Ariston pushed a commit that referenced this issue May 18, 2020
Omega-Ariston pushed a commit that referenced this issue Apr 9, 2021
Omega-Ariston pushed a commit that referenced this issue Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants