当前位置: 首页 > 知识库问答 >
问题:

为什么我在按类(而不是接口)查找JDK动态代理包装的bean时没有经历任何异常?

管峻
2023-03-14

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

并考虑两种不同的用法:

@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为beanmybeanb创建JDK动态代理,而该代理不是mybeanb的子类型。我们可以这样轻松修复:

@Service
public class MyBeanA {
    @Autowired
    private MyBeanBInterface myBeanB;   
    ....
}
MyBeanB beanB = context.getBean(MyBeanB.class);
System.out.println(beanB.getCounter());

对我来说,令人惊讶的是,它可以在排除任何运行时异常的情况下工作,但我希望在这种情况下看到nosuchBeanDefinitionException,因为int case 1应用程序无法启动

感谢来自comments的guy--我检查了BeanB的类,它是My.Pack.MyBeanB$$EnhancerBySpringCglib$$B1346261,所以Spring使用CGLIB创建代理,但它与bean定义相矛盾(@scope(value=“prototype”,proxyMode=scopedProxyMode.interfaces)并且看起来像一个bug。)

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

共有1个答案

魏彦
2023-03-14

正如我在对另一个问题的评论中向您解释的,Spring AOP可以根据情况同时使用CGLIB和JDK代理。默认值是实现接口的类的JDK代理,但您也可以对它们强制使用CGLIB。对于不实现接口的类,只保留CGLIB,因为JDK代理只能基于接口创建动态代理。

因此,看看您的案例1,您明确地说您想要接口代理,即JDK代理:

@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)

但是mybeana不实现任何接口。因此,您将得到在本例中看到的错误消息。

这里没什么惊喜。

如果您想避免出现情况1中的错误消息,也许您应该使用scopedproxymode.target_class

更新:对不起,我被你们类似的、不伦不类名myBeanamyBeanb激怒了。下次使用更具描述性的、类似于干净代码的类名是有意义的,理想情况下是描述场景中类的角色的类名,如MyServiceMyInterfaceMyScopedBean

不管怎样,我又读了你的问题和错误信息。错误消息表示,根据您的注释,正在生成一个基于接口的代理,但您正在尝试将其注入到类类型中。您可以通过如下声明来修复此问题:

@Autowired
private MyBeanBInterface myBeanB;

在case/usage 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实现base

您可以声明Base=new Sub(),但当然不能声明Sub=new Base(),因为Sub也是Base,但并非每个Base都是Sub。例如,如果您也有othersubextendsbase,当尝试将base对象赋给sub变量时,它可能是othersub实例。这就是为什么dot甚至不使用Sub s=(Sub)MyBaseObject进行编译的原因。

到目前为止还不错。现在再看一遍您的代码:

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

MyBeanBInterface
  MyBeanB
  MyBeanB_JDKProxy

因此,您不能将mybeanb_jdkproxy注入mybeanb字段,因为代理对象不是mybeanb的实例。你还不明白吗?问题坐在电脑前,没有神秘的春虫。您将其配置为失败。

这就是为什么我告诉您将代码更改为@autowired private MyBeanBInterface mybeanb;的原因,因为这样当然可以工作,因为代理实现了接口,一切都很好。我还告诉您,如果您使用proxymode=scopedproxymode.target_class进行作用域声明,您也可以保留@autowired private MyBeanB;

在用法2中,问题是相同的:您说的是getBean(ClassB.Class),也就是说,您显式地指示Spring为该类创建一个代理。但是对于一个类,您不能创建一个JDK代理,只能创建一个CGLIB代理,这正是Spring所做的。同样,我给出了解决方案,指导您改用getBean(MyBeanBInterface.class)。然后您将获得所需的JDK代理。

Spring对两者都足够聪明

  • 使用法1中的JDK代理找到作用域服务beanmyclassb并将html" target="_blank">方法调用委托给它(注意:委托,而不是继承!)和
  • 使CGLIB代理扩展MyClassB(注意:此处为继承,不需要委派)。
 类似资料:
  • 问题内容: JDK Proxy类仅在工厂方法newProxyInstance()中接受接口。 是否有可用的解决方法或替代实施?如果我必须将方法提取到接口以使其能够与代理一起使用,则用例是有限的。我想包装它们以在运行时应用基于注释的动作。 问题答案: 您可以像这样使用cglib: 例如,这使您可以使用默认的实现方法来构建抽象类。但是您可以将增强器更改为所需的增强器。

  • 问题内容: 使用动态代理的用例是什么? 它们与字节码生成和反射有何关系? 有什么推荐的读物吗? 问题答案: 我强烈推荐此资源。 首先,您必须了解什么是代理模式用例。请记住,代理的主要目的是控制对目标对象的访问,而不是增强目标对象的功能。访问控制包括同步,身份验证,远程访问(RPC),惰性实例化(休眠,Mybatis),AOP(事务)。 与静态代理相反,动态代理生成在运行时需要Java反射的字节码。

  • 当我运行(Windows 7命令行)时: C:\rest-app\src\main\java\com\mycompany\app\test>java org.testng.testng testng.xml Suite1运行的测试总数:0,失败:0,跳过:0 ================================================== 此时我的testng.xml文件如下所

  • 问题内容: 如果是代理设计模式,那么JDK的动态代理和第三方动态代码生成API(例如CGLib)有什么区别? 使用这两种方法之间的区别是什么?何时应该优先选择另一种方法? 问题答案: JDK动态代理只能按接口进行代理(因此,您的目标类需要实现一个接口,然后该接口也可以由代理类实现)。 CGLIB(和javassist)可以通过子类化创建代理。在这种情况下,代理将成为目标类的子类。无需接口。 因此,

  • 在代理设计模式的情况下,jdk的动态代理和第三方的动态代码生成API(如cglib)有什么区别? 使用这两种方法之间有什么区别?什么时候应该选择一种方法而不是另一种方法?

  • 我知道在Java中,静态方法和实例方法一样是继承的,不同的是,当它们被重新声明时,父实现是隐藏的,而不是重写的。好吧,这有道理。但是,Java教程指出 接口中的静态方法从不继承。 然而,