Java静态final实例层次结构反序列化

huangapple go评论112阅读模式
英文:

Java Static final instances hierarchy deserialization

问题

我有3个类:

    public class Base {
      private int code;
      private static final Map<String, Map<Integer, Base>> ALL_MAPS = 
      new HashMap<>();

     protected Base(int code) {
      this.code = code;
      getConstMap(this.getClass()).put(code, this);
    }
   
    protected Map<Integer, Base> getConstMap(Class<?> clazz) {
      Map<Integer, Base> res;
      if ((res = ALL_MAPS.get(clazz.getName())) == null) {
         res = new HashMap<>();
         ALL_MAPS.put(clazz.getName(), res);
      }
      return res;
    }
    //...
    }

    public class AConstants extends Base {
       public static final AConstants A_1 = new AConstants(1, 
     "sample1");
     public static final AConstants A_2 = new AConstants(2, "sample2");
       //...
       private final String text;
       private AConstants(int code, String text) {
          super(code);
          this.text = text;
       }
    }

    public class BConstants extends Base {
       public static final BConstants B_1 = new BConstants(1, 
    "sample1", "moreText");
       public static final BConstants B_2 = new BConstants(2, 
    "sample2", "moreText");
       //...
       private final String text;
       private final String moreText;
       private BConstants(int code, String text, String moreText) {
          super(code);
          this.text = text;
          this.moreText = moreText;
       }
    }

我想要实现的是在Base类中有一个反序列化(和序列化)静态final实例的基础方法。显然,由于在编译期间无法从静态上下文中引用泛型类型(另外我显然无法获取CONST的类),我无法这样做:

    @JsonCreator
    public static CONST deserialize(@JsonProperty("code") int code) {
        // 应该获取CONST的类并根据code检索静态final实例
        return ALL_MAPS.get(CONST.class).get(code);
    }
    
    @JsonValue
    public Integer serialize() {
        // 返回所需类的静态final实例的代码
        return getKeyByValue(ALL_MAPS.get(CONST.class), this);
    }

是否有办法使用Jackson实现这一点?如果不行,我该如何编写一个自定义的序列化程序/反序列化程序?

英文:

I have 3 classes:

public class Base {
private int code;
private static final Map<String, Map<Integer, Base>> ALL_MAPS = 
new HashMap<>();
protected Base(int code) {
this.code = code;
getConstMap(this.getClass()).put(code, this);
}
protected Map<Integer, Base> getConstMap(Class<?> clazz) {
Map<Integer, Base> res;
if ((res = ALL_MAPS.get(clazz.getName())) == null) {
res = new HashMap<>();
ALL_MAPS.put(clazz.getName(), res);
}
return res;
}
//...
}
public class AConstants extends Base {
public static final AConstants A_1 = new AConstants(1, 
"sample1");
public static final AConstants A_2 = new AConstants(2, "sample2");
//...
private final String text;
private AConstants(int code, String text) {
super(code);
this.text = text;
}
}
public class BConstants extends Base {
public static final BConstants B_1 = new BConstants(1, 
"sample1", "moreText");
public static final BConstants B_2 = new BConstants(2, 
"sample2", "moreText");
//...
private final String text;
private final String moreText;
private BConstants(int code, String text, String moreText) {
super(code);
this.text = text;
this.moreText = moreText;
}
}

What I want to achieve is having a base method for deserializing (and serealizing) static final instances in the Base class. Obviously, I can't do smth like this due to generic type being unable to be referenced from static context during compilation (plus I obviously can't get the class of CONST):

@JsonCreator
public static CONST deserialize(@JsonProperty("code") int code) {
// Should get the class of CONST and retrieve static final instance by code
return ALL_MAPS.get(CONST.class).get(code);
}
@JsonValue
public Integer serialize() {
// Return code for static final instance of the necessary class
return getKeyByValue(ALL_MAPS.get(CONST.class), this);
}

Is there a way to achieve this with Jackson? Or if not, how do I write a custom serializer/deserializer for this?

答案1

得分: 0

如果有人有类似的用例,以下是我是如何做的。 Base 类基本上是所有 static final 实例(或者我应该称它们为 constants)的占位符 - ALL_MAPS 包含键,作为扩展 Base 的类的名称,以及值 - 一个 Map,键为实例的代码,值为实例本身。当 Base 的子类加载到 JVM 中时,其中的所有 constants(因为它们是 static)都会放入 ALL_MAP。因此,在反序列化时,除了将类加载到内存中之外,我只需要一个 constant 的类和一个指向它的键(code)。我可以在自定义反序列化器中像这样获取类:https://github.com/FasterXML/jackson-databind/issues/2711

