简体   繁体   English

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

[英]What is a scoped proxy in Spring?

As we know Spring uses proxies to add functionality ( @Transactional and @Scheduled for example).我们知道 Spring 使用代理来添加功能(例如@Transactional@Scheduled )。 There are two options - using a JDK dynamic proxy (the class has to implement non-empty interfaces), or generating a child class using the CGLIB code generator.有两种选择 - 使用 JDK 动态代理(class 必须实现非空接口),或使用 CGLIB 代码生成器生成子 class。 I always thought that proxyMode allows me to choose between a JDK dynamic proxy and CGLIB.我一直认为 proxyMode 允许我在 JDK 动态代理和 CGLIB 之间进行选择。

But I was able to create an example which shows that my assumption is wrong:但是我能够创建一个示例来表明我的假设是错误的:

Case 1:情况1:

Singleton: Singleton:

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

    public void foo() {
        System.out.println(myBeanB.getCounter());
    }

    public MyBeanB getMyBeanB() {
        return myBeanB;
    }
}

Prototype:原型:

@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;
    }
}

Main:主要的:

MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());

Output: Output:

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

Here we can see two things:在这里我们可以看到两件事:

  1. MyBeanB was instantiated only once . MyBeanB仅被实例化一次
  2. To add the @Transactional functionality for MyBeanB , Spring used CGLIB.要为MyBeanB添加@Transactional功能,Spring 使用了 CGLIB。

Case 2:案例二:

Let me correct the MyBeanB definition:让我更正MyBeanB定义:

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {

In this case the output is:在这种情况下,output 是:

constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2

Here we can see two things:在这里我们可以看到两件事:

  1. MyBeanB was instantiated 3 times. MyBeanB被实例化了3次。
  2. To add the @Transactional functionality for MyBeanB , Spring used CGLIB.要为MyBeanB添加@Transactional功能,Spring 使用了 CGLIB。

Could you explain what is going on?你能解释一下发生了什么吗? How does proxy mode really work?代理模式如何真正起作用?

PS附言

I've read the documentation:我已阅读文档:

/**
 * Specifies whether a component should be configured as a scoped proxy
 * and if so, whether the proxy should be interface-based or subclass-based.
 * <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
 * that no scoped proxy should be created unless a different default
 * has been configured at the component-scan instruction level.
 * <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
 * @see ScopedProxyMode
 */

but it is not clear for me.但我不清楚。

Update更新

Case 3:案例3:

I investigated one more case, in which I extracted the interface from MyBeanB :我调查了另外一个案例,其中我从MyBeanB中提取了接口:

public interface MyBeanBInterface {
    long getCounter();
}



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


@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanB implements MyBeanBInterface {

and in this case the output is:在这种情况下,output 是:

constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92

Here we can see two things:在这里我们可以看到两件事:

  1. MyBeanB was instantiated 3 times. MyBeanB被实例化了3次。
  2. To add the @Transactional functionality for MyBeanB , Spring used a JDK dynamic proxy.要为MyBeanB添加@Transactional功能,Spring 使用了 JDK 动态代理。

The proxy generated for @Transactional behavior serves a different purpose than the scoped proxies.@Transactional行为生成的代理与作用域代理的用途不同。

The @Transactional proxy is one that wraps the specific bean to add session management behavior. @Transactional代理是一种包装特定 bean 以添加 session 管理行为的代理。 All method invocations will perform the transaction management before and after delegating to the actual bean.所有方法调用都将在委托给实际 bean 之前和之后执行事务管理。

If you illustrate it, it would look like如果你说明它,它看起来像

main -> getCounter -> (cglib-proxy -> MyBeanB)

For our purposes, you can essentially ignore its behavior (remove @Transactional and you should see the same behavior, except you won't have the cglib proxy).出于我们的目的,您基本上可以忽略它的行为(删除@Transactional ,您应该会看到相同的行为,除非您没有 cglib 代理)。

The @Scope proxy behaves differently. @Scope代理的行为不同。 The documentation states:该文档指出:

[...] you need to inject a proxy object that exposes the same public interface as the scoped object but that can also retrieve the real target object from the relevant scope (such as an HTTP request) and delegate method calls onto the real object. [...] you need to inject a proxy object that exposes the same public interface as the scoped object but that can also retrieve the real target object from the relevant scope (such as an HTTP request) and delegate method calls onto the real object .

What Spring is really doing is creating a singleton bean definition for a type of factory representing the proxy. Spring 真正做的是为代表代理的工厂类型创建 singleton bean 定义。 The corresponding proxy object, however, queries the context for the actual bean for every invocation.然而,相应的代理 object 会为每次调用查询实际 bean 的上下文。

If you illustrate it, it would look like如果你说明它,它看起来像

main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)

Since MyBeanB is a prototype bean, the context will always return a new instance.由于MyBeanB是一个原型 bean,因此上下文将始终返回一个新实例。

For the purposes of this answer, assume you retrieved the MyBeanB directly with出于此答案的目的,假设您直接检索了MyBeanB

MyBeanB beanB = context.getBean(MyBeanB.class);

which is essentially what Spring does to satisfy an @Autowired injection target.这本质上是 Spring 为满足@Autowired注入目标所做的。


In your first example,在你的第一个例子中,

@Service
@Scope(value = "prototype")
public class MyBeanB { 

You declare a prototype bean definition (through the annotations).您声明一个原型 bean定义(通过注释)。 @Scope has a proxyMode element which @Scope有一个proxyMode元素

Specifies whether a component should be configured as a scoped proxy and if so, whether the proxy should be interface-based or subclass-based.指定是否应将组件配置为作用域代理,如果是,则代理应基于接口还是基于子类。

Defaults to ScopedProxyMode.DEFAULT , which typically indicates that no scoped proxy should be created unless a different default has been configured at the component-scan instruction level.默认为ScopedProxyMode.DEFAULT ,这通常表示不应创建范围代理,除非在组件扫描指令级别配置了不同的默认值。

So Spring is not creating a scoped proxy for the resulting bean.所以 Spring 没有为生成的 bean 创建作用域代理。 You retrieve that bean with你检索那个bean

MyBeanB beanB = context.getBean(MyBeanB.class);

You now have a reference to a new MyBeanB object created by Spring.您现在可以引用由 Spring 创建的新MyBeanB object。 This is like any other Java object, method invocations will go directly to the referenced instance.这就像任何其他 Java object 一样,方法调用将 go 直接指向引用的实例。

If you used getBean(MyBeanB.class) again, Spring would return a new instance, since the bean definition is for a prototype bean .如果您再次使用getBean(MyBeanB.class) , Spring 将返回一个新实例,因为 bean 定义是针对原型 bean的。 You're not doing that, so all your method invocations go to the same object.你没有这样做,所以你所有的方法调用 go 到同一个 object。


In your second example,在你的第二个例子中,

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {

you declare a scoped proxy that is implemented through cglib.你声明了一个通过 cglib 实现的作用域代理。 When requesting a bean of this type from Spring with当从 Spring 请求这种类型的 bean 时

MyBeanB beanB = context.getBean(MyBeanB.class);

Spring knows that MyBeanB is a scoped proxy and therefore returns a proxy object that satisfies the API of MyBeanB (ie. implements all its public methods) that internally knows how to retrieve an actual bean of type MyBeanB for each method invocation. Spring 知道MyBeanB是一个作用域代理,因此返回一个满足 MyBeanB 的 API 的代理MyBeanB (即,每个调用实现内部知道如何检索其所有实际 bean 类型的MyBeanB方法)。

Try running尝试运行

System.out.println("singleton?: " + (context.getBean(MyBeanB.class) == context.getBean(MyBeanB.class)));

This will return true hinting to the fact that Spring is returning a singleton proxy object (not a prototype bean).这将返回true的暗示,即 Spring 正在返回 singleton 代理 object (不是原型 bean)。

On a method invocation, inside the proxy implementation, Spring will use a special getBean version that knows how to distinguish between the proxy definition and the actual MyBeanB bean definition.在方法调用中,在代理实现中,Spring 将使用一个特殊的getBean版本,该版本知道如何区分代理定义和实际的MyBeanB bean 定义。 That'll return a new MyBeanB instance (since it's a prototype) and Spring will delegate the method invocation to it through reflection (classic Method.invoke ).这将返回一个新的MyBeanB实例(因为它是原型),并且 Spring 将通过反射(经典Method.invoke )将方法调用委托给它。


Your third example is essentially the same as your second.您的第三个示例与您的第二个示例基本相同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM