我正在使用它们的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/

10-11 07:44