为什么当我按类(而不是接口)包装由JDK动态代理包装的查找bean时,为什么没有遇到任何异常? [英] Why don't I experience any exception when I lookup bean wrapped by JDK dynamic proxy by class(instead of interface)?
问题描述
让我们考虑以下bean:
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;
}
}
并考虑2种不同的用法:
and consider 2 different usages:
@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.
我希望看到它,因为我要求spring为bean MyBeanB
创建JDK动态代理,并且该代理不是MyBeanB的子类型.我们可以像这样轻松地解决它:
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;
....
}
用法2:
MyBeanB beanB = context.getBean(MyBeanB.class);
System.out.println(beanB.getCounter());
令人惊讶的是,它在没有任何运行时异常的情况下都能正常工作,但我希望在这种情况下看到NoSuchBeanDefinitionException
,因为int case 1应用程序无法启动
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
感谢评论人员-我检查了beanB
类,它是my.pack.MyBeanB$$EnhancerBySpringCGLIB$$b1346261
,因此Spring使用CGLIB创建代理,但它与bean定义(@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES
)相矛盾,并且看起来像个bug.
)
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.
)
您能解释一下为什么它不适用于案例2的原因吗?
Could you explain why it is working for case 2 not working for case 1 ?
推荐答案
正如我在对其他问题的评论中向您解释的那样,Spring AOP可以根据情况使用CGLIB和JDK代理.默认是实现接口的类的JDK代理,但是您也可以为它们强制使用CGLIB.对于未实现接口的类,仅保留CGLIB,因为JDK代理只能基于接口创建动态代理.
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.
因此,在第一种情况下,您明确表示需要接口代理,即JDK代理:
So looking at your case 1, you explicitly say you want interface proxies, i.e. JDK proxies:
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
但是MyBeanA
没有实现任何接口.因此,您会收到在这种情况下看到的错误消息.
But MyBeanA
does not implement any interfaces. Consequently you get the error message you see in this case.
但是,在情况2中,您使用ApplicationContext.getBean(..)
来创建代理.在这里,您依靠Spring来确定选择哪种代理类型,而不是在尝试强制执行任何操作.因此,通过CGLIB代理成功.
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.
这里没有惊喜.
如果要避免出现情况1的错误消息,也许应该使用ScopedProxyMode.TARGET_CLASS
.
If you want to avoid the error message in case 1, maybe you ought to use ScopedProxyMode.TARGET_CLASS
.
更新:抱歉,您的类名MyBeanA
和MyBeanB
相似而又不被我激怒.下次使用更具描述性的,类似干净代码的类名是很有意义的,理想情况下,使用类名称来描述场景中类的角色,如MyService
,MyInterface
,MyScopedBean
.
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;
在案例2中,您再次明确声明了bean的类而不是接口类型.就像我说的那样,Spring尝试通过唯一的方式来满足您的需求,即为该类创建一个CGLIB代理.您可以通过声明接口类型来解决此问题,然后将获得预期的JDK代理:
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());
更新2:我认为根据您的评论您仍然不明白的是OOP的基本事实:如果有
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
和classSub extends Base
或 - 接口
Base
和类Sub implements Base
- class
Base
and classSub extends Base
or - interface
Base
and classSub implements Base
您可以声明Base b = new Sub()
,但不能声明Sub s = new Base()
,因为Sub
也是Base
,但并非每个Base
都是Sub
.例如,如果您也有OtherSub extends Base
,则在尝试将Base
对象分配给Sub
变量时,它可能是OtherSub
实例.这就是为什么即使不使用Sub s = (Sub) myBaseObject
也不编译的原因.
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:
在用法1 中,您具有@Autowired private MyBeanB myBeanB;
,但已配置MyBeanB
来生成JDK代理,即将创建一个具有直接实现MyBeanBInterface
的父类Proxy
的新代理类. IE.您有两个不同的类,两个都直接实现相同的接口.由于上面我所解释的原因,这些类相互之间是赋值不兼容的.关于接口,我们具有类层次结构
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
因此您不能将MyBeanB_JDKProxy
注入到MyBeanB
字段中,因为代理对象不是MyBeanB
的实例.你不懂吗问题出在电脑前,没有神秘的Spring错误.您将其配置为失败.
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.
这就是为什么我告诉您将代码更改为@Autowired private MyBeanBInterface myBeanB;
的原因,因为它当然可以工作,因为代理实现了接口并且一切都很好.我还告诉过您,或者,如果将proxyMode = ScopedProxyMode.TARGET_CLASS
用于范围声明,则可以保留@Autowired private MyBeanB myBeanB;
.
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.
在用法2 中,问题是相同的:您说的是getBean(ClassB.class)
,即您明确指示Spring为该类创建代理.但是对于一个类,您不能创建JDK代理,而只能创建CGLIB代理,而Spring正是这样做的.再次,我通过指示您使用getBean(MyBeanBInterface.class)
来提供了解决方案.然后,您将获得预期的JDK代理.
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
- 使使用中的JDK代理1找到有作用域的服务bean
MyClassB
并委托对其的方法调用(注意:委派,不是继承!)和 - 使CGLIB代理扩展
MyClassB
(注意:此处继承,无需委派).
- 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).
这篇关于为什么当我按类(而不是接口)包装由JDK动态代理包装的查找bean时,为什么没有遇到任何异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!