After upgrading from spring boot 3.5.7 to 4.0.0 I am unable to get beans from BeanFactory.
I have classes A1, A2, A3... implementing interface A. In old code implementation of A is selected at runtime dynamically based on request context using BeanFactory:
public A getA(String version) {
return switch (version) {
...
case "5" -> beanFactory.getBean(A5.class);
}
}
This code now throws BeanNotOfRequiredTypeException because actual type is class jdk.proxy1.$Proxy....
When I add annotation @ Proxyable(TARGET_CLASS) to all my A1..An classes, everything starts working as previously.
Below fully working example with incorrect behavior with slightly different exception, but I believe this is the same root cause:
package com.example.demo;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.resilience.annotation.EnableResilientMethods;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Component;
@EnableResilientMethods
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
interface A {
int getValue();
}
@Component
class A1 implements A {
@Retryable
@Override
public int getValue() {
return 1;
}
}
@Component
class A2 implements A {
@Retryable
@Override
public int getValue() {
return 2;
}
}
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
try {
BeanFactory beanFactory = ctx;
var myA = beanFactory.getBean(A1.class);
System.out.println(myA.getClass());
System.out.println(myA.getValue());
} catch (Exception ex) {
ex.printStackTrace();
}
};
}
}
After commenting out '@EnableResilientMethods' - everything works.
Comment From: rafaljaw
If I add @ Lazy to my bean then I see the error more similar to the one I saw in my original code when first time saw it but which obviously I cannot share because it is part of bigger code base I am working on:
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'com.example.demo.DemoApplication$A1' is expected to be of type 'com.example.demo.DemoApplication$A1' but was actually of type 'com.example.demo.$Proxy62'
at org.springframework.beans.factory.support.AbstractBeanFactory.adaptBeanInstance(AbstractBeanFactory.java:418)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1626)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1581)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:556)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:384)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:377)
Comment From: jhoeller
If this is caused by @EnableResilientMethods, does @EnableResilientMethods(proxyTargetClass=true) help? That forces it to not consider interface proxies by default. I wonder why this is not happening by default in Boot against the default proxy config there - in Boot, I'd expect that flag to effectively default to true.
Other than that, your use of @Proxyable is actually as intended, expressing that your bean class always needs to be proxied with its full class despite implementing a service interface.
Comment From: rafaljaw
You are right, @EnableResilientMethods(proxyTargetClass = true)also helps, thanks for pointing this. I was expecting this should work no matter what value will be used for proxyTargetClass.
Comment From: jhoeller
It looks like we got a gap with Boot's default setup since this issue has effectively been raised before in https://github.com/spring-projects/spring-framework/issues/35286#issuecomment-3323784653 - this defaulting works on the Framework side, we'll need to investigate this on the Boot side.
Comment From: jhoeller
It turns out that this does work with a direct RetryAnnotationBeanPostProcessor definition but not with @EnableResilientMethods due to a mismatch on the Framework side. To be fixed for 7.0.2.