为什么当我按类(而不是接口)包装由JDK动态代理包装的查找bean时,为什么没有遇到任何异常? [英] Why don't I experience any exception when I lookup bean wrapped by JDK dynamic proxy by class(instead of interface)?

查看:135
本文介绍了为什么当我按类(而不是接口)包装由JDK动态代理包装的查找bean时,为什么没有遇到任何异常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们考虑以下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.

更新:抱歉,您的类名MyBeanAMyBeanB相似而又不被我激怒.下次使用更具描述性的,类似干净代码的类名是很有意义的,理想情况下,使用类名称来描述场景中类的角色,如MyServiceMyInterfaceMyScopedBean.

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和class Sub extends Base
  • 接口Base和类Sub implements Base
  • class Base and class Sub extends Base or
  • interface Base and class Sub 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