Spring中的作用域代理是什么? [英] What is a scoped proxy in Spring?

查看:82
本文介绍了Spring中的作用域代理是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

As we know Spring uses proxies to add functionality (@Transactional and @Scheduled for example). 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. I always thought that proxyMode allows me to choose between a JDK dynamic proxy and CGLIB.

但是我能够创建一个示例,说明我的假设是错误的:

But I was able to create an example which shows that my assumption is wrong:

单例:

@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

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

Here we can see two things:

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

情况2:

让我更正MyBeanB定义:

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

在这种情况下,输出为:

In this case the output is:

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被实例化了 3 次.
  2. 为了添加MyBeanB@Transactional功能,Spring使用了CGLIB.
  1. MyBeanB was instantiated 3 times.
  2. To add the @Transactional functionality for MyBeanB, Spring used CGLIB.

您能解释发生了什么吗?代理模式如何真正起作用?

Could you explain what is going on? How does proxy mode really work?

我已阅读文档:

/**
 * 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
 */

但是我不清楚.

我研究了另一种情况,其中我从MyBeanB中提取了接口:

I investigated one more case, in which I extracted the interface from 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:

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被实例化了 3 次.
  2. 为了添加MyBeanB@Transactional功能,Spring使用了JDK动态代理.
  1. MyBeanB was instantiated 3 times.
  2. To add the @Transactional functionality for MyBeanB, Spring used a JDK dynamic proxy.

推荐答案

@Transactional行为生成的代理的作用与作用域代理不同.

The proxy generated for @Transactional behavior serves a different purpose than the scoped proxies.

@Transactional代理是一种包装特定bean的代理,以添加会话管理行为.所有方法调用将在委派给实际bean之前和之后执行事务管理.

The @Transactional proxy is one that wraps the specific bean to add session management behavior. All method invocations will perform the transaction management before and after delegating to the actual bean.

如果您进行说明,它将看起来像

If you illustrate it, it would look like

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

出于我们的目的,您基本上可以忽略其行为(删除@Transactional,您应该会看到相同的行为,只是您没有cglib代理).

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).

The @Scope proxy behaves differently. The documentation states:

[...]您需要注入一个代理对象,该对象公开相同的内容 公共接口作为作用域对象,但它也可以检索 相关范围中的真实目标对象(例如HTTP请求) 然后委托方法调用真实对象.

[...] 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.

Spring真正在做的是为代表代理的工厂类型创建一个单例bean定义.但是,相应的代理对象会在每次调用时在上下文中查询实际的bean.

What Spring is really doing is creating a singleton bean definition for a type of factory representing the proxy. The corresponding proxy object, however, queries the context for the actual bean for every invocation.

如果您进行说明,它将看起来像

If you illustrate it, it would look like

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

由于MyBeanB是原型Bean,因此上下文将始终返回新实例.

Since MyBeanB is a prototype bean, the context will always return a new instance.

出于此答案的目的,假定您直接使用

检索了MyBeanB.

For the purposes of this answer, assume you retrieved the MyBeanB directly with

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

实质上是Spring为满足@Autowired注入目标所要做的.

which is essentially what Spring does to satisfy an @Autowired injection target.

在第一个示例中,

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

您声明原型bean 定义(通过注释). @Scope 具有

You declare a prototype bean definition (through the annotations). @Scope has a proxyMode element which

指定是否应将组件配置为作用域代理 如果是这样,则代理服务器应基于接口还是基于接口 基于子类.

Specifies whether a component should be configured as a scoped proxy and if so, whether the proxy should be interface-based or subclass-based.

默认为ScopedProxyMode.DEFAULT,通常表示没有 除非创建了不同的默认值,否则应该创建作用域代理 在组件扫描指令级别配置.

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.

因此,Spring不会为生成的bean创建一个作用域代理.您可以使用

So Spring is not creating a scoped proxy for the resulting bean. You retrieve that bean with

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

您现在可以引用由Spring创建的新MyBeanB对象.就像任何其他Java对象一样,方法调用将直接转到引用的实例.

You now have a reference to a new MyBeanB object created by Spring. This is like any other Java object, method invocations will go directly to the referenced instance.

如果再次使用getBean(MyBeanB.class),Spring将返回一个新实例,因为Bean定义用于

If you used getBean(MyBeanB.class) again, Spring would return a new instance, since the bean definition is for a prototype bean. You're not doing that, so all your method invocations go to the same object.

在第二个示例中,

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

您声明通过cglib实现的作用域代理.当通过Spring向

you declare a scoped proxy that is implemented through cglib. When requesting a bean of this type from Spring with

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

Spring知道MyBeanB是有作用域的代理,因此返回一个满足MyBeanB API(即实现其所有公共方法)的代理对象,该对象内部知道如何检索类型为MyBeanB的实际bean.对于每个方法调用.

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.

尝试运行

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

这将返回true,暗示Spring正在返回一个单例代理对象(不是原型bean).

This will return true hinting to the fact that Spring is returning a singleton proxy object (not a prototype bean).

在代理实现内部的方法调用上,Spring将使用特殊的getBean版本,该版本知道如何区分代理定义和实际的MyBeanB bean定义.这将返回一个新的MyBeanB实例(因为它是原型),Spring会通过反射(经典的Method.invoke)将方法调用委托给它.

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. 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).

您的第三个示例与您的第二个示例基本相同.

Your third example is essentially the same as your second.

这篇关于Spring中的作用域代理是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