aws / aws/aws-lambda-java-libs
Custom Serialization feature for handler class that implements RequestHandler and uses method name handler syntax does not use exact types during serialization
- Lenguaje dominante
- Java
- Estrellas
- 548
- Forks
- 241
- Merge medio
- 2 d 5 min
- PR fusionados (30 d)
- 11
Descripción
The [custom serialization](https://docs.aws.amazon.com/lambda/latest/dg/java-custom-serialization.html) feature does not use the exact types of type parameters in the presence of inheritance when (a) the implementation class implements `RequestHandler`; and (b) the configured handler uses method name syntax (as opposed to class name syntax, so, `com.example.ExampleLambdaFunction#requestHandler`).
For example, consider the below Lambda implementation with the given custom serializer. Note that the `ExampleLambdaFunction` is the actual handler class, and it extends `LambdaFunctionBase`, which implements `RequestHandler` and uses generic type parameters for the `handleRequest` method parameters and result.
package com.example;
public abstract class LambdaFunctionBase
implements RequestHandler {
@Override
public OutputT handleRequest(InputT input, Context context) {
System.out.println("Enter handleRequest");
OutputT result = doHandleRequest(input, context);
System.out.println("Exit handleRequest");
return result;
}
protected abstract OutputT doHandleRequest(InputT input, Context context);
}
public class ExampleLambdaFunction
extends LambdaFunctionBase {
@Override
public ExampleResponse doHandleRequest(ExampleRequest request) {
ExampleResponse result;
result = businessLogic(request);
return result;
}
}
public class ExampleCustomPojoSerializer
implements CustomPojoSerializer {
@Override
public T fromJson(InputStream input, Type type) {
System.err.println("fromJson(" + type + ")");
return deserializeImplementation(input, type);
}
@Override
public T fromJson(String input, Type type) {
System.err.println("fromJson(" + type + ")");
return deserializeImplementation(input, type);
}
@Override
public void toJson(T value, OutputStream output, Type type) {
System.err.println("toJson(" + type + ")");
return serializeImplementation(value, output, type);
}
}
If the configured handler looks like `com.example.ExampleLambdaFunction`, then the result of creating, deploying, and invoking the lambda would produce the following output on stderr:
fromJson(ExampleRequest)
toJson(ExampleResponse)
So everything works as expected.
However, if the configured handler looks like `com.example.ExampleLambdaFunction::handleRequest`, then the result is different:
fromJson(InputT)
toJson(OutputT) // <-- Assuming serialization succeeded, and the lambda completed successfully
Having poked around in the code a bit, it looks like the difference is due to code in aws-lambda-java-runtime-interface-client. If we look at [`EventHandler#getHandlerFromOverload(Class,Method)`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L415), we see that it creates the `LambdaRequestHandler` from the literal return and parameter types of the given method. However, we know that in this example, those are going to be generic type parameters. It needs to use [the exact return type of the method](https://github.com/leangen/geantyref?tab=readme-ov-file#getting-the-exact-return-type-of-a-method) and [the exact parameter types of the method](https://github.com/leangen/geantyref?tab=readme-ov-file#getting-the-exact-types-of-method-parameters) (examples linked from the excellent [https://github.com/leangen/geantyref](geantyref) library, purely for illustration and reference).
The call stack looks like this when the error occurs:
EventHandlerLoader::loadEventHandler // <-- Branch in this method is why handler syntax matters
EventHandlerLoader::loadEventPojoHandler
EventHandlerLoader::getHandlerFromOverload // <-- Error is here
The feature works as expected when the handler looks like `com.example.ExampleLambdaFunction` because in [`EventHandlerLoader#loadEventHandler`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L336), control flows through [`EventHandler.loadStreamingRequestHandler`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L325), which calls through methods to handle generic types, i.e., [`EventHandlerLoader#wrapPojoHandler`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L522).
The feature does not work as expected when the handler looks like `com.example.ExampleLambdaFunction::handleRequest` because in [`EventHandlerLoader#loadEventHandler`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L336), control flows through [`EventHandler.loadEventPojoHandler`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L452), which uses the literal types of the indicated method, as noted above.
It's not clear whether or not this is a "bug", per se. Based on the code, it seems like the intent is that developers use `com.example.ExampleLambdaFunction` syntax when their implementation inherits from `RequestHandler`, and `com.example.ExampleLambdaFunction::methodName` when it doesn't. On the one hand, method name syntax should always work as expected. On the other hand, if the user uses method name syntax for a class that inherits from RequestHandler, it's "belt and suspenders" at best, and potentially confusing at worst.
In any case, I lost some time debugging my custom serializer due to this issue, so I thought I'd write it up. Fortunately, there should be a pretty simple fix, if the team agrees that this is a bug. (I'd be happy to contribute a patch!) Otherwise, a line in the documentation like "If your class implements RequestHandler, then use `com.example.ExampleLambdaFunction` syntax; otherwise, use `com.example.ExampleLambdaFunction::methodName` syntax. If your RequestHandler implementation uses method name syntax, you might get unexpected behavior in some cases." might be useful to others. (Please ignore if that documentation already exists! I looked, but didn't find it.)
Please let me know if you have any questions, or if I can help in any way!
Guía de contribución
Evaluación
Este issue todavía no se ha evaluado.