之后,剩下的就是将类加载到内存中 - 我通过反射来实现,通过在具有 null 值的任何静态字段上调用 get 方法来实现,因为我根本不想允许创建 const 子类的实例。然后 - 读取包含 code 的 JSON 节点并获取 constant

作为一种说明:在 Base 类上,我使用了 @JsonDeserialize(using = BaseConstDeserializer.class),其中 BaseConstDeserializer 是自定义的反序列化器。

这是反序列化器的代码:

public class BaseConstDeserializer<CONST extends BaseConst> extends StdDeserializer<CONST> implements ContextualDeserializer {
    private final Class<CONST> deserializedFieldClazz;

    protected BaseConstDeserializer(Class<CONST> deserializedFieldClazz) {
        super(BaseConst.class);
        this.deserializedFieldClazz = deserializedFieldClazz;
    }

    @Override
    @SuppressWarnings("unchecked")
    public CONST deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException {
        JsonNode node = jp.getCodec().readTree(jp);
        Integer code = JsonUtils.readSingleNode(node, "code", JsonNode::asInt, ValidUtils::isInteger);
        if (code != null) {
            BaseConst.initConst(deserializedFieldClazz);
            return (CONST) BaseConst.getConstAndClearMap(code, deserializedFieldClazz);
        }
        return null;
    }

    @Override
    @SuppressWarnings("unchecked")
    public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty property) throws JsonMappingException {
        Class<CONST> rawClass = (Class<CONST>) property.getType().getRawClass();
        return new BaseConstDeserializer<>(rawClass);
    }
}

我唯一能猜到的缺点是,我需要自己管理内存使用情况 - 每次反序列化 JSON 时清除映射。我知道在 Spring 中可以使用 @Scope 来将 bean 实例的 "生命周期" 设置为请求的范围,但在这种情况下,我根本不喜欢创建和拥有这些实例。这可能是一种不正统的方法,但它适合我的用例,我喜欢这个解决方案。
无论如何,如果有人看到其他潜在的优缺点,我愿意听取意见。

英文:

In case someone has a similiar use case, here's how I did it. Base class is basically a placeholder for all the static final instances (or should I call them constants) - ALL_MAPS contains key as the name of the class which extends Base and as of value - a Map of keys - code of the instance and the instance itself as value. When the subclass of Base is loaded into the JVM, all the constants in it (as they are static) are placed in the ALL_MAP. So when deserializing, apart from loading the class into the memory, I only need a class of the constant and a key to it (code). I can get the class in the custom deserializer just like this: https://github.com/FasterXML/jackson-databind/issues/2711

After that, all is left is to load the class into memory - I achieved it via reflection by calling get method on any static field with null value as I don't want to allow creating instances of const subclasses at all. And then - to read the json node which contains the code and get the constant.

As a note: on the Base class I used @JsonDeserialize(using = BaseConstDeserializer.class) where BaseConstDeserializer is the custom deserializer.

Here is the code of the deserializer:

public class BaseConstDeserializer&lt;CONST extends BaseConst&gt; extends StdDeserializer&lt;CONST&gt; implements ContextualDeserializer {
private final Class&lt;CONST&gt; deserializedFieldClazz;
protected BaseConstDeserializer(Class&lt;CONST&gt; deserializedFieldClazz) {
super(BaseConst.class);
this.deserializedFieldClazz = deserializedFieldClazz;
}
@Override
@SuppressWarnings(&quot;unchecked&quot;)
public CONST deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = jp.getCodec().readTree(jp);
Integer code = JsonUtils.readSingleNode(node, &quot;code&quot;, JsonNode::asInt, ValidUtils::isInteger);
if (code != null) {
BaseConst.initConst(deserializedFieldClazz);
return (CONST) BaseConst.getConstAndClearMap(code, deserializedFieldClazz);
}
return null;
}
@Override
@SuppressWarnings(&quot;unchecked&quot;)
public JsonDeserializer&lt;?&gt; createContextual(DeserializationContext deserializationContext, BeanProperty property) throws JsonMappingException {
Class&lt;CONST&gt; rawClass = (Class&lt;CONST&gt;) property.getType().getRawClass();
return new BaseConstDeserializer&lt;&gt;(rawClass);
}
}

The only drawback I can guess here is that I need to manage the memory usage by myself - by clearing the map every time I deserialized json. I know that in Spring I can use @Scope to set the "lifespan" of an instance of bean to the scope of request but I don't like creating and having those instances in this case at all. This is propably an unorthodox approach, but it serves my case and I like the solution.
Anyway, if someone sees any other potentials cons and pros of it, I'd like to hear.

huangapple
  • 本文由 发表于 2023年7月3日 22:34:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76605751.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定