New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ClassCastException after upgrade to Spring Boot 2.5-RC1 #3558
Comments
Well, isn't that a problem in Kotlin then, rather than in Spring Integration? In Spring Integration we have:
This is exactly aligned with Spring Framework: https://github.com/spring-projects/spring-framework/blob/main/build.gradle#L4. Meanwhile I'm adding your test into the project for more coverage. BTW, haven't you tried with the latest Kotlin SNAPSHOT? Might be very the case that there is something was fixed around reflection... |
While copying your sample, I'd suggest you take a look into a Kotlin DSL we provide in Spring Integration: https://docs.spring.io/spring-integration/docs/current/reference/html/kotlin-dsl.html#kotlin-dsl |
Thanks @artembilan for your fast reply. I also think the problem is in Kotlin 1.5-RC. The dependency to Kotlin 1.5-RC comes from Spring Boot spring-boot-dependencies-2.5.0-RC1
Yes I tried the latest SNAPSHOT (removed the local .m2 directory). Thank you for the hint to the Kotlin DSL. |
Well, this one So, it's probably a bug in the current Kotlin
Therefore something indeed was changed in Kotlin
I'm still looking into Kotlin |
Oh 😱 - thanks a lot for the suggested fix 👍 |
With the transformer part it's the same problem:
|
So, yes, something has changed in Kotlin
Therefore the fix I suggest with an extra type argument for Your transformer logic to just copy headers is redundant: if you return just a payload, the framework will create a message for you and copy request headers, respectively. |
Okay to fix the test with Kotlin 1.5-RC is to change the flow like this:
The header in the transform part is used in the real code. I'm fine with closing the issue. Maybe some hint should be added in either the Spring Boot or Spring Integration upgrade documentation. Thank you! |
Thanks for confirmation! I don't think there is needed some hint, since we have that doc already pointing for the reason of such a |
Unfortunately with the Kotlin DSL this flow also raise an
|
And this one indeed a problem. It fails even with current Kotlin
Thank you for your persistence! Reopening... |
I will have a look on weekend. Let me know if I can help. |
Thank you, @agebhar1 , for your proposal! For now it is hard to say how you can help... I'm thinking about removing
The maximum what we have, but this is still not an end-user line of code. So, if there is really some end-user mistake in that code, he (she) won't have a clue where to go. With a
So, we have that Re. casting problem... Let's see if we can assume that class name containing that |
The minimal flow w/ Kotlin DSL with only the handle part I've tried is: @Bean
fun flow(): IntegrationFlow {
return integrationFlow(input()) {
handle<Message<String>> { message, _ -> message.also { println(it) } }
channel(output())
}
} The de-compiled Java version by IntelliJ (stripped garbage) is: @Bean
@NotNull
public IntegrationFlow flow() {
return IntegrationFlowDslKt.integrationFlow(input(), new Function1() {
public Object invoke(Object var1) {
this.invoke((KotlinIntegrationFlowDefinition) var1);
return Unit.INSTANCE;
}
public final void invoke(@NotNull KotlinIntegrationFlowDefinition flow) {
flow.getDelegate().handle(Message.class, new SpringIntegrationKotlinTests$Configuration$flow$1$$special$$inlined$handle$1());
flow.channel(output());
}
});
} with final class SpringIntegrationKotlinTests$Configuration$flow$1$$special$$inlined$handle$1 implements GenericHandler {
@NotNull
public final Object handle(Object p, MessageHeaders h) {
Message message = (Message) p;
System.out.println(message);
return message;
}
} A pure Java integration flow is: @Bean
public IntegrationFlow flow() {
return from(input())
.handle(Message.class, new SpringIntegrationKotlinTests$Configuration$flow$1$$special$$inlined$handle$1())
.channel(output())
.get();
} The test also fails with:
But if the implementing class of final class SpringIntegrationKotlinTests$Configuration$flow$1$$special$$inlined$handle$1 implements GenericHandler<Message<String>> {
@Override
public Object handle(Message<String> payload, MessageHeaders headers) {
System.out.println(payload);
return payload;
}
} I can provide the tests in the repository if it's helpful. |
This is invalid Kotlin code:
the compiler complains:
|
Thank you, @agebhar1 , for sharing those reports! What I see in our tests that it works for Not sure what to do yet... |
Well, I played a little bit and in the
Leaving our end-user code as it is:
Which confirms that we enter an end-user code with desired This also confirms that generic info is carried correctly only when one param is present in the method final method signature. Other notes: So, I'm not saying that Kotlin is wrong somewhere, but definitely something to investigate. |
I had a similar idea, in Lines 1042 to 1053 in 8f3a091
GenericHandler<T> instance to provide the required parameter information. The compiler ensures it's a valid interface implementation of GenericHandler<T> with Class<T> .
This could solve the problem for Kotlin as well as for Java with using raw What did you think? |
Sorry, I'm not sure what you mean. This doesn't work:
Just because it is Java and that generic type is erased during compilation we still end up with an Would you mind to share the change you think of? Thank you for your help on this! |
You are totally right. I missed the point that it's library code and not custom code. But let's think the other way. If the handler is not detected as a lambda and no runtime type information is available, like an instance of raw I am happy to help. |
Thank you for your help!
Pay attention to the If this is OK with you, I'm ready to PR the fix! |
It's similar to what I had in mind but simpler. The delegation by the method reference does the trick to create the provide the required runtime type information. else if (payloadType != null) {
return handle(payloadType, handler::handle, endpointConfigurer);
} This is a compact form of the lambda I created in the code above. You also simplified (skip) the check "if it's not present" type information. I'm fine with this fix 👍 |
I compared this one:
and this:
Using this generic info accessor:
And this is what I got for the:
And this is what for the:
Then I checked similar structure with Java:
(Right, anonymous because of generic info presence) and I get this result:
So, looks like indeed Kotlin is missing that generic info for two-param methods 😢 . Will PR the fix we have talked tomorrow. |
This coincides with what I had seen in #3558 (comment) Thank you for taking care of the issue! |
Fixes spring-projects#3558 Kotlin lambdas mostly used to configure endpoints in DSL manner are not really Java lambdas, but rather anonymous classes implementing respective Java interfaces. While in most cases such classes carry generic info for their method impls properly in Java, it is somehow doesn't work well for `GenericHandler` implemented by Kotlin lambdas * Wrap provided `GenericHandler` in the `BaseIntegrationFlowDefinition.handle()` into a Java lambda and call `handle()` recursively to carry an expected type to the `LambdaMessageProcessor` * Fix `LambdaMessageProcessor` to handle `ClassUtils.isKotlinUnit()` result of an invocation as a `null` reply **Cherry-pick to `5.4.x` & `5.3.x`**
See related PR: #3561 |
Fixes #3558 Kotlin lambdas mostly used to configure endpoints in DSL manner are not really Java lambdas, but rather anonymous classes implementing respective Java interfaces. While in most cases such classes carry generic info for their method impls properly in Java, it is somehow doesn't work well for `GenericHandler` implemented by Kotlin lambdas * Wrap provided `GenericHandler` in the `BaseIntegrationFlowDefinition.handle()` into a Java lambda and call `handle()` recursively to carry an expected type to the `LambdaMessageProcessor` * Fix `LambdaMessageProcessor` to handle `ClassUtils.isKotlinUnit()` result of an invocation as a `null` reply **Cherry-pick to `5.4.x` & `5.3.x`**
Fixes #3558 Kotlin lambdas mostly used to configure endpoints in DSL manner are not really Java lambdas, but rather anonymous classes implementing respective Java interfaces. While in most cases such classes carry generic info for their method impls properly in Java, it is somehow doesn't work well for `GenericHandler` implemented by Kotlin lambdas * Wrap provided `GenericHandler` in the `BaseIntegrationFlowDefinition.handle()` into a Java lambda and call `handle()` recursively to carry an expected type to the `LambdaMessageProcessor` * Fix `LambdaMessageProcessor` to handle `ClassUtils.isKotlinUnit()` result of an invocation as a `null` reply **Cherry-pick to `5.4.x` & `5.3.x`**
Fixes #3558 Kotlin lambdas mostly used to configure endpoints in DSL manner are not really Java lambdas, but rather anonymous classes implementing respective Java interfaces. While in most cases such classes carry generic info for their method impls properly in Java, it is somehow doesn't work well for `GenericHandler` implemented by Kotlin lambdas * Wrap provided `GenericHandler` in the `BaseIntegrationFlowDefinition.handle()` into a Java lambda and call `handle()` recursively to carry an expected type to the `LambdaMessageProcessor` * Fix `LambdaMessageProcessor` to handle `ClassUtils.isKotlinUnit()` result of an invocation as a `null` reply **Cherry-pick to `5.4.x` & `5.3.x`**
In what version(s) of Spring Integration are you seeing this issue?
5.5.0-RC1 via Spring Boot 2.5-RC1
Describe the bug
on
handler
method within integration flow.Sample
https://github.com/agebhar1/spring-integration-kotlin-bug
The error does not occur if explicitly use Koltin 1.4(.32)
The text was updated successfully, but these errors were encountered: