Description
I recently upgrade my spring boot project to 3.2.3 from 3.1.4. After upgrading, when I run in aot mode, it fails with error
org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'clientConfigMapper' for bean class [com.example.demo.repo.mapper.ClientConfigMapper] conflicts with existing, non-compatible bean definition of same name and class [org.mybatis.spring.mapper.MapperFactoryBean]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:361) ~[spring-context-6.1.4.jar:6.1.4]
at org.mybatis.spring.mapper.ClassPathMapperScanner.checkCandidate(ClassPathMapperScanner.java:345) ~[mybatis-spring-3.0.3.jar:na]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:288) ~[spring-context-6.1.4.jar:6.1.4]
at org.mybatis.spring.mapper.ClassPathMapperScanner.doScan(ClassPathMapperScanner.java:230) ~[mybatis-spring-3.0.3.jar:na]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.scan(ClassPathBeanDefinitionScanner.java:255) ~[spring-context-6.1.4.jar:6.1.4]
at org.mybatis.spring.mapper.MapperScannerConfigurer.postProcessBeanDefinitionRegistry(MapperScannerConfigurer.java:381) ~[mybatis-spring-3.0.3.jar:na]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:349) ~[spring-context-6.1.4.jar:6.1.4]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:148) ~[spring-context-6.1.4.jar:6.1.4]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:788) ~[spring-context-6.1.4.jar:6.1.4]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:606) ~[spring-context-6.1.4.jar:6.1.4]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.2.3.jar:3.2.3]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.2.3.jar:3.2.3]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.2.3.jar:3.2.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[spring-boot-3.2.3.jar:3.2.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-3.2.3.jar:3.2.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-3.2.3.jar:3.2.3]
at com.example.demo.DemoApplication.main(DemoApplication.java:15) ~[classes/:na]
and
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.Class<?>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1880) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1406) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:907) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.RegisteredBean.resolveAutowiredArgument(RegisteredBean.java:229) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveAutowiredArgument(BeanInstanceSupplier.java:341) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArguments(BeanInstanceSupplier.java:264) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:204) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1217) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1161) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1443) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveValue(AutowiredFieldValueResolver.java:188) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveAndSet(AutowiredFieldValueResolver.java:167) ~[spring-beans-6.1.4.jar:6.1.4]
at com.example.demo.controller.TestController__Autowiring.apply(TestController__Autowiring.java:17) ~[classes/:na]
at org.springframework.beans.factory.support.InstanceSupplier$1.get(InstanceSupplier.java:83) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1217) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1161) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:959) ~[spring-context-6.1.4.jar:6.1.4]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) ~[spring-context-6.1.4.jar:6.1.4]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.2.3.jar:3.2.3]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.2.3.jar:3.2.3]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.2.3.jar:3.2.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[spring-boot-3.2.3.jar:3.2.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-3.2.3.jar:3.2.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-3.2.3.jar:3.2.3]
at com.example.demo.DemoApplication.main(DemoApplication.java:15) ~[classes/:na]
It appears spring boot 3.2.x is using spring 6.1 and in 6.1 they change the check logic in spring-projects/spring-framework@eaf54b5#diff-02b9877a0a8b049e25dc47ce5b99ae822277865085e27a3944c963b32f87bb90 and spring-projects/spring-framework@e685ff0 causing the first error.
// Explicitly registered overriding bean?
if (!(existingDef instanceof ScannedGenericBeanDefinition) &&
(this.registry.isBeanDefinitionOverridable(beanName) || ObjectUtils.nullSafeEquals(
beanDefinition.getBeanClassName(), existingDef.getBeanClassName()))) {
return false;
}
beanDefinition.getBeanClassName()
and existingDef.getBeanClassName()
would not be the same.
I'm thinking why scanning for mapper again in aot mode? Shouldn't all beans already been processed in build time?
How about adding skip the scan in org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
like
@@ -227,7 +228,11 @@ public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
- Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
+ Set<BeanDefinitionHolder> beanDefinitions = new HashSet<>();
+ if (AotDetector.useGeneratedArtifacts()) {
+ return beanDefinitions;
+ }
+ beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
if (printWarnLogIfNotFoundMappers) {
As for the second error, spring 6.1 add support for generic constructor argument values in aot but didn't support string to class convert in this commit
It seems need to change definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
also to definition.getConstructorArgumentValues().addGenericArgumentValue(beanClass);
like
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
- // the mapper interface is the original class of the bean
- // but, the actual class of the bean is MapperFactoryBean
- definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
try {
Class<?> beanClass = Resources.classForName(beanClassName);
+ // the mapper interface is the original class of the bean
+ // but, the actual class of the bean is MapperFactoryBean
+ definition.getConstructorArgumentValues().addGenericArgumentValue(beanClass); // issue #59
// Attribute for MockitoPostProcessor
// https://github.com/mybatis/spring-boot-starter/issues/475
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClass);
./mvnw -Pnative -DskipTests clean package
java -Dspring.aot.enabled=true -Dorg.graalvm.nativeimage.imagecode=runtime -jar target/demo-0.0.1-SNAPSHOT.jar
will show first error
after change spring.main.allow-bean-definition-overriding to true and package&run again will show second error
uncomment
//ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
//constructorArgumentValues.addGenericArgumentValue(mapperInterface);
//beanDefinition.setConstructorArgumentValues(constructorArgumentValues);
in com.example.demo.mybatis.MyBatisMapperFactoryBeanPostProcessor
line 51 and package&run again will run success.