Skip to content
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

Kotlin functional config DSL should prevent beans to be registered twice in AOT-optimized contexts #29211

Closed
joshlong opened this issue Sep 27, 2022 · 5 comments
Assignees
Labels
theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Milestone

Comments

@joshlong
Copy link
Member

joshlong commented Sep 27, 2022

here's the spring initializr configuration i used

here's some kotlin code

package com.example.nativekotlin

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.boot.runApplication
import org.springframework.context.ApplicationListener
import org.springframework.context.annotation.Bean
import org.springframework.context.support.beans
import org.springframework.data.annotation.Id
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Component

@SpringBootApplication
class NativeKotlinApplication  {

    /*@Bean
    fun myListener(cr: CustomerRepository) = MyListener(cr)*/

}


fun main(args: Array<String>) {
    runApplication<NativeKotlinApplication>(*args)  {

        this.addInitializers(beans {
            bean {
                MyListener(ref())
            }
        })

    }
}

class MyListener(val repo: CustomerRepository) : ApplicationListener<ApplicationReadyEvent> {

    override fun onApplicationEvent(event: ApplicationReadyEvent) {
        listOf("James", "Josh")
            .map { Customer(null, it) }
            .map { repo.save(it) }
            .forEach { println(it) }
    }
}


interface CustomerRepository : CrudRepository<Customer, Int>

data class Customer(@Id val id: Int?, val name: String) 

I compile it using mvn -Pnative -DskipTests clean package to get a GraalVM binary. if I leave the code as-is, on the JRE, I see James and Josh. If I run it as a native image on GraalVM, I see James, Josh, James, Josh. (the same thing, twice). My listener is being called twice, for some reason.

Odder still, if I use @Bean to register the MyListener or if I use @Component, then in those cases I only see Josh, and James once. Not twice.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Sep 27, 2022
@snicoll
Copy link
Member

snicoll commented Sep 28, 2022

Thanks for the report, as far as I can see this has nothing to do with kotlin or the functional config. When AOT processes the context, it honors the initializers that you've defined. This defines a bean in the bean factory so it's processed. When the AOT-optimized context runs, it uses the generated context that registers a bean, and then call your initializer again. Can you please confirm that my analysis is correct (you should see some generated code for MyListener).

@snicoll snicoll added the status: waiting-for-feedback We need additional information before we can continue label Sep 28, 2022
@sdeleuze
Copy link
Contributor

I had a look and I think your analysis is correct @snicoll. There is some code generated (MyListener__BeanDefinitions) and with a quick test with that updates and the messages are only printed one time each:

fun main(args: Array<String>) {
	runApplication<NativeKotlinApplication>(*args)  {

		this.addInitializers(beans {
			if (!AotDetector.useGeneratedArtifacts()) {
				bean {
					MyListener(ref())
				}
			}
		})
	}
}

@snicoll snicoll added type: enhancement A general enhancement theme: aot An issue related to Ahead-of-time processing and removed status: waiting-for-feedback We need additional information before we can continue status: waiting-for-triage An issue we've not yet triaged or decided on labels Sep 29, 2022
@snicoll snicoll added this to the 6.0.0-RC1 milestone Sep 29, 2022
@snicoll snicoll changed the title ApplicationListener<T>s added via functional config in Kotlin get run twice in an AOT native image context Kotlin functional config DSL should prevent beans to be registered twice in AOT-optimized contexts Sep 29, 2022
@snicoll
Copy link
Member

snicoll commented Sep 29, 2022

Thanks for testing @sdeleuze. We've been discussing this one as part of spring-projects/spring-boot#32262 and we believe that this should work out-of-the-box.

Our thinking is that adding the AotDetector check around the childeren in this method might do the trick + a test in a smoke test that uses the Kotlin DSL (if that doesn't exist).

WDYT?

@sdeleuze
Copy link
Contributor

sdeleuze commented Sep 29, 2022

Yeah it was not a Gradle project (so painful to test by modifying Spring Framework snapshots) so I went the easy way to do a basic test.

I am ok with your proposal, but the fix will be Kotlin DSL specific and people using directly the Java functional API to do the same thing will continue to see the double registration if they don't do the AotDetector check manually. I don't see a better option so I guess we have to live with that but just asking for confirmation.

@snicoll
Copy link
Member

snicoll commented Sep 29, 2022

I don't see a better option so I guess we have to live with that but just asking for confirmation.

Yes. Our plan is to gather more feedback and then make an API change if necessary with more feedback. This one looks like it can be handled internally so we should do that regardless.

@sdeleuze sdeleuze self-assigned this Sep 29, 2022
sdeleuze added a commit to sdeleuze/spring-aot-smoke-tests that referenced this issue Sep 30, 2022
sdeleuze added a commit to sdeleuze/spring-aot-smoke-tests that referenced this issue Sep 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants