Java 为什么不';当我按类(而不是接口)查找JDK动态代理包装的bean时,我是否遇到任何异常? 让我们考虑下面的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种不同用法: 用法1:
在这种情况下,应用程序无法启动并打印:Java 为什么不';当我按类(而不是接口)查找JDK动态代理包装的bean时,我是否遇到任何异常? 让我们考虑下面的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种不同用法: 用法1:,java,spring,spring-boot,spring-aop,dynamic-proxy,Java,Spring,Spring Boot,Spring Aop,Dynamic Proxy,在这种情况下,应用程序无法启动并打印: *************************** 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.MyBe
***************************
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为beanMyBeanB
创建JDK动态代理,而该代理不是MyBeanB的子类型。我们可以这样轻松地修复它:
@Service
public class MyBeanA {
@Autowired
private MyBeanBInterface myBeanB;
....
}
用法2:
令人惊讶的是,它在没有任何运行时异常的情况下工作,但我希望在这种情况下看到NoSuchBeanDefinitionException
,因为int case1应用程序无法启动
感谢评论中的家伙-我检查了beanB
的类,它是my.pack.MyBeanB$$EnhancerBySpringCGLIB$$b1346261
,因此Spring使用CGLIB创建代理,但它与bean定义(@Scope(value=“prototype”,proxyMode=ScopedProxyMode.INTERFACES
)相矛盾,看起来像个bug。
)
你能解释为什么它适用于案例2而不适用于案例1吗?正如我在对Spring的评论中向你解释的那样,AOP可以根据情况同时使用CGLIB和JDK代理。默认情况下是用于实现接口的类的JDK代理,但是您也可以为它们强制使用CGLIB。对于未实现接口的类,只保留CGLIB,因为JDK代理只能基于接口创建动态代理 因此,在案例1中,您明确表示需要接口代理,即JDK代理:
@Scope(value=“prototype”,proxyMode=ScopedProxyMode.INTERFACES)
但是MyBeanA
没有实现任何接口。因此,您会得到在本例中看到的错误消息
然而,在案例2中,您使用ApplicationContext.getBean(..)
来创建代理。在这里,您依靠Spring来决定选择哪种代理类型,而不是试图强制执行任何操作。因此,通过CGLIB代理成功
这一点也不奇怪
如果要避免案例1中的错误消息,可能应该使用ScopedProxyMode.TARGET\u CLASS
更新:对不起,我被你类似的、不起眼的类名
MyBeanA
和MyBeanB
激怒了。下次使用更具描述性、更简洁的类名称是有意义的,最理想的是那些描述场景中类的角色的类名称,如MyService
,MyInterface
,MyScopedBean
无论如何,我再次阅读了你的问题和错误消息。错误消息表示,根据您的注释,正在生成一个基于接口的代理,但您正试图将其注入到类类型中。您可以通过如下方式声明它来修复此问题:
@Service
public class MyBeanA {
@Autowired
private MyBeanBInterface myBeanB;
....
}
@Autowired
私有myBeanB接口myBeanB;
在case/usage 2中,您再次显式声明了一个类,而不是bean的接口类型。正如我所说,Spring试图通过唯一可能的方式满足您的需求,即为类创建CGLIB代理。您可以通过声明接口类型来修复此问题,并将获得预期的JDK代理:
MyBeanInterface MyBeanInterface=appContext.getBean(MyBeanInterface.class);
System.out.println(myBeanBInterface.getCounter());
System.out.println(myBeanBInterface.getClass());
更新2:根据您的评论,我认为您仍然不理解OOP的基本事实:如果您有
- class
和classBase
或Sub扩展Base
- 接口
和类Base
子实现Base
Base b=new-Base()
,但当然不能声明Sub s=new-Base()
,因为Sub
也是Base
,但不是每个Base
都是Sub
。例如,如果您还有OtherSub扩展Base
,则在尝试将Base
对象分配给Sub
变量时,它可能是OtherSub
实例。这就是为什么dot甚至不使用Sub s=(Sub)myBaseObject
进行编译
到目前为止,一切顺利。现在再次查看您的代码:
在用法1中,您拥有@Autowired private MyBeanB MyBeanB
但配置了MyBeanB
以生成JDK代理,即将创建一个新的代理类,其父类proxy
直接实现MyBeanB接口
。也就是说,您有两个不同的类,它们都直接实现相同的接口。由于我上面解释的原因,这些类彼此不兼容。关于接口,我们有类层次结构
mybeanb接口
MyBeanB
MyBeanB_JDKProxy
因此,您不能将MyBeanB\u JDKProxy
注入MyBeanB
字段,因为代理对象不是MyBeanB
的实例。你不明白吗?问题就在电脑前面,没有神秘的Spring bug。您已将其配置为失败
这就是为什么我告诉您将代码更改为@Autowired private myBeanB接口myBeanB代码>因为它当然可以工作,因为代理实现了接口,而且一切都很好。我还告诉过你,或者你可以保留@Autowired private MyBeanB MyBeanB代码>如果使用范围声明的proxyMode=ScopedProxyMode.TARGET\u CLASS
在用法2中,问题是相同的:你说的是getBean(ClassB.class)
,也就是说,你明确指示Spring为该类创建一个代理。但是对于一个类,您不能创建JDK代理,只能创建CGLIB代理,这就是Spring所做的。同样,我通过指示您使用getBean(MyBeanBInterface.class)
给出了解决方案。然后得到预期的JDK代理
春天足够聪明,两者兼而有之
- 使JDK代理处于用法1中
MyBeanB beanB = context.getBean(MyBeanB.class);
System.out.println(beanB.getCounter());