spring-projects / spring-framework

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

Bean injection fails due to `nullSafeConciseToString()` invoking `isEmpty()` on a `Map`/`Collection` proxy #31138

Closed luisericlf closed 1 year ago

luisericlf commented 1 year ago

I have two beans like this:

<alias name="defaultProblematicBean" alias="problematicBean" />
<bean id="defaultProblematicBean"
    class="com.application.ProblematicClass">
    <property name="strategies" ref="problematicStrategiesList" />
</bean>

<alias name="defaultProblematicStrategiesList" alias="problematicStrategiesList" />
<util:list id="defaultProblematicStrategiesList">
    <bean id="bean1" parent="defaultProblematicBean"
        class="com.application.ProblematicClass$ProblematicInnerClass1" />
    <bean id="bean2" parent="defaultProblematicBean"
        class="com.application.ProblematicClass$ProblematicInnerClass2" />
</util:list>

Please note: ProblematicInnerClass1 and ProblematicInnerClass2 are static inner classes inside of ProblematicClass that extend from ProblematicClass.

When I try to start the context, spring throws the following summarized error: Initialization of bean failed; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'jdk.proxy4.$Proxy363 implementing java.util.List' to required type 'java.util.List' for property 'strategies'; nested exception is java.lang.IllegalStateException: Singleton instance not initialized yet

Note: This works fine with 5.3.27 but fails with 5.3.29, we noticed that some changes were made to ObjectUtils.nullSafeConciseToString, which is part of the stack trace.

This is the stacktrace that contains the reference to ObjectUtils.nullSafeConciseToString

Caused by: java.lang.IllegalStateException: Singleton instance not initialized yet
at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.config.AbstractFactoryBean.getSingletonInstance(AbstractFactoryBean.java:188) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.config.AbstractFactoryBean.access$200(AbstractFactoryBean.java:63) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.config.AbstractFactoryBean$EarlySingletonInvocationHandler.invoke(AbstractFactoryBean.java:275) ~[spring-beans-5.3.29.jar:5.3.29]
at jdk.proxy4.$Proxy363.isEmpty(Unknown Source) ~[?:?]
at org.springframework.util.ObjectUtils.nullSafeConciseToString(ObjectUtils.java:981) ~[spring-core-5.3.29.jar:5.3.29]
at org.springframework.core.convert.ConversionFailedException.<init>(ConversionFailedException.java:52) ~[spring-core-5.3.29.jar:5.3.29]
at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47) ~[spring-core-5.3.29.jar:5.3.29]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192) ~[spring-core-5.3.29.jar:5.3.29]
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:129) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:590) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.AbstractNestablePropertyAccessor.convertForProperty(AbstractNestablePropertyAccessor.java:609) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:219) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.convertForProperty(AbstractAutowireCapableBeanFactory.java:1756) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1712) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1452) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:374) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:127) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveManagedList(BeanDefinitionValueResolver.java:428) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:173) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1707) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1452) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:113) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1707) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1452) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:113) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1707) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1452) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330) ~[spring-beans-5.3.29.jar:5.3.29]
... 30 more
bclozel commented 1 year ago

Please provide a sample, minimal application that reproduces this problem.

mohlam12 commented 1 year ago

I came across the same issue on my Spring based project

I think the stacktrace of .27 vs .29 is a good indicator of the root cause as you can see below (for the same project, just different spring core version).

On core 5.3.27, the exception is logged only in DEBUG mode, as it is handled by Spring :

org.springframework.core.convert.ConversionFailedException: Failed to convert from type [jdk.proxy5.$Proxy341] to type [@org.springframework.beans.factory.annotation.Required java.util.List<fr.royality.UserLoader>] for value [jdk.proxy5.$Proxy341@16be9b34]; 

nested exception is java.lang.IllegalStateException: Singleton instance not initialized yet

at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47) ~[spring-core-5.3.27.jar:5.3.27]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192) ~[spring-core-5.3.27.jar:5.3.27]
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:129) ~[spring-beans-5.3.27.jar:5.3.27]

On 5.3.29, the exception is fatal and breaks the context initialization:

java.lang.IllegalStateException: Singleton instance not initialized yet

at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.config.AbstractFactoryBean.getSingletonInstance(AbstractFactoryBean.java:188) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.config.AbstractFactoryBean.access$200(AbstractFactoryBean.java:63) ~[spring-beans-5.3.29.jar:5.3.29]
at org.springframework.beans.factory.config.AbstractFactoryBean$EarlySingletonInvocationHandler.invoke(AbstractFactoryBean.java:275) ~[spring-beans-5.3.29.jar:5.3.29]

at jdk.proxy4.$Proxy341.isEmpty(Unknown Source) ~[?:?]
at org.springframework.util.ObjectUtils.nullSafeConciseToString(ObjectUtils.java:981) ~[spring-core-5.3.29.jar:5.3.29]

at org.springframework.core.convert.ConversionFailedException.<init>(ConversionFailedException.java:52) ~[spring-core-5.3.29.jar:5.3.29]

at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47) ~[spring-core-5.3.29.jar:5.3.29]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192) ~[spring-core-5.3.29.jar:5.3.29]
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:129) ~[spring-beans-5.3.29.jar:5.3.29]

Looks like the error on 5.3.29 breaks the context initialization because when instantiating a new ConversionFailedException, there is the use of ObjectUtils.nullSafeConciseToString which calls the proxy's isEmpty and this tries to invoke the bean creation again- which fails. On 5.3.27, the ObjectUtils.nullSafeConciseToString did only return the class name as the evan representation without invoking any proxy method.

It looks like this change is related to the commit : https://github.com/spring-projects/spring-framework/commit/a3907a64e528cc055807d02aa4e4e6132aa07a1e

bclozel commented 1 year ago

@mohlam12 let's not jump to conclusions before we had a chance to look at the problem. Do you have a minimal sample application you can share with us?

mohlam12 commented 1 year ago

Sorry but that was not my intention. My previous comment does not have a conclusion, just an opinion and a commit that looks to be related to the same class that causes the exception according to the stacktrace since 5.3.29 Unfortunately, I'm not able to reproduce it on a minimal sample project. I only have the error on a big 1000+ beans project since 5.3.29 - i'll still try to see if I'm able to generate the same error somehow.

jhoeller commented 1 year ago

I can see a potential regression here indeed for lazy beans of type Collection, caused by #30810. One way or the other, we'll have to fix this for 6.0.12 and 5.3.30.

sbrannen commented 1 year ago

I can see a potential regression here indeed for lazy beans of type Collection, caused by #30810. One way or the other, we'll have to fix this for 6.0.12 and 5.3.30.

I agree.

I've put some thought into it, and here are a few brainstorming ideas.

  1. Revert the changes made in #30810 so that we never invoke isEmpty() on a Collection or Map, but keep the changes for array support.
  2. Only invoke isEmpty() if the collection/map is not a proxy, and fall back to a class name based string representation otherwise.
  3. Invoke isEmpty() (or practically the entire nullSafeConciseToString() method if we want to be safer) in a try-catch block, and fall back to a class name based string representation if an exception is thrown.
  4. If we want a targeted fix for this particular use case, we could add some logic to AbstractFactoryBean.EarlySingletonInvocationHandler so that isEmpty() always returns true if the AbstractFactoryBean is !initialized (if the isEmpty() Method is from Map or Collection).

Though, the latter might only be applicable for SetFactoryBean, ListFactoryBean, and MapFactoryBean instead of for AbstractFactoryBean in general.

Additional ideas are welcome.


As a side note, it might be a good idea to implement isEmpty() in the proxy for SetFactoryBean, ListFactoryBean, and MapFactoryBean in any case.

lennartjuette commented 1 year ago

I may have stumbled upon a similar problem in a SAP Commerce project. In versuon 2211.10 SAP upgraded spring from 5.3.27 to 5.3.29. I see the same behaviour.

In my case I'm having issues with a list of beans, that implement a certain interface:

<util:list id="defaultAdapters" value-type="not.for.the.public.SomeAdapter">
        <ref bean="adapterA"/>
        <ref bean="adapterB"/>
        <ref bean="adapterC"/>
        <ref bean="adapterD"/>
</util:list>
<bean id="myBean"
          class="not.for.the.public.SomeInitializer">
        <property name="conditionsAdapters" ref="defaultAdapters"/>
    </bean>

Beans injecting the list defaultAdapters fail to create an instance in 5.3.29, which was working in 5.3.27:

Caused by: org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'jdk.proxy8.$Proxy317 implementing java.util.List' to required type 'java.util.List' for property 'conditionsAdapters'; nested exception is java.lang.IllegalStateException: Singleton instance not initialized yet.

[...]

Caused by: java.lang.IllegalStateException: Singleton instance not initialized yet at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.29.jar:5.3.29] at org.springframework.beans.factory.config.AbstractFactoryBean.getSingletonInstance(AbstractFactoryBean.java:188) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.config.AbstractFactoryBean.access$200(AbstractFactoryBean.java:63) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.config.AbstractFactoryBean$EarlySingletonInvocationHandler.invoke(AbstractFactoryBean.java:275) ~[spring-beans-5.3.29.jar:5.3.29] at jdk.proxy8.$Proxy317.isEmpty(Unknown Source) ~[?:?] at org.springframework.util.ObjectUtils.nullSafeConciseToString(ObjectUtils.java:981) ~[spring-core-5.3.29.jar:5.3.29] at org.springframework.core.convert.ConversionFailedException.(ConversionFailedException.java:52) ~[spring-core-5.3.29.jar:5.3.29] at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47) ~[spring-core-5.3.29.jar:5.3.29]

I'd be happy to test any potential fix.

sbrannen commented 1 year ago

Hi @lennartjuette,

I may have stumbled upon a similar problem in a SAP Commerce project. In versuon 2211.10 SAP upgraded spring from 5.3.27 to 5.3.29. I see the same behaviour.

Yes, that is the same issue, triggered by the use of <util:list /> in XML configuration.

I'd be happy to test any potential fix.

Thanks!

We'll post back here once we have a fix in place.

sbrannen commented 1 year ago

This has been addressed in 6.0.x in ea4105165119937d1da59704be053f4023d56a9c and backported to 5.3.x, for inclusion in 6.0.12 and 5.3.30.

Please try out the upcoming snapshots and let us know if you run into any further issues.

Thanks!

luisericlf commented 1 year ago

Very much appreciated @sbrannen!

We'll give it a shot!

sbrannen commented 1 year ago

We'll give it a shot!

Thanks, @luisericlf.

I also just noticed that this was the first issue you have created on GitHub -- "(their first ever)" as GitHub phrases it.

So, congratulations on that! 👍

lennartjuette commented 1 year ago

Not knowing where to look i searched and stumbled upon a 5.3.30-SNAPSHOT build from 08.09.2023: https://repo.spring.io/artifactory/snapshot/org/springframework/spring/5.3.30-SNAPSHOT/spring-5.3.30-20230908.144355-28-dist.zip

Using these jars worked for me on my local setup. I'd say the issue was fixed. Assuming that I picked the correct SNAPSHOT build, that is.

sbrannen commented 1 year ago

Not knowing where to look i searched and stumbled upon a 5.3.30-SNAPSHOT build from 08.09.2023: https://repo.spring.io/artifactory/snapshot/org/springframework/spring/5.3.30-SNAPSHOT/spring-5.3.30-20230908.144355-28-dist.zip

That looks like it's good.

FYI: documentation for how to use snapshots in your Gradle or Maven build can be found here.

Using these jars worked for me on my local setup. I'd say the issue was fixed. Assuming that I picked the correct SNAPSHOT build, that is.

Great!

Thanks for trying it out and letting us know.

rafikhamid commented 1 year ago

Hi @lennartjuette

I am having the exact same issue while upgrading to SAP Commerce Cloud 2211.12, the spring jars are in platform core and we cannot modify them while we are on the cloud. can you please tell me how did you replace the 5.3.29 with 5.3.27 ?

Thanks.

lennartjuette commented 1 year ago

That's actually not a spring issue, so you should ask this in the SAP support forums.

To answer your question: The jars have only been replaced manually on a local install for testing so far. I didn't bother to fiddle around with our regular builds and roll this out. You could set up a buildcallback that replace the jars, though.

My solution was to pin down my project on 2211.9, until a new spring release has been released and SAP catches up with that in their next release.

rafikhamid commented 1 year ago

@lennartjuette Thank you, I rasied a SAP support ticket, I also tested this by changing jars inside platform, and it worked. I will too suggest to my client to stick to 2211.9 so far, thanks for the quick reply. Regards.

sbrannen commented 1 year ago

until a new spring release has been released

FYI: Spring Framework 6.0.12 and 5.3.30 were both released today.

rafikhamid commented 1 year ago

@sbrannen Thanks for the info, but @lennartjuette meant that we will wait until SAP Commerce releases a patch using Spring Framework 5.3.30 or higher. because SAP Commerce does not provide a "standard" way to choose dependencies versions.

lennartjuette commented 1 year ago

@rafikhamid is right: As Spring is a) a dependency of SAP Commerce as platform, so it's their issue to deal with and b) it's not exactly trivial to override dependencies, they will have to fix is quickly it for the bulk of all SAP Commerce users.

Thanks @sbrannen, I'll give the SAP guys a hint, so they can test this and pull the new Spring jars into their release. Hopefully you'll don't have to deal with any more of us SAP CC devs here ;) Thanks for the support!