cod*_*dle 5 scala amazon-web-services aws-lambda
我正在使用其POJO处理程序构建一个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
Run Code Online (Sandbox Code Playgroud)
以下代码在上载到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
}
Run Code Online (Sandbox Code Playgroud)
现在,因为我正在使用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)
}
Run Code Online (Sandbox Code Playgroud)
当我运行javap PojoTest1.class这是使一切正常的方法:
public void handleRequest(com.amazonaws.services.lambda.runtime.events.SNSEvent, com.amazonaws.services.lambda.runtime.Context);
Run Code Online (Sandbox Code Playgroud)
运行时,javap PojoTest2.class您可以从该签名中看到的类型SNSEvent已被擦除为Object:
public void handleRequest(java.lang.Object, com.amazonaws.services.lambda.runtime.Context);
Run Code Online (Sandbox Code Playgroud)
这看起来就像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}")
}
Run Code Online (Sandbox Code Playgroud)
即使直接扩展抽象类也不会产生更好的结果:
// 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)
}
Run Code Online (Sandbox Code Playgroud)
当我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);
Run Code Online (Sandbox Code Playgroud)
唯一有效的类型擦除是
public Object handleRequest(Object input, Context context);
Run Code Online (Sandbox Code Playgroud)
因为I和O是不受约束的。
现在假设你做
public class PojoTest1 implements RequestHandler<SNSEvent, Void> {
@Override
public Void handleRequest(SNSEvent input, Context context) {
// whatever
return null;
}
}
Run Code Online (Sandbox Code Playgroud)
此时,您说您有一个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;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,您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;
}
}
Run Code Online (Sandbox Code Playgroud)
此时返回类型是固定的,但参数仍然是未绑定的泛型。因此,编译器必须产生如下内容:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
所以现在当我们来到
public class PojoTest2 extends Handler2<SNSEvent> {
@Override
protected void act(SNSEvent input) {
// whatever
}
}
Run Code Online (Sandbox Code Playgroud)
您已覆盖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
}
}
Run Code Online (Sandbox Code Playgroud)
或者,如果将树展平并在中显示所有(相关)方法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
}
}
Run Code Online (Sandbox Code Playgroud)
这两种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)
}
Run Code Online (Sandbox Code Playgroud)
这种方法的好处是您仍然可以在其中添加一些其他包装逻辑(例如日志记录)handleRequestImpl。但这仍然只能按照惯例进行。我没有办法强迫开发人员以正确的方式使用此代码。
如果您的整个观点Handler2只是将输出类型绑定O到Unit而不添加任何包装逻辑,则可以执行此操作而无需将方法重命名为act:
trait UnitHandler[Event] extends RequestHandler[Event, Unit]{
override def handleRequest(input: Event, context: Context): Unit
}
Run Code Online (Sandbox Code Playgroud)
这样,您的子类仍将必须handleRequest使用绑定的特定类型来实现,Event并且编译器将不得不在那里生成特定的方法,因此不会发生此问题。
| 归档时间: |
|
| 查看次数: |
333 次 |
| 最近记录: |