spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.79k stars 38.17k forks source link

AOT registers BeanDefinitionRegistryPostProcessor beans causing duplication of configuration #30411

Open DanielThomas opened 1 year ago

DanielThomas commented 1 year ago

AOT retains registrations of BeanDefinitionRegistryPostProcessor beans, causing them to run again under AOT. Other than meaning you don't realise the benefits of avoidance of post-processing, if those processors register definitions unconditionally they fail with:

***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'GrpcClient_myclient' could not be registered. A bean with that name has already been defined and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

Appears that Boot is handling this conditionally in several places with AotDetector, but that appears to be explicitly for internal use.

In this specific case, it feels like AOT should filter the registrations, but what's the recommendation for similar patterns in end-user implementations?

Example project:

https://github.com/DanielThomas/spring-aot-issues/tree/dannyt/post-processor-duplicate

Run and note the failure:

./gradlew bootJar && java -Dspring.aot.enabled=true -jar build/libs/demo-0.0.1-SNAPSHOT.jar
sdeleuze commented 1 year ago

Looks like related to #29484 and something we should potentially improve in Spring Framework 6.1. For your use case, in order to avoid the use of AotDetector internal API, could you maybe evaluate if you can update your BeanDefinitionRegistryPostProcessor to skip the processing if already applied (register a bean definition only if it does not exists, change existing beans only if not already done, etc.)?

DanielThomas commented 1 year ago

Most of our actual implementations do that already. We're also trying to avoid the overhead of running the processors at all:

Screenshot 2023-05-02 at 12 05 17 am

We also have ContextCustomizer implementations that will have the same problem for tests, and looks like that's where Boot uses the AotDetector heavily. Is there a plan to provide a convention for user implementations to indicate they only do things that AOT is capturing?

sdeleuze commented 1 year ago

I think what will come up from #29484 should be usable for user implementation as well, maybe you could comment on it to bring this need to the attention of the team and we close this one as duplicate?

snicoll commented 1 year ago

@DanielThomas thanks for the report. We haven't really made up our mind on BeanDefinitionRegistryPostProcessor. I agree a "regular" use of the interface should be filtered out. Unfortunately, this isn't always the case, with implementations mutating the bean definitions in a way that AOT can't process them. Ideally, the "static" bean registration use case should be separated from the "runtime" bean mutation use case. It isn't easy as there aren't clear boundaries in the core API for this either.

I'd like this one to be kept open so that we can do an overview of our own implementations.

snicoll commented 1 year ago

I did a bit of searching in the codebase to at least what Spring Boot integrates with directly.

Post processors disabled with AotDetector:

Post processors excluded explicitly via BeanRegistrationExcludeFilter:

Post processors excluded explicitly in the core infrastructure:

org.springframework.context.annotation.ConfigurationClassPostProcessor.

Other cases

org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration.WsdlDefinitionBeanFactoryPostProcessor registers bean definitions based on a property and use an instance supplier so it will fail if used with AOT.

org.springframework.boot.test.context.ImportsContextCustomizer.ImportsCleanupPostProcessor doesn't seem to be excluded but since it removes bean definitions once configuration class processing has completed, it should definitely be.

org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.ConfigurationWarningsPostProcessor looks a legit use case of running them twice. Altough, an AOT contribution that stores the warning rather than running the check twice would be more appropriate.

org.springframework.integration.config.MessagingAnnotationPostProcessor is using AotDetector but not to prevent it to run with AOT. Rather it uses it to change the behavior of the post-processor.

org.springframework.security.config.debug.SecurityDebugBeanFactoryPostProcessor is logging a warning and is mutating a bean definition. While the former is required, the latter is not as it has been captured by AOT.

org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.RequestRejectedHandlerPostProcessor is likely to be wrong at this stage as it runs twice and it probably shouldn't.

org.springframework.security.config.http.OAuth2ClientWebMvcSecurityPostProcessor is also to be reviewed. Unless the attributes that it sets on a bean definition can't be handled by AOT, it shouldn't run twice.

org.springframework.security.config.method.GlobalMethodSecurityBeanDefinitionParser.LazyInitBeanDefinitionRegistryPostProcessor is running twice and shouldn't. It's mutating the bean definition and that's captured by AOT.

org.springframework.security.config.websocket.WebSocketMessageBrokerSecurityBeanDefinitionParser.MessageSecurityPostProcessor should be reviewed. I believe it registers bean definitions programmatically and shouldn't run again with AOT.

org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.RegisterMissingBeanPostProcessor should be reviewed as well for the same reason.

Mixing contracts

Looking at whether they implement a new contract, this is clean with a few exceptions:

Conclusion

There seem to be some possible improvements in our own codebase but it's not really straightforward to think about AOT when designing such a contract. Perhaps excluding such implementations automatically could help as it'll "force" users to decouple the behaviour, or avoid runtime mutation of the bean registry. I don't know if we can actually make such a change in a non major release and how we'd let folks opt-in for this to run again.

snicoll commented 1 year ago

Related #30372