【问题标题】:How to deserialize com.auth0.jwk.Jwk that does not have no-arg constructor?如何反序列化没有无参数构造函数的 com.auth0.jwk.Jwk?
【发布时间】:2021-12-16 04:52:37
【问题描述】:

我正在使用 GemFire 在我的 Spring Boot (v2.5.2) 应用程序中实现缓存。

需要缓存的类-

import org.springframework.data.annotation.Id;
import org.springframework.data.gemfire.mapping.annotation.Region;

import com.auth0.jwk.Jwk;

@Region("jwks")
public class Key {

   @Id
   private String id;

   private Jwk jwk;

   @PersistenceConstructor
   public Key(String id, Jwk jwk){
       this.id = id;
       this.jwk = jwk;
   }

   // Getters and setters for variables
}

从缓存中获取存储的实体时出现以下错误 -

org.apache.geode.SerializationException : While deserializing query result with root cause
java.lang.NoSuchMethodException: com.auth0.jwk.Jwk.<init>()
at java.lang.Class.getConstructors0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2187)
...

当 Jwk 没有无参数构造函数时,如何反序列化这个对象?

编辑 - build.gradle

implementation 'org.springframework.geode:spring-geode-starter'
implementation 'org.springframework.geode:spring-data-geode'

另外,根据建议的答案更新了构造函数,但仍然给出相同的错误。

错误导致代码 -

Optional<Key> key = this.keyRepository.findById(id);

