java - 为什么在将 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 或使用动态代理,所以无论如何代理都会被注入
解决方案
如果你定义一个带有作用域的 bean ,当从 Spring 上下文中引用 bean 时,prototype
会返回一个新实例。在您的第一个示例中,在创建单例bean时创建了一个新的原型ApplicationContext
实例。然而,由于在 Spring 生命周期中只构造了一次(因为它是一个单例),它只请求一个要注入的原型 bean 的引用。MessageBuilder
MessageService
MessageService
MessageBuilder
换句话说,在您的第一个示例中,MessageBuilder
bean 仅在注入(自动装配)一次时被实例化MessageService
一次。在注入的原型 bean 上执行的方法调用之后不会被代理到新的原型 bean 实例。
通过设置proxyMode
to TARGET_CLASS
,ApplicationContext
不会直接在另一个 bean 中注入新的原型 bean 实例,而是注入原型 bean 的代理。因此,当单例 bean 从注入的单例 bean 调用方法时,中间代理会引用一个新的原型 bean 并调用该方法。
更多信息可以在Spring 文档中找到:
如果你想(例如)将一个 HTTP 请求范围的 bean 注入到另一个具有更长生命周期的 bean 中,你可以选择注入一个 AOP 代理来代替这个范围 bean。也就是说,您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但也可以从相关作用域(例如 HTTP 请求)检索真实目标对象,并将方法调用委托给真实对象。
推荐阅读
- thingsboard - 仪表板调整大小和小部件丢失值
- mysql - 替代 phpMyAdmin
- mysql - 数据库不可用时延迟的工作人员退出
- c# - 无法在 editorconfig 文件旁边创建新项目
- javascript - 尝试在注册服务人员之前运行 fetch 功能。
- nginx - 413 请求实体在 5.4MB JSON 上太大:如何增加设置?
- matlab - 在需要矩阵行的函数上应用 arrayfun
- c# - 使用链接服务器 sql 的替代方法
- android - 类没有定义无参数构造函数。如果您使用 ProGuard,请确保这些构造函数没有被剥离
- android - 在 Constraintlayout 中动画 Google 地图使应用程序冻结