spring 为什么在将CGLIB prototype注入Singleton的情况下,每次访问prototype都会创建一个新对象?

dldeef67  于 2022-12-21  发布在  Spring
关注(0)|答案(2)|浏览(114)

我读过以下有趣的文章:Injecting Spring Prototype bean into Singleton bean

第一个案例:

    • 原型:**
@Service
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
class MessageBuilder {

    private static final AtomicInteger instanceCounter = new AtomicInteger(0);

    MessageBuilder() {
        instanceCounter.incrementAndGet();
    }

    static int getInstanceCounter() {
        return instanceCounter.get();
    }
    ....
}
    • 单胎:**
@Service
class MessageService {

    private final MessageBuilder messageBuilder;

    MessageService(MessageBuilder messageBuilder) {
        this.messageBuilder = messageBuilder;
    }

    Message createMessage(String content, String receiver) {
        return messageBuilder
                .withContent(content)
                .withReceiver(receiver)
                .build();
    }     
}
    • 测试:**
@RunWith(SpringRunner.class)
@SpringBootTest
public class MessageServiceTest {

    @Autowired
    private MessageService messageService;

    @Test
    public void shouldCreateTwoBuilders() throws Exception {
        //when
        messageService.createMessage("text", "alice");
        messageService.createMessage("msg", "bob");
        //then
        int prototypeCounter = MessageBuilder.getInstanceCounter();
        assertEquals("Wrong number of instances", 2, prototypeCounter);
    }

}

显然,测试失败,因为进样仅发生一次,实际结果为1,但我们预期为2。

    • 第二起案件:**

Singleton和Test是相同的,但原型现在看起来如下所示(proxyMode已更改):

@Service
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, 
       proxyMode = ScopedProxyMode.TARGET_CLASS)
class MessageBuilder {
  // ...
}

当我们开始测试时,我们看到实际结果为6,因为内部createMessage方法messageBuilder被访问了3次,createMessage方法被调用了两次,因此3 * 2 = 6。
为了解释行为,作者提供了以下图片:

我不明白哪一个bean是依赖的,为什么每次访问代理messageBuilder genetes都需要新的bean示例化。为什么第一种情况会不同?你能解释一下吗?就我所知-代理是创建的-使用CGLIB或使用动态代理,所以无论如何代理都是注入的

nwnhqdif

nwnhqdif1#

如果您使用prototype范围定义Bean,则当从Spring上下文引用该Bean时,ApplicationContext将返回一个新示例。在第一个示例中,在创建MessageServicesingleton Bean时,将创建MessageBuilderprototype 的新示例。但是,由于MessageService在Spring生命周期中仅构造一次(因为它是单例),它只请求注入MessageBuilder原型bean的一个引用。
换句话说,在第一个示例中,MessageBuilder bean只被示例化一次,因为它被注入(自动连接)到MessageService中一次。在注入的原型bean上执行的方法调用将不会被代理到新的原型bean示例。
通过将proxyMode设置为TARGET_CLASSApplicationContext不直接将新的原型bean示例注入到另一个bean中,而是注入原型bean的 * 代理 *。因此,当单例bean从注入的单例bean调用方法时,中间代理引用新的原型bean并调用该方法。
更多信息可以在Spring文档中找到:
如果你想注射(例如)将一个HTTP请求作用域的bean插入到另一个更长生存期的作用域的bean中,您可以选择注入一个AOP代理来代替作用域bean。您需要注入一个代理对象,该代理对象公开与作用域对象相同的公共接口,但也可以从相关作用域中检索真实的的目标对象(如HTTP请求)并将方法调用委托给真实的对象。

xpcnnkqh

xpcnnkqh2#

https://stackoverflow.com/a/21762454/2674303是关于JDK动态代理和CGLIB的讨论
文章http://dolszewski.com/spring/accessing-prototype-bean-in-singleton/是关于如何将原型bean注入单例bean的
通过原型作用域的定义,在为MessageBuilder设置原型作用域时,每次得到的对象都不一样,可以验证ctx.getBean(MessageBuilder.class);
当MessageService被自动连接到一个单例bean时,你想要相同的行为。MessageService是单例的,并且只初始化一次。如果你向它注入一个真实的的MessageBuilder对象,你不可能在MessageService中得到一个不同的MessageBuilder对象。所以你需要为MessageBuilder对象注入一个aop代理。Spring可以在内部正确地处理它,并且即使只有一个MessageService,也会给你不同的对象。

相关问题