Spring 中的作用域代理是什么?

正如我们所知,Spring 使用代理来添加功能(@Transactional例如@Scheduled)。有两种选择 - 使用 JDK 动态代理(该类必须实现非空接口),或使用 CGLIB 代码生成器生成子类。我一直认为 proxyMode 允许我在 JDK 动态代理和 CGLIB 之间进行选择。


但我能够创建一个例子来表明我的假设是错误的:


情况1:

单例:


@Service

public class MyBeanA {

    @Autowired

    private MyBeanB myBeanB;


    public void foo() {

        System.out.println(myBeanB.getCounter());

    }


    public MyBeanB getMyBeanB() {

        return myBeanB;

    }

}

原型:


@Service

@Scope(value = "prototype")

public class MyBeanB {

    private static final AtomicLong COUNTER = new AtomicLong(0);


    private Long index;


    public MyBeanB() {

        index = COUNTER.getAndIncrement();

        System.out.println("constructor invocation:" + index);

    }


    @Transactional // just to force Spring to create a proxy

    public long getCounter() {

        return index;

    }

}

主要的:


MyBeanA beanA = context.getBean(MyBeanA.class);

beanA.foo();

beanA.foo();

MyBeanB myBeanB = beanA.getMyBeanB();

System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());

输出:


constructor invocation:0

0

0

counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e

在这里我们可以看到两件事:

  1. MyBeanB仅被实例化一次

  2. 为了添加 的@Transactional功能MyBeanB,Spring 使用了 CGLIB。

案例2:

让我纠正一下MyBeanB定义:


@Service

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

public class MyBeanB {

在这种情况下,输出是:


constructor invocation:0

0

constructor invocation:1

1

constructor invocation:2

counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2

在这里我们可以看到两件事:


MyBeanB被实例化3次。

为了添加 的@Transactional功能MyBeanB,Spring 使用了 CGLIB。

你能解释一下发生了什么事吗?代理模式到底如何工作?

呼啦一阵风
浏览 112回答 1
1回答

芜湖不芜

为行为生成的代理@Transactional与作用域代理具有不同的用途。代理@Transactional是一种包装特定 bean 以添加会话管理行为的代理。所有方法调用都将在委托给实际 bean 之前和之后执行事务管理。如果你举例说明的话,它看起来像main -> getCounter -> (cglib-proxy -> MyBeanB)出于我们的目的,您基本上可以忽略它的行为(删除@Transactional后您应该看到相同的行为,除非您没有 cglib 代理)。代理@Scope的行为有所不同。文档指出:[...]您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但也可以从相关作用域(例如 HTTP 请求)检索真实目标对象,并将方法调用委托给真实对象。Spring 真正做的是为代表代理的工厂类型创建一个单例 bean 定义。然而,相应的代理对象会在每次调用时查询上下文以获取实际的 bean。如果你举例说明的话,它看起来像main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)由于MyBeanB是原型 bean,上下文将始终返回一个新实例。出于本答案的目的,假设您MyBeanB直接使用MyBeanB beanB = context.getBean(MyBeanB.class);这本质上就是 Spring 为满足@Autowired注入目标所做的事情。在你的第一个例子中,@Service@Scope(value = "prototype")public class MyBeanB { 您声明原型 bean定义(通过注释)。@Scope有一个proxyMode元素指定组件是否应配置为作用域代理,如果是,则代理是否应基于接口或基于子类。默认为ScopedProxyMode.DEFAULT,这通常表示不应创建作用域代理,除非在组件扫描指令级别配置了不同的默认值。因此 Spring 不会为生成的 bean 创建作用域代理。您可以使用以下命令检索该 beanMyBeanB beanB = context.getBean(MyBeanB.class);您现在拥有对 Spring 创建的新对象的引用MyBeanB。这与任何其他 Java 对象一样,方法调用将直接转到引用的实例。如果再次使用getBean(MyBeanB.class),Spring 将返回一个新实例,因为 bean 定义是针对原型 bean的。您没有这样做,因此所有方法调用都会转到同一个对象。在你的第二个例子中,@Service@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)public class MyBeanB {您声明一个通过 cglib 实现的作用域代理。当从 Spring 请求这种类型的 bean 时MyBeanB beanB = context.getBean(MyBeanB.class);Spring知道这MyBeanB是一个作用域代理,因此返回一个满足API的代理对象MyBeanB(即实现其所有公共方法),该对象内部知道如何MyBeanB为每个方法调用检索实际的bean类型。尝试跑步System.out.println("singleton?: " + (context.getBean(MyBeanB.class) == context.getBean(MyBeanB.class)));这将返回true暗示 Spring 返回一个单例代理对象(而不是原型 bean)的事实。在代理实现内部的方法调用上,Spring 将使用一个特殊getBean版本,该版本知道如何区分代理定义和实际MyBeanBbean 定义。这将返回一个新MyBeanB实例(因为它是原型),Spring 将通过反射将方法调用委托给它(经典Method.invoke)。您的第三个示例与第二个示例基本相同。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java