gpt4 book ai didi

scala - AWS Lambda处理程序抛出带有Scala泛型的ClassCastException

转载 作者:行者123 更新时间:2023-12-04 18:00:52 24 4
gpt4 key购买 nike

我正在使用它们的POJO handler构建AWS lambda函数,但是通过RequestHandler接口进行抽象会导致擦除类型。发生这种情况时,AWS无法转换为我的lambda函数的输入类型:

java.util.LinkedHashMap cannot be cast to com.amazonaws.services.lambda.runtime.events.SNSEvent: java.lang.ClassCastException
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.amazonaws.services.lambda.runtime.events.SNSEvent


以下代码在上载到AWS时适用:

import com.amazonaws.services.lambda.runtime._
import com.amazonaws.services.lambda.runtime.events.SNSEvent

// Only working version
class PojoTest1 extends Handler1[SNSEvent]{
override def handleRequest(input: SNSEvent, context: Context): Unit =
println(s"message: ${input.getRecords.get(0).getSNS.getMessage}")
}

trait Handler1[Event] extends RequestHandler[Event, Unit]{
override def handleRequest(input: Event, context: Context): Unit
}


现在,因为我正在使用Scala,所以我将抽象化具有通用特征的Java RequestHandler。以下是无法正常工作的缩小示例:

// Doesn't work
class PojoTest2 extends Handler2[SNSEvent]{
override def act(input: SNSEvent): Unit =
println(s"message: ${input.getRecords.get(0).getSNS.getMessage}")
}

trait Handler2[Event] extends RequestHandler[Event, Unit]{
def act(input: Event): Unit
override def handleRequest(input: Event, context: Context): Unit = act(input)
}


当我运行 javap PojoTest1.class时,这是使一切正常工作的方法:

public void handleRequest(com.amazonaws.services.lambda.runtime.events.SNSEvent, com.amazonaws.services.lambda.runtime.Context);


当我运行 javap PojoTest2.class时,您可以从该签名中看到 SNSEvent的类型已被擦除为 Object

public void handleRequest(java.lang.Object, com.amazonaws.services.lambda.runtime.Context);


这看起来与 SI-8905中描述的问题完全相同。不幸的是,发布的解决方法似乎也不起作用:

// Doesn't work
abstract class Handler3[T] extends Handler2[T]

class PojoTest3 extends Handler3[SNSEvent]{
override def act(input: SNSEvent): Unit =
println(s"message: ${input.getRecords.get(0).getSNS.getMessage}")
}


即使直接扩展抽象类也不会产生更好的结果:

// Doesn't work
class PojoTest4 extends Handler4[SNSEvent]{
override def act(input: SNSEvent): Unit =
println(s"message: ${input.getRecords.get(0).getSNS.getMessage}")
}

abstract class Handler4[Event] extends RequestHandler[Event, Unit] {
def act(input: Event): Unit
override def handleRequest(input: Event, context: Context): Unit = act(input)
}


当我在任何无法使用的类上使用 javap时,仍然会获得与擦除类型相同的方法签名。

我正在使用Scala 2.12.7,sbt 1.1.2和sbt-assembly 0.14.8。

我正在寻找各种工作来解决这个问题。

最佳答案

注意:我不适用于Amazon或Sun / Oracle,因此部分答案是猜测。

我认为在JVM类型擦除,AWS如何解决该问题以及您要做什么之间存在根本冲突。我也不认为您引用的错误是相关的。我认为Java的行为相同。

从AWS的角度来看,AFAIU的问题看起来是这样的:存在一系列不同类型的事件和一堆处理程序。您需要确定给定处理程序可以处理哪些事件。显而易见的解决方案是查看handleRequest方法的签名并使用参数的类型。不幸的是,JVM类型系统实际上并不真正支持泛型,因此您必须寻找最特定的方法(请参阅进一步),并假定该方法是真正的交易。

现在假设您开发了一个针对JVM的编译器(Scala或Java,Java中将有更多示例说明这不是特定于Scala的问题)。由于JVM不支持泛型,因此必须擦除类型。而且您希望将它们擦除为涵盖所有可能参数的最窄类型,因此您在JVM级别仍然是类型安全的。

对于RequestHandler.handleRequest



public O handleRequest(I input, Context context);


唯一有效的类型擦除是

public Object handleRequest(Object input, Context context);


因为 IO是未绑定的。

现在假设你做

public class PojoTest1 implements RequestHandler<SNSEvent, Void> {
@Override
public Void handleRequest(SNSEvent input, Context context) {
// whatever
return null;
}
}


此时,您说您有一个具有此非通用签名的 handleRequest方法,并且编译器必须遵守该方法。但同时,它也必须尊重您的 implements RequestHandler。因此,编译器要做的就是添加“桥接方法”,即生成逻辑上等效于

public class PojoTest1 implements RequestHandler {
// bridge-method
@Override
public Object handleRequest(Object input, Context context) {
// call the real method casting the argument
return handleRequest((SNSEvent)input, context);
}

// your original method
public Void handleRequest(SNSEvent input, Context context) {
// whatever
return null;
}
}


请注意,您的 handleRequest并不是真正覆盖 RequestHandler.handleRequest的。您也有 Handler1的事实不会改变任何东西。真正重要的是,您的非泛型类中有一个 override,因此编译器必须在您的最终类中生成一个非泛型方法(即没有擦除类型的方法)。现在,您有两种方法,AWS可以理解,采用 SNSEvent的方法是最具体的一种,因此它代表了您的真实界限。

现在假设您确实添加了通用中间类 Handler2

public abstract class Handler2<E> implements RequestHandler<E, Void> {
protected abstract void act(E input);

@Override
public Void handleRequest(E input, Context context) {
act(input);
return null;
}
}


此时返回类型是固定的,但参数仍然是未绑定的泛型。因此,编译器必须产生如下内容:

public abstract class Handler2 implements RequestHandler {
protected abstract void act(Object input);

// bridge-method
@Override
public Object handleRequest(Object input, Context context) {
// In Java or Scala you can't distinguish between methods basing
// only on return type but JVM can easily do it. This is again
// call of the other ("your") handleRequest method
return handleRequest(input, context);
}

public Void handleRequest(Object input, Context context) {
act(input);
return null;
}
}


所以现在当我们来到

public class PojoTest2 extends Handler2<SNSEvent> {
@Override
protected void act(SNSEvent input) {
// whatever
}
}


您已覆盖 act,但未覆盖 handleRequest。因此,编译器不必生成特定的 handleRequest方法,并且也不需要。它仅生成特定的 act。因此,生成的代码如下所示:

public class PojoTest2 extends Handler2 {
// Bridge-method
@Override
protected void act(Object input) {
act((SNSEvent)input); // call the "real" method
}

protected void act(SNSEvent input) {
// whatever
}
}


或者,如果将树展平并在 PojoTest2中显示所有(相关)方法,则它看起来像这样:

public class PojoTest2 extends Handler2 {

// bridge-method
@Override
public Object handleRequest(Object input, Context context) {
// In Java or Scala you can't distinguish between methods basing
// only on return type but JVM can easily do it. This is again
// call of the other ("your") handleRequest method
return handleRequest(input, context);
}

public Void handleRequest(Object input, Context context) {
act(input);
return null;
}

// Bridge-method
@Override
protected void act(Object input) {
act((SNSEvent)input); // call the "real" method
}

protected void act(SNSEvent input) {
// whatever
}
}


这两个 handleRequest方法仅接受 Object作为参数,这是AWS必须承担的。由于您没有在 handleRequest中覆盖 PojoTest2方法(并且不必这样做就继承了继承层次结构的全部内容),因此编译器没有为此生成更具体的方法。

不幸的是,我看不到任何解决此问题的好方法。如果您想让AWS识别 I通用参数的界限,则必须在层次结构中真正知道该界限的位置覆盖 handleRequest

您可以尝试执行以下操作:

// Your _non-generic_ sub-class has to have the following implementation of handleRequest:
// def handleRequestImpl(input: EventType, context: Context): Unit = handleRequestImpl(input, context)
trait UnitHandler[Event] extends RequestHandler[Event, Unit]{
def act(input: Event): Unit

protected def handleRequestImpl(input: Event, context: Context): Unit = act(input)
}


这种方法的好处是您仍然可以在 handleRequestImpl中添加一些其他包装逻辑(例如,日志记录)。但这仍然只能按照惯例进行。我没有办法强迫开发人员以正确的方式使用此代码。

如果 Handler2的全部内容只是将输出类型 O绑定到 Unit而不添加任何包装逻辑,则可以执行此操作而无需将方法重命名为 act

trait UnitHandler[Event] extends RequestHandler[Event, Unit]{
override def handleRequest(input: Event, context: Context): Unit
}


这样,您的子类仍将必须使用绑定到 handleRequest的特定类型来实现 Event,并且编译器将必须在那里生成特定方法,因此不会发生此问题。

关于scala - AWS Lambda处理程序抛出带有Scala泛型的ClassCastException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54098144/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com