Closed snicoll closed 2 years ago
@snicoll Thanks for your work!! I try this change in my local. The AOT mode on JVM work fine, but native mode does not work...
p.s. Following is result that I've applied the Spring Boot 2.6.3 and spring-native 0.11.2-SNAPSHOT latest version.
...
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'runner': Unsatisfied dependency expressed through method 'runner' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cityMapper': Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'mapperInterface' of bean class [org.mybatis.spring.mapper.MapperFactoryBean]: Bean property 'mapperInterface' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
at org.springframework.aot.beans.factory.InjectedConstructionResolver.resolve(InjectedConstructionResolver.java:88) ~[na:na]
at org.springframework.aot.beans.factory.InjectedElementResolver.resolve(InjectedElementResolver.java:19) ~[na:na]
at org.springframework.aot.beans.factory.InjectedElementResolver.create(InjectedElementResolver.java:50) ~[na:na]
at org.springframework.aot.beans.factory.BeanDefinitionRegistrar$InstanceSupplierContext.create(BeanDefinitionRegistrar.java:193) ~[na:na]
at org.mybatis.spring.nativex.sample.simple.ContextBootstrapInitializer.lambda$registerMybatisSpringNativeSampleApplication_runner$1(ContextBootstrapInitializer.java:11) ~[na:na]
at org.springframework.aot.beans.factory.ThrowableFunction.apply(ThrowableFunction.java:18) ~[na:na]
at org.springframework.aot.beans.factory.BeanDefinitionRegistrar.lambda$instanceSupplier$0(BeanDefinitionRegistrar.java:97) ~[na:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1249) ~[na:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1191) ~[na:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[na:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[na:na]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[na:na]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[na:na]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[na:na]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[na:na]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953) ~[na:na]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[na:na]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[na:na]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) ~[mybatis-spring-native-sample-simple:2.6.3]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:414) ~[mybatis-spring-native-sample-simple:2.6.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:302) ~[mybatis-spring-native-sample-simple:2.6.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[mybatis-spring-native-sample-simple:2.6.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[mybatis-spring-native-sample-simple:2.6.3]
at org.mybatis.spring.nativex.sample.simple.MybatisSpringNativeSampleApplication.main(MybatisSpringNativeSampleApplication.java:32) ~[mybatis-spring-native-sample-simple:0.0.1-SNAPSHOT]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cityMapper': Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'mapperInterface' of bean class [org.mybatis.spring.mapper.MapperFactoryBean]: Bean property 'mapperInterface' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1744) ~[na:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1452) ~[na:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[na:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[na:na]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[na:na]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[na:na]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[na:na]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[na:na]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[na:na]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1389) ~[na:na]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309) ~[na:na]
at org.springframework.aot.beans.factory.InjectedConstructionResolver.lambda$resolve$0(InjectedConstructionResolver.java:83) ~[na:na]
at org.springframework.aot.beans.factory.InjectedConstructionResolver.resolveDependency(InjectedConstructionResolver.java:97) ~[na:na]
at org.springframework.aot.beans.factory.InjectedConstructionResolver.resolve(InjectedConstructionResolver.java:83) ~[na:na]
... 23 common frames omitted
Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'mapperInterface' of bean class [org.mybatis.spring.mapper.MapperFactoryBean]: Bean property 'mapperInterface' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
at org.springframework.beans.BeanWrapperImpl.createNotWritablePropertyException(BeanWrapperImpl.java:243) ~[na:na]
at org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(AbstractNestablePropertyAccessor.java:432) ~[na:na]
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:278) ~[na:na]
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:266) ~[na:na]
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:104) ~[na:na]
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:79) ~[na:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1740) ~[na:na]
... 36 common frames omitted
Thanks for trying. It is so easy to forget what the AOT engine does for us automatically. Yeah, writing custom code means you need to know about manual hints. I'll revisit this shortly.
OK so the problem now is that the type of the bean is CityMapper
and no longer MapperFactoryBean
. When the engine goes through the type to register hints automatically, it searches for a mapperInterface
property on CityMapper
(since that type is exposed). It doesn't find it and therefore give up.
One way to fix this would be to rework this PR so that the type exposed is ResolvableType.forClassWithGenerics(MapperFactoryBean.class, CityMapper.class)
. The problem with that approach as we've seen in Spring Data is that the factory bean could be a sub-class, or it could have different generic types. Us blindly rewriting the type assuming it only has one type is dangerous, which is why I didn't act on it.
Another solution would be to introduce the concept of FactoryBean
as a first class citizen in BeanInstanceDescriptor
. This would let us keep the simple type and expose the factory bean for further inspection.
What do you think?
@snicoll Thanks for your comment! Which method would you recommend? I've tried ResolvableType.forClassWithGenerics(MapperFactoryBean.class, CityMapper.class)
, it work fine in my local.
before:
beanDefinition.setTargetType(mapperInterface);
after:
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(beanDefinition.getBeanClass(), mapperInterface));
Another solution would be to introduce the concept of FactoryBean as a first class citizen in BeanInstanceDescriptor.
What should I do specifically for this?
Which method would you recommend?
I don't know. Is sub-classing MapperFactoryBean
happening frequently? the Spring integration has a way to specify the factorybean to use so it is possible to subclass it. If we can then the problem I've tried to describe in my previous comment could apply. I don't know enough about mybatis to know.
What should I do specifically for this?
Nothing, that would be done completely in Spring Native. I doubt I would have the time to work on that short term though.
@snicoll Thanks for quick response!!
happening frequently?
Probably, I think it's rare. Therefore I want to apply to first solution(using ResolvableType.forClassWithGenerics
). At first release, we will announce as limitation for using sub-class of MapperFactoryBean
.
OK let me update the PR in that direction
This commit post-processes the bean factory so that the target type produced by the factory bean is exposed. While this is not a problem with a regular runtime, AOT uses instance supplier and without a proper bound for the generic type that it produces, the only solution is to create it early.
An alternative would be to change
ClassPathMapperScanner
in mybatis so that it exposes the type that the factory bean produces.