【问题讨论】:

    标签: java spring-boot spring-data deserialization spring-data-gemfire


    【解决方案1】:

    首先,提问时请准确。

    你的Key 类甚至不是有效的JavaKey 类本身甚至无法按原样编译。

    在这种情况下,构造函数 (JsonWebKey) 是 1) 没有以类 (Key) 命名,以及 2) 不能有 2 个没有 arg 构造函数。

    我假设第二个构造函数是:

    public Key(String id, Jwk jwk) {
      this.id = id;
      this.jwk = jwk;
    }
    

    我猜是构造函数名称错误,或者可能是某些 [静态] 内部类的一部分??

    Never-the-less,假设您使用 Spring Boot for Apache Geode和 VMware Tanzu (Pivotal) GemFire),又名SBDG(请参阅here;如果您使用 SBDG,那么您应该使用!),并且由于 SBDG enables GemFire/Geode PDX serialization by default,那么 SBDG 在 Spring Data for Apache 的帮助下Geode和 VMware Tanzu GemFire),又名 SDG(在其上构建 SBDG,以及核心 Spring 框架Spring DataSpring Boot)将为您处理这些序列化问题,您无需付出额外的努力。参见对应的 SDGdocumentation

    举个例子,我写了这个test class,它驻留在这个package中,来演示一下。

    默认情况下,测试类使用 PDX 序列化 configured with Spring,使用 test configuration。基于 Spring 的配置类是hereother test configuration classes 仅通过适当的test Spring profile configured 启用。

    此测试的模型类是model 子包中的CompositeValue class

    如您所见,该类有2 primary constructors。我还声明了一个commented-out, default, public no-arg constructor,我将在下面进一步解释。

    这个CompositeValue 模型类是经过精心设计的。你会注意到它(大部分)是不可变的。

    我使用 Spring Data CrudRepository(参见 here)在 GemFire/GeodeRegion(“Values") 然后检索它 (findBy..)。 “Values”区域必须是 PARTITION 区域(请参阅 here),因为 PARTITION 区域以序列化形式存储值,在我们的例子中,PDX 序列化。

    使用 Spring 配置,test 运行并成功通过!春天正在做所有繁重的工作!

    如果您没有充分利用 Spring,那么您(很可能)会遇到问题,除非您知道自己在使用 GemFire/Geode 做什么。

    开箱即用的 GemFire/Geode PDX 序列化具有一定的局限性。请参阅documentation。具体见here

    例如,如果您使用的是 GemFire/Geode 的 ReflectionBasedAutoSerializer 类(Javadocdocumentation;我怀疑您是)而不是 Spring,那么它需要您的应用程序域对象(模型类/实体)有一个默认的、公共的无参数构造函数。

    这与不可变、有效不可变和大部分不可变的类背道而驰,因为那时您无法使用构造函数适当地初始化类,这在高并发、多线程上下文(如 GemFire/Geode)中至关重要。

    你可以通过启用我写的“gemfire”配置文件in the example test class来查看尝试使用GemFire/Geode的ReflectionBasedAutoSerializer的效果,配置为here

    没有commented-out, default, public no-arg constructor,测试将无法通过。

    使用 Apache Geode 1.13.4 时,出现以下错误:

    2021-11-01 11:53:08,720  WARN ode.pdx.internal.AutoSerializableManager: 274 - Class 
    io.stackoverflow.questions.spring.geode.serialization.pdx.model.CompositeValue 
    matched with '.*' cannot be auto-serialized due to missing public no-arg constructor.
    Will attempt using Java serialization.
    

    但是,即使使用 Java 序列化(GemFire/Geode 备份序列化策略),测试也会导致失败:

    Caused by: java.io.NotSerializableException: io.stackoverflow.questions.spring.geode.serialization.pdx.model.CompositeValue
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
        at org.apache.geode.internal.InternalDataSerializer.writeSerializableObject(InternalDataSerializer.java:2184)
        at org.apache.geode.internal.InternalDataSerializer.basicWriteObject(InternalDataSerializer.java:2058)
        at org.apache.geode.DataSerializer.writeObject(DataSerializer.java:2839)
        at org.apache.geode.internal.cache.CachedDeserializableFactory.calcSerializedSize(CachedDeserializableFactory.java:245)
        ... 60 common frames omitted
    

    好吧,java.io.NotSerializableException 被抛出是因为CompositeValue 类(故意)没有实现java.io.Serialiable 接口。

    为什么是故意的?因为你不能在你不拥有的类上实现java.io.Serializable,这在使用 3rd 方库及其类时是正确的。即使在我的情况下我们拥有CompositeValue,但我要说明一点,因为在您的情况下,您并不拥有Jwk

    因此,我们不仅不能(大部分/有效地)使用不可变类,我们也不能依赖于 GemFire/Geode 中的默认序列化机制。

    当然,我们可以通过实现自定义PdxSerializer 来处理这个问题,这是documentation 中的第二个策略(在“Procedure”下,第1 步,“Serializing your Domain Object with a PdxSerializer”)。

    如果我们在我编写的示例测试中再次将活动的 Spring 配置文件 change 设置为“gemfire-custom-pdxserializer”,那么测试将通过。

    但是,它的价格很高!请参阅necessary configuration 以进行此安排。

    在我们的例子中,我们只有 1 个这样的模型/实体类来构建自定义 PdxSerializer 。但是,想象一下,如果我们有数百个类要处理。

    更糟糕的是,GemFire/Geode 只允许将单个 PdxSerializer 注册到 Singleton GemFire/Geode 缓存,这意味着您只能拥有 1 个。现在您必须依赖 Composite Software Design Pattern 来组合多个 @ 987654379@ 处理所有需要序列化的应用程序域模型类型所必需的。虽然这很优雅,但您必须为每个应用程序模型/实体类型构建一个自定义 PdxSerializer。当然,您可以将所有类型处理融入到 1 个PdxSerializer 实现中,但这会很快变得丑陋!

    最后,您的应用程序模型/实体类型可以实现 GemFire/Geode 的 PdxSerializable 接口 (Javadoc)。这并不比java.io.Serializable 好,并且(再次)不适用于您不拥有的类型。它还将您的应用程序与 GemFire/Geode 耦合,这就是我没有演示这种方法的原因,因为它应该被视为一种反模式。

    使用 SDG 的 MappingPdxSerializer,SBDG 会为您自动配置(默认情况下),您无需执行上述任何操作。 SBDG 默认自动配置 PDX(不需要 SDG @EnablePdx 注释),并且对您的应用程序域对象(模型)/实体类没有特殊要求。

    但是,如果您的实体类中有超过 1 个 构造函数,那么您需要指定 1 个构造函数作为主要的持久性构造函数。在CompositeValue 类中,这个constructor 被指定为使用Spring Data 的@PersistenceConstructor 注释的主要持久性构造函数,SDG 的MappingPdxSerializer 在反序列化和构造应用程序域对象模型类型时会考虑到这一点。

    如果你的类中只有 1 个构造函数,那么你甚至不需要在唯一的构造函数上声明 @PersistenceConstructor 注释。也就是说,如果CompositeValue 中的other constructor 不存在,那么这个constructor 上的@PersistenceConstructor 注释就没有必要了。 SD[G] 可以解决。

    出于学习目的,请随意使用我的示例测试。

    【讨论】:

    • 我在参数化构造函数上添加了@PersistenceConstructor 注释,但这并没有解决问题。还有什么可以做的吗?
    • 好吧,如果您使用的是 GemFire/Geode ReflectionBasedAutoSerializer,那么 SD 中的 @PersistentencConstructor 注释当然不起作用。 Spring 注释仅在 SDG @MappingPdxSerializer 起作用时才有效。
    • 您在使用 SBDG 吗?你是如何为缓存配置序列化的?
    • 我已经在问题中添加了依赖项。请参考他们。我没有使用ReflectionBasedAutoSerializer。目前,没有明确的反序列化设置。
    • 我已经添加了我当前使用的构造函数代码以及获取它的引发错误的代码 - org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.auth0.jwk.Jwk using constructor NO_CONSTRUCTOR with arguments connection with root cause java.lang.NoSuchMethodException: com.auth0.jwk.Jwk.&lt;init&gt;()
    猜你喜欢
    • 2021-01-11
    • 1970-01-01
    • 1970-01-01
    • 2015-12-23
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多