首页 > 解决方案 > 为什么在将 CGLIB 原型注入 Singleton 的情况下,每次访问原型都会创建一个新对象?

问题描述

免责声明: 我已经阅读了有关 JDK 动态代理和 CGLIB 的后续工作人员:https ://stackoverflow.com/a/21762454/2674303

我读过以下有趣的文章: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基因都需要新的 bean 实例化。为什么第一种情况的情况不同?你能解释一下吗?据我了解 - 代理无论如何都会被装箱 - 使用 CGLIB 或使用动态代理,所以无论如何代理都会被注入

标签: javaspringscopesingletoninstantiation

解决方案


如果你定义一个带有作用域的 bean ,当从 Spring 上下文中引用 bean 时,prototype会返回一个新实例。在您的第一个示例中,在创建单例bean时创建了一个新的原型ApplicationContext实例。然而,由于在 Spring 生命周期中只构造了一次(因为它是一个单例),它只请求一个要注入的原型 bean 的引用。MessageBuilder MessageService MessageServiceMessageBuilder

换句话说,在您的第一个示例中,MessageBuilderbean 仅在注入(自动装配)一次时被实例化MessageService一次。在注入的原型 bean 上执行的方法调用之后不会被代理到新的原型 bean 实例。

通过设置proxyModeto TARGET_CLASSApplicationContext不会直接在另一个 bean 中注入新的原型 bean 实例,而是注入原型 bean 的代理。因此,当单例 bean 从注入的单例 bean 调用方法时,中间代理会引用一个新的原型 bean 并调用该方法。

更多信息可以在Spring 文档中找到:

如果你想(例如)将一个 HTTP 请求范围的 bean 注入到另一个具有更长生命周期的 bean 中,你可以选择注入一个 AOP 代理来代替这个范围 bean。也就是说,您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但也可以从相关作用域(例如 HTTP 请求)检索真实目标对象,并将方法调用委托给真实对象。


推荐阅读