spring-projects / spring-data-relational

Spring Data Relational. Home of Spring Data JDBC and Spring Data R2DBC.
https://spring.io/projects/spring-data-jdbc
Apache License 2.0
737 stars 339 forks source link

Lifecycle Events using `Any` in kotlin does not work #1809

Closed blommish closed 1 month ago

blommish commented 1 month ago

Using kotlin and Any instead of Object does work.

@Bean
fun loggingSaves(): ApplicationListener<BeforeSaveEvent<Any>> {
    return ApplicationListener { event: BeforeSaveEvent<Any> ->

https://docs.spring.io/spring-data/jdbc/docs/2.4.18/reference/html/#jdbc.events

Should it be possible? Since the documentation mentions Object i would think it would match all using Any (or Object)

mp911de commented 1 month ago

What does the code above compile into (bytecode-wise)? How does the signature look like?

blommish commented 1 month ago

What does the code above compile into (bytecode-wise)? How does the signature look like?

Here is the byte code

> What does the code above compile into (bytecode-wise)? How does the signature look like?

Here is the byte code

```java
// class version 65.0 (65)
// access flags 0x21
public class foo/Foo {

  // compiled from: Foo.kt

  @Lorg/springframework/context/annotation/Configuration;()

  @Lkotlin/Metadata;(mv={1, 9, 0}, k=1, xi=48, d1={"\u0000\u0016\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0008\u0002\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\u0008\u0017\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002J\u0014\u0010\u0003\u001a\u000e\u0012\n\u0012\u0008\u0012\u0004\u0012\u00020\u00010\u00050\u0004H\u0017\u00a8\u0006\u0006"}, d2={"Lfoo/Foo;", "", "()V", "loggingSaves", "Lorg/springframework/context/ApplicationListener;", "Lorg/springframework/data/relational/core/mapping/event/BeforeSaveEvent;", "appname"})

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 9 L1
    RETURN
   L2
    LOCALVARIABLE this Lfoo/Foo; L0 L2 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  // signature ()Lorg/springframework/context/ApplicationListener<Lorg/springframework/data/relational/core/mapping/event/BeforeSaveEvent<Ljava/lang/Object;>;>;
  // declaration: org.springframework.context.ApplicationListener<org.springframework.data.relational.core.mapping.event.BeforeSaveEvent<java.lang.Object>> loggingSaves()
  public loggingSaves()Lorg/springframework/context/ApplicationListener;
  @Lorg/springframework/context/annotation/Bean;()
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    INVOKEDYNAMIC onApplicationEvent()Lorg/springframework/context/ApplicationListener; [
      // handle kind 0x6 : INVOKESTATIC
      java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
      // arguments:
      (Lorg/springframework/context/ApplicationEvent;)V, 
      // handle kind 0x6 : INVOKESTATIC
      foo/Foo.loggingSaves$lambda$0(Lorg/springframework/data/relational/core/mapping/event/BeforeSaveEvent;)V, 
      (Lorg/springframework/data/relational/core/mapping/event/BeforeSaveEvent;)V
    ]
   L1
    LINENUMBER 12 L1
    ARETURN
   L2
    LOCALVARIABLE this Lfoo/Foo; L0 L2 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1A
  private final static loggingSaves$lambda$0(Lorg/springframework/data/relational/core/mapping/event/BeforeSaveEvent;)V
    // parameter  event
   L0
    ALOAD 0
    LDC "event"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 13 L1
    RETURN
   L2
    LOCALVARIABLE event Lorg/springframework/data/relational/core/mapping/event/BeforeSaveEvent; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1
}
mp911de commented 1 month ago

Thanks for the detail. Your listener translates into BeforeSaveEvent<Object> that doesn't accept subclasses, only exact Object matches. You rather want to use Kotlin projections such as ApplicationListener<BeforeSaveEvent<*>> (renders to a BeforeSaveEvent<Object> signature) to capture all events.