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

众所周知,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

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

MyBeanB仅实例化一次。
为了为@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。

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

附言

我已阅读文档:

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

但我不清楚。

更新资料

情况3:

我研究了另一种情况,其中从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 {

在这种情况下,输出为:

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

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

MyBeanB被实例化3次。
为了为@Transactional添加MyBeanB功能,Spring使用了JDK动态代理。

参考方案

@Transactional行为生成的代理服务器的作用与作用域代理不同。

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

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

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

就我们的目的而言,您基本上可以忽略其行为(删除@Transactional,您应该会看到相同的行为,除非您没有cglib代理)。

@Scope proxy的行为有所不同。该文档指出:

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

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

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

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

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

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

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

实际上,这是Spring为满足MyBeanB注入目标所做的工作。

在第一个示例中,

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

您声明一个prototype bean定义(通过注释)。 @Autowired具有@Scope元素,该元素

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

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

因此,Spring不会为生成的bean创建作用域代理。你用

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

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

如果再次使用MyBeanB,Spring将返回一个新实例,因为bean定义用于prototype bean。您没有这样做,因此所有方法调用都转到同一个对象。

在第二个示例中

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

您声明通过cglib实现的作用域代理。当从Spring请求这种类型的bean时

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

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

尝试跑步

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

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

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

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

Java-非泛型类扩展了泛型类 - java

我想知道如何(如果可能)创建一个类,它是泛型类的特定类型。具体来说,我有一个实现所有必需方法的abstract class Stack<Type>,我想有一个class StackInteger,而StackInteger的实例也是Stack<Integer>的实例。我看到我可以做这样的事情:class StackInteger { …

Java:线程池如何将线程映射到可运行对象 - java

试图绕过Java并发问题,并且很难理解线程池,线程以及它们正在执行的可运行“任务”之间的关系。如果我创建一个有10个线程的线程池,那么我是否必须将相同的任务传递给池中的每个线程,或者池化的线程实际上只是与任务无关的“工人无人机”可用于执行任何任务?无论哪种方式,Executor / ExecutorService如何将正确的任务分配给正确的线程? 参考方案 …

Java-关于使用具有静态成员的类vs. - java

所以我有一个关于最佳实践的问题。基本上,我正在执行以下操作以简化访问不同类的成员的操作:class myClass1 { public static int var1; public static String var2; //... public static void method1() { //... } } 然后在其他类中,我只能使用myClass1…

JAVA:字节码和二进制有什么区别? - java

java字节代码(已编译的语言,也称为目标代码)与机器代码(当前计算机的本机代码)之间有什么区别?我读过一些书,他们将字节码称为二进制指令,但我不知道为什么。 参考方案 字节码是独立于平台的,在Windows中运行的编译器编译的字节码仍将在linux / unix / mac中运行。机器代码是特定于平台的,如果在Windows x86中编译,则它将仅在Win…

java:继承 - java

有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…