Using spring 3.0.10 with spring-boot-starter-data-jdbc:
When using a FullyQualifiedAnnotationBeanNameGenerator as the bean naming strategy, repository beans are not named correctly (I suspect it uses the AnnotationBeanNameGenerator ? ).
Consider the application:
package com.namestrat.demo;
@SpringBootApplication(nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
I. e the repository defined in foo
package com.namestrat.demo.foo;
import org.springframework.data.repository.CrudRepository;
public interface ExampleRepository extends CrudRepository<Example, Long> { }
and a repository with the same name but different package bar
package com.namestrat.demo.bar;
import org.springframework.data.repository.CrudRepository;
public interface ExampleRepository extends CrudRepository<DifferentExample, Long> { }
produces:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.10)
2023-08-31T13:18:34.490-05:00 INFO 23161 --- [ main] com.namestrat.demo.DemoApplication : Starting DemoApplication using Java 17 with PID 23161 (/Users/hackcam/Desktop/demo/target/classes started by hackcam in /Users/hackcam/Desktop/demo)
2023-08-31T13:18:34.492-05:00 INFO 23161 --- [ main] com.namestrat.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default"
2023-08-31T13:18:34.637-05:00 INFO 23161 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JDBC repositories in DEFAULT mode.
2023-08-31T13:18:34.660-05:00 WARN 23161 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'exampleRepository' defined in com.namestrat.demo.foo.ExampleRepository defined in @EnableJdbcRepositories declared on JdbcRepositoriesRegistrar.EnableJdbcRepositoriesConfiguration: Cannot register bean definition [Root bean: class [org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in com.namestrat.demo.foo.ExampleRepository defined in @EnableJdbcRepositories declared on JdbcRepositoriesRegistrar.EnableJdbcRepositoriesConfiguration] for bean 'exampleRepository' since there is already [Root bean: class [org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in com.namestrat.demo.bar.ExampleRepository defined in @EnableJdbcRepositories declared on JdbcRepositoriesRegistrar.EnableJdbcRepositoriesConfiguration] bound.
2023-08-31T13:18:34.667-05:00 INFO 23161 --- [ main] .s.b.a.l.ConditionEvaluationReportLogger :
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2023-08-31T13:18:34.681-05:00 ERROR 23161 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
The bean 'exampleRepository', defined in com.namestrat.demo.foo.ExampleRepository defined in @EnableJdbcRepositories declared on JdbcRepositoriesRegistrar.EnableJdbcRepositoriesConfiguration, could not be registered. A bean with that name has already been defined in com.namestrat.demo.bar.ExampleRepository defined in @EnableJdbcRepositories declared on JdbcRepositoriesRegistrar.EnableJdbcRepositoriesConfiguration and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
I've tried fiddling around with package/entityScan, different @configuration with customized @EnableJdbcRepositories and such with no avail - I know that I could simply characterize each repository with a different name but it irks me that the program does not behave as I expected.
I'm fairly new to the whole spring / boot data ecosystem, so If this is in fact expected behavior for repository beans feel free to close this issue!
Somewhat related SO questions (some of them rather old 😞 )
bean naming strategy Two repos with same name
rather easy to replicate, but here is a sandbox demo:
Comment From: scottfrederick
In the case of Repository beans like this, Spring Data is fully in control of the names used when creating the generated beans. I can see that the FullyQualifiedAnnotationBeanNameGenerator is getting passed down to the Spring Data configuration, but I don't know the Spring Data code well enough to tell what's happening from there. Please create an issue in the Spring Data Commons repository so that team can take a look.
Comment From: mp911de
I'd like to reopen this ticket.
There's indeed a backoff when we determine that the name generator equals ConfigurationClassPostProcessor.IMPORT_BEAN_NAME_GENERATOR.
However, when I specify @SpringBootApplication(nameGenerator = MyAnnotationBeanNameGenerator.class), then ConfigurationClassBeanDefinitionReader.importBeanNameGenerator is still set to FullyQualifiedAnnotationBeanNameGenerator.
Comment From: wilkinsona
I think that's the expect behavior.
nameGenerator on SpringBootApplication is an alias for the nameGenerator attribute on @ComponentScan and should not affect ConfigurationClassBeanDefinitionReader as I understand it. It will "be used for naming detected components within the Spring container" and that's what I see at the moment.
Here's an example with Boot's Data JDBC smoke test:
@SpringBootApplication(nameGenerator = MyAnnotationBeanNameGenerator.class)
public class SampleDataJdbcApplication {
public static void main(String[] args) {
SpringApplication.run(SampleDataJdbcApplication.class);
}
static class MyAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator {
@Override
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanName = super.buildDefaultBeanName(definition).toUpperCase();
System.out.println(beanName);
return beanName;
}
}
}
This outputs SAMPLECONTROLLER for the SampleController @Controller and that's it.
To apply the custom naming more broadly it needs to be configured at the level of the application context. This can be done through the SpringApplication or SpringApplicationBuilder API, for example:
public static void main(String[] args) {
new SpringApplicationBuilder(SampleDataJdbcApplication.class)
.beanNameGenerator(new FullyQualifiedAnnotationBeanNameGenerator())
.run(args);
}
This results in all beans, other than those defined in @Bean methods, have fully-qualified names. For example, the smoke test's Spring Data JDBC repository is named smoketest.data.jdbc.CustomerRepository.
Perhaps Spring Data's repository scanning should use the name generator from component scanning such that context-wide configuration isn't needed? However, I'm not sure how that can be achieved, particularly in Boot, given that it's driven through Framework's ImportBeanDefinitionRegistrar contract.
Comment From: mp911de
Thanks for the insight, Andy. I wasn't aware there's a difference between the two approaches. Spring Data uses ImportBeanDefinitionRegistrar and receives the context-wide bean name generator via ImportBeanDefinitionRegistrar.registerBeanDefinitions(…).
After learning that's the expected behavior, I think we can close also the ticket on the Data side as we stick to the ImportBeanDefinitionRegistrar contract as well.