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

标签 java spring proxy singleton dynamic-proxy

我们知道 Spring 使用代理来添加功能(例如 @Transactional@Scheduled)。有两种选择 - 使用 JDK 动态代理(该类必须实现非空接口(interface)),或使用 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;
    }
}

原型(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;
    }
}

主营:
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。

  • 案例二:

    让我更正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 中提取了接口(interface)。 :
    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 以添加 session 管理行为的代理。所有方法调用都将在委托(delegate)给实际 bean 之前和之后执行事务管理。

    如果你说明它,它看起来像

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

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

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

    [...] 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 的上下文。

    如果你说明它,它看起来像
    main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)
    

    由于MyBeanB是一个原型(prototype)bean,上下文总是会返回一个新的实例。

    出于此答案的目的,假设您检索到 MyBeanB直接用
    MyBeanB beanB = context.getBean(MyBeanB.class);
    

    这基本上是 Spring 为满足 @Autowired 所做的工作。注入(inject)目标。

    在你的第一个例子中,

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

    您声明 prototype bean定义(通过注释)。 @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.



    所以 Spring 没有为生成的 bean 创建一个作用域代理。你检索那个bean
    MyBeanB beanB = context.getBean(MyBeanB.class);
    

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

    如果您使用 getBean(MyBeanB.class)再次,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 知道MyBeanB是一个作用域代理,因此返回一个满足 MyBeanB 的 API 的代理对象(即实现其所有公共(public)方法)内部知道如何检索 MyBeanB 类型的实际 bean对于每个方法调用。

    尝试运行

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

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

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

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

    关于java - Spring 中的作用域代理是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58170627/

    相关文章:

    security - 反向代理和网关有什么区别?

    java - Socksifying Java ServerSocket - 如何处理

    java - 使用父级对链式对象进行排序

    java - 如何为 Eclipse Mars 正确设置 JavaFX?

    java - 为什么绑定(bind)项目中没有生成某些类?

    java - Spring Boot - GET( cucumber )的 MockMvc 结果为 null

    java - 如何获取访问服务器的用户的IP地址

    java - Spring 安全 3 : Is it possible to see which AuthenticationProvider has authenticated a session?

    authentication - 使用基本身份验证通过 HTTP 代理访问 HTTPS

    java游戏如何构造定时效果/定距效果?