问题
Lets consider following bean:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanB implements MyBeanBInterface {
private static final AtomicLong COUNTER = new AtomicLong(0);
private Long index;
public MyBeanB() {
index = COUNTER.getAndIncrement();
System.out.println("constructor invocation:" + index);
}
@Transactional
@Override
public long getCounter() {
return index;
}
}
and consider 2 different usages:
USAGE 1:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
....
}
At this case application can't be started and prints:
***************************
APPLICATION FAILED TO START
***************************
Description:
The bean 'myBeanB' could not be injected as a 'my.pack.MyBeanB' because it is a JDK dynamic proxy that implements:
my.pack.MyBeanBInterface
Action:
Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.
I expected to see it because I asked spring to create JDK dynamic proxy for bean MyBeanB
and that proxy is not a subtype of MyBeanB. We can easily fix it like this:
@Service
public class MyBeanA {
@Autowired
private MyBeanBInterface myBeanB;
....
}
USAGE 2:
MyBeanB beanB = context.getBean(MyBeanB.class);
System.out.println(beanB.getCounter());
Surprisingly for me it works wihtout any Runtime Exceptions but I expected to see NoSuchBeanDefinitionException
at this case because int case 1 application can't start
Thanks for guy from comments - I checked the class of beanB
and it is my.pack.MyBeanB$$EnhancerBySpringCGLIB$$b1346261
so Spring used CGLIB to create proxy but it contradicts the bean definition(@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES
) and looks like a bug.
)
Could you explain why it is working for case 2 not working for case 1 ?
回答1:
As I explained to you in my comments to the other question, Spring AOP can use both CGLIB and JDK proxies depending on the situation. The default are JDK proxies for classes implementing interfaces, but you can enforce CGLIB usage for them too. For classes not implementing interfaces only CGLIB remains because JDK proxies can only create dynamic proxies based on interfaces.
So looking at your case 1, you explicitly say you want interface proxies, i.e. JDK proxies:
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
But MyBeanA
does not implement any interfaces. Consequently you get the error message you see in this case.
In case 2 however you use ApplicationContext.getBean(..)
in order to create a proxy. Here you are relying on Spring to determine which proxy type to choose, you are not trying to enforce anything. Thus, proxying via CGLIB succeeds.
No surprises here.
If you want to avoid the error message in case 1, maybe you ought to use ScopedProxyMode.TARGET_CLASS
.
Update: Sorry, I was irritated by your similar and nondescript class names MyBeanA
and MyBeanB
. It would make sense to use more descriptive, clean-code-like class names next time, ideally ones describing the roles ob the classes in your scenario like MyService
, MyInterface
, MyScopedBean
.
Anyway, I read your question and the error message again. The error message says that according to your annotation an interface-based proxy is being generated but you are trying to inject it into a class type. You can fix that by declaring it like this:
@Autowired
private MyBeanBInterface myBeanB;
In case/usage 2 you are again explicitly declaring a class and not an interface type for your bean. So as I said, Spring tries to satisfy your requirement by the only way possible, i.e. creating a CGLIB proxy for the class. You can fix this by declaring an interface type and you will get the expected JDK proxy:
MyBeanBInterface myBeanBInterface = appContext.getBean(MyBeanBInterface.class);
System.out.println(myBeanBInterface.getCounter());
System.out.println(myBeanBInterface.getClass());
Update 2: Something I think you still do not understand according to your comments is this basic fact of OOP: If you have
- class
Base
and classSub extends Base
or - interface
Base
and classSub implements Base
you can declare Base b = new Sub()
but of course not Sub s = new Base()
because a Sub
is also a Base
, but not every Base
is a Sub
. For example, if you also have OtherSub extends Base
, when trying to assign a Base
object to a Sub
variable it could be an OtherSub
instance. This is why this does dot even compile without using Sub s = (Sub) myBaseObject
.
So far, so good. Now look at your code again:
In usage 1 you have @Autowired private MyBeanB myBeanB;
but configured MyBeanB
to produce a JDK proxy, i.e. a new proxy class with parent class Proxy
directly implementing MyBeanBInterface
will be created. I.e. you have two different classes, both directly implementing the same interface. Those classes are assignment-incompatible to each other for the reason I explained above. With regard to the interface we have the class hierarchy
MyBeanBInterface
MyBeanB
MyBeanB_JDKProxy
Thus you cannot inject MyBeanB_JDKProxy
into a MyBeanB
field because a proxy object is not an instance of MyBeanB
. Don't you understand? The problem sits in front of the computer, there is no mysterious Spring bug. You configured it to fail.
This is why I told you to change the code to @Autowired private MyBeanBInterface myBeanB;
because then of course it works because the proxy implements the interface and everything is fine. I also told you that alternatively you can keep @Autowired private MyBeanB myBeanB;
if you use proxyMode = ScopedProxyMode.TARGET_CLASS
for your scope declaration.
In usage 2 the problem is the same: You are saying getBean(ClassB.class)
, i.e. you are explicitly instructing Spring to create a proxy for that class. But for a class you cannot create a JDK proxy, only a CGLIB proxy, which is what Spring does. Again, I gave you the solution by instructing you to use getBean(MyBeanBInterface.class)
instead. Then you get the expected JDK proxy.
Spring is smart enough to both
- make the JDK proxy in usage 1 find the scoped service bean
MyClassB
and delegate method calls to it (note: delegation, not inheritance!) and - make the CGLIB proxy extend
MyClassB
(note: inheritance here, no delegation necessary).
来源:https://stackoverflow.com/questions/58221152/why-dont-i-experience-any-exception-when-i-lookup-bean-wrapped-by-jdk-dynamic-p