我正在使用它们的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);
因为
I
和O
是未绑定的。现在假设你做
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/