AWS LAMBDA处理程序抛出Scala的泛型一个ClassCastException

问题描述 投票:3回答:2

我建立与他们的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组装0.14.8。

我正在寻找任何一种变通的解决这个问题。

scala amazon-web-services aws-lambda
2个回答
5
投票

注:我不为亚马逊或Sun / Oracle的工作,所以回答的部分是一场炒作。

我认为这是JVM类型擦除之间的根本冲突,AWS如何试图围绕工作,它和你正在尝试做的。我也不认为你引用的错误是相关的。我认为,行为是Java一样的。

AFAIU从视AWS点的问题是这样的:有不同类型的事件流和一堆处理程序。您需要决定哪些事件给定的处理器可以处理。显而易见的解决方案是看handleRequest方法的签名,并使用参数的类型。不幸的是JVM类型的系统并没有真正支持泛型,所以你必须寻找最具体的方法(见下文),并假定该方法是实打实的。

现在,假设你开发一个编译器,JVM目标(斯卡拉或Java以外,其它实例将在Java中表明,这是不是一个斯卡拉特定问题)。由于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所以编译器生成你的final类的非泛型方法(即带不被删除类型的方法)。现在你有两个方法和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有承担。因为你没有覆盖handleRequestPojoTest2方法(和没有这样做,是你的继承层次的整点),编译器没有产生它一个更具体的方法。

不幸的是我没有看到这个问题有什么好的解决方法。如果你想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和编译器必须产生具体的方法有这样的问题不会发生特定类型。


2
投票

作为@SergGr说,有在JVM中没有真正的仿制药。所有类型被替换为它们的债券或对象。

这个答案对如何实现自定义的抽象处理程序不使用AWS RequestHandler涉及创建一个不同的看法。

我已经解决了这一问题的方法是使用context boundsClassTag是这样的:

abstract class LambdaHandler[TEvent: ClassTag, TResponse<: Any] {

  def lambdaHandler(inputStream: InputStream, outputStream: OutputStream, context: Context): Unit = {
    val json = Source.fromInputStream(inputStream).mkString
    log.debug(json)
    val event = decodeEvent(json)
    val response = handleRequest(event, context)
    // do things with the response ...
    outputStream.close()
  }

  def decodeEvent(json: String): TEvent = jsonDecode[TEvent](json)
}

其中jsonDecode是它可以将字符串事件的预期TEvent的功能。在下面的例子中,我使用json4s但你可以使用任何你想要的德/序列化方法:

def jsonDecode[TEvent: ClassTag](json: String): TEvent = {
  val mapper = Mapper.default
  jsonDecode(mapper)
}

最终,你将能写这样的功能

// AwsProxyRequest and AwsProxyResponse are classes from the com.amazonaws.serverless aws-serverless-java-container-core package
class Function extends LambdaHandler[AwsProxyRequest, AwsProxyResponse] {
  def handleRequest(request: AwsProxyRequest, context: Context): AwsProxyResponse = {
    // handle request and retun an AwsProxyResponse
  }
}

或自定义SNS处理器,其中TEvent是SNS消息的定制类型:

// SNSEvent is a class from the com.amazonaws aws-lambda-java-events package
abstract class SnsHandler[TEvent: ClassTag] extends LambdaHandler[TEvent, Unit]{
  override def decodeEvent(json: String): TEvent = {
    val event: SNSEvent = jsonDecode[SNSEvent](json)
    val message: String = event.getRecords.get(0).getSNS.getMessage
    jsonDecode[TEvent](message)
  }
}

如果您使用此方法,直接开箱的,你很快就会意识到,有大量的边缘情况下反序列化JSON的有效载荷,因为在你从AWS事件得到类型的不一致。因此,你将有微调,以满足您的需求jsonDecode方法。

另外,使用现有的库,需要照顾这些步骤你。有一个图书馆,我知道的斯卡拉(但尚未使用)称为aws-lambda-scala或者你可以在全面实施我LambdaHandler in GitHub的看看

© www.soinside.com 2019 - 2024. All rights reserved.