java - 为什么当我按类(而不是接口(interface))包装由JDK动态代理包装的查找bean时,为什么没有遇到任何异常?

标签 java spring spring-boot spring-aop dynamic-proxy

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

@Service
public class MyBeanA {
    @Autowired
    private MyBeanB myBeanB;   
    ....
}


在这种情况下,无法启动应用程序并打印:

***************************
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的子类型。我们可以像这样轻松地解决它:

@Service
public class MyBeanA {
    @Autowired
    private MyBeanBInterface myBeanB;   
    ....
}


用法2:

MyBeanB beanB = context.getBean(MyBeanB.class);
System.out.println(beanB.getCounter());


令我惊讶的是,它没有任何运行时异常,但我希望在这种情况下看到NoSuchBeanDefinitionException,因为int情况1应用程序无法启动

谢谢大家的意见-我检查了beanB的类,它是my.pack.MyBeanB$$EnhancerBySpringCGLIB$$b1346261,因此Spring使用CGLIB创建代理,但是它与bean定义(@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)相矛盾,并且看起来像个bug。


您能解释一下为什么它不适用于情况2吗?

最佳答案

正如我在对other question的评论中向您解释的那样,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_CLASS



更新:抱歉,您对类似且不起眼的类名称MyBeanAMyBeanB感到很恼火。下次使用更具描述性的,类似干净代码的类名是很有意义的,理想情况下,是在您的方案中描述角色的类,如MyServiceMyInterfaceMyScopedBean

无论如何,我会再次阅读您的问题和错误消息。该错误消息表明,根据您的注释,正在生成基于接口的代理,但是您尝试将其注入类类型中。您可以通过这样声明来解决此问题:

@Autowired
private MyBeanBInterface myBeanB;


在案例2中,您再次明确声明了bean的类而不是接口类型。就像我说的那样,Spring尝试通过唯一的方式来满足您的需求,即为该类创建一个CGLIB代理。您可以通过声明接口类型来解决此问题,然后将获得预期的JDK代理:

MyBeanBInterface myBeanBInterface = appContext.getBean(MyBeanBInterface.class);
System.out.println(myBeanBInterface.getCounter());
System.out.println(myBeanBInterface.getClass());




更新2:我认为根据您的评论您仍然不明白的是OOP的基本事实:如果您有


Base和类Sub extends Base
接口Base和类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的情况下甚至可以编译的原因。

到目前为止,一切都很好。现在再次查看您的代码:

在用法1中,您具有@Autowired private MyBeanB myBeanB;但已配置MyBeanB来生成JDK代理,即将创建一个具有直接实现Proxy的父类MyBeanBInterface的新代理类。即您有两个不同的类,两个都直接实现相同的接口。由于我上面解释的原因,这些类是彼此不兼容的。关于接口,我们具有类层次结构

MyBeanBInterface
  MyBeanB
  MyBeanB_JDKProxy


因此,不能将MyBeanB_JDKProxy注入MyBeanB字段,因为代理对象不是MyBeanB的实例。你不懂吗问题出在电脑前,没有神秘的Spring错误。您配置它失败。

这就是为什么我告诉您将代码更改为@Autowired private MyBeanBInterface myBeanB;的原因,因为它当然可以工作,因为代理实现了接口并且一切都很好。我还告诉您,或者,如果将@Autowired private MyBeanB myBeanB;用于范围声明,则可以保留proxyMode = ScopedProxyMode.TARGET_CLASS

在用法2中,问题是相同的:您说的是getBean(ClassB.class),即您明确指示Spring为该类创建代理。但是对于一个类,您不能创建JDK代理,而只能创建CGLIB代理,而Spring正是这样做的。再次,我通过指示您改用getBean(MyBeanBInterface.class)为您提供了解决方案。然后,您将获得预期的JDK代理。

春天对双方都足够聪明


使JDK代理在使用中1找到作用域服务bean MyClassB并对其进行委托方法调用(注意:委托,而不是继承!),以及
使CGLIB代理扩展MyClassB(注意:此处继承,无需委派)。

关于java - 为什么当我按类(而不是接口(interface))包装由JDK动态代理包装的查找bean时,为什么没有遇到任何异常?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58221152/

相关文章:

java - Spring MVC 后端返回 404 (Rest)

spring-boot - 如何禁用transport.failover.FailoverTransport 成功连接到tcp ://localhost:61616 message

java - 为什么 ArrayList 中的值总是返回 null 作为 Java 中的第一个值?

java - 持久化数据的 DBUnit 问题

java - OpenCV 中相机捕获的图像为全黑

java - Quartz Scheduler (cron) 能否在同一执行时间调度多个作业?

java - Ribbon 客户端无法发现在 eureka 中注册的微服务

spring - Spring Boot 2.0.5 中来自 PrometheusPushGateway 的 @PreDestroy 注释的 BeanCreationNotAllowedException

Java:向接口(interface) List<E> 添加一个新方法

java - 如何使用 websockets 从 mysql 获取实时通知更新?