我建立与他们的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。
我正在寻找任何一种变通的解决这个问题。
注:我不为亚马逊或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);
因为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
所以编译器生成你的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有承担。因为你没有覆盖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
和编译器必须产生具体的方法有这样的问题不会发生特定类型。
作为@SergGr说,有在JVM中没有真正的仿制药。所有类型被替换为它们的债券或对象。
这个答案对如何实现自定义的抽象处理程序不使用AWS RequestHandler
涉及创建一个不同的看法。
我已经解决了这一问题的方法是使用context bounds和ClassTag
是这样的:
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的看看