为什么使用@Lazy注解创建的类的bean与相同类的正常Autowired对象相比具有不同的引用。我知道@Lazy在需要时提供bean示例,但我认为它总是相同的引用。
我写了下面的测试来证明这一点。
简单的柜台服务:
@Service
public class CounterService {
private final AtomicInteger counter = new AtomicInteger();
public void inc() {
counter.incrementAndGet();
}
public int getCounter() {
return counter.get();
}
}
不使用Lazy的测试:
public class CounterServiceTest extends BaseITTest {
@Autowired
private CounterService counterService;
@Autowired
private ApplicationContext context;
@Test
void testBeansReference() {
CounterService contextCounterService = context.getBean(CounterService.class);
counterService.inc();
contextCounterService.inc();
System.out.println(counterService.getCounter() + " / " + contextCounterService.getCounter());
System.out.println("counterService --> obj: " + counterService + ", hashCode: " + counterService.hashCode());
System.out.println("contextCounterService --> obj: " + contextCounterService + ", hashCode: " + contextCounterService.hashCode());
System.out.println("== Check: " + (counterService == contextCounterService));
System.out.println(".equals() Check: " + (counterService.equals(contextCounterService)));
}
}
输出:
2 / 2
counterService --> obj: com.flock.appointment.service.CounterService@750c23a3, hashCode: 1963729827
contextCounterService --> obj: com.flock.appointment.service.CounterService@750c23a3, hashCode: 1963729827
== Check: true
.equals() Check: true
不使用Lazy的测试:
public class CounterServiceLazyTest extends BaseITTest {
@Lazy
@Autowired
private CounterService counterService;
@Autowired
private ApplicationContext context;
@Test
void testBeansReference() {
CounterService contextCounterService = context.getBean(CounterService.class);
counterService.inc();
contextCounterService.inc();
System.out.println(counterService.getCounter() + " / " + contextCounterService.getCounter());
System.out.println("counterService --> obj: " + counterService + ", hashCode: " + counterService.hashCode());
System.out.println("contextCounterService --> obj: " + contextCounterService + ", hashCode: " + contextCounterService.hashCode());
System.out.println("== Check: " + (counterService == contextCounterService));
System.out.println(".equals() Check: " + (counterService.equals(contextCounterService)));
}
}
输出:
2 / 2
counterService --> obj: com.flock.appointment.service.CounterService@259c6ab8, hashCode: -1833445830
contextCounterService --> obj: com.flock.appointment.service.CounterService@259c6ab8, hashCode: 631007928
== Check: false
.equals() Check: false
- 请注意,第二个测试(使用lazy)的输出是2,这证明即使引用不同,类仍然是单例的,这是预期的,因为其他情况下的输出不会是2/2。
- 我也不确定为什么在第二个测试中,即使hashCode不同,并且==检查也显示false,但object.toString()仍会产生相同的输出。toString()末尾的数字不是hashCode的十六进制数吗?由于打印的hashCode不同,因此十六进制数应该不同?
- 我在调试器的测试2中观察到了counterService的示例,该示例通过CGLIB得到了增强,我读到了CGLIB,这让我认为大多数事情都是由于它,但我不太理解这一切是如何发生的,我还想详细了解Spring内部是如何做到这一点的。
1条答案
按热度按时间pcww981p1#
我会尽力提供帮助,但让我先说一句,我不是Spring或任何类似的Maven。
当使用
@Autowired
和@Lazy
(或@Inject
和@Lazy
)时,Spring不会注入请求的示例本身,这是很自然的,因为您希望它被延迟初始化,相反,它会为它创建一个“延迟代理”对象,代理的目的是尽可能长时间地延迟真实的依赖项的初始化。可以将Lazy代理看作Mockito间谍或
java.lang.reflect.Proxy
,它与要代理到的原始对象具有相同的接口,而且其中还包含一些“中间件(也称为InvocationHandler
实现),其负责一旦在代理上进行第一次调用就初始化“真实的依赖关系”。代理上的所有方法调用被简单地转发到下游依赖关系。我想,Spring在https://github.com/spring-projects/spring-framework/blob/main/spring-core/src/main/java/org/springframework/cglib/proxy/Proxy.java上有自己的Proxy实现(而不是使用Java的内置Proxy),这就是你提到的“CGLIB”,浏览该类、整个模块以及父模块,了解更多信息。
查看有关Java内置代理too https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html的文档
使用
@Lazy
的一个更好的选择可能是使用Provider<Foo> fooProvider
或类似的,而不是@Lazy @Autowired Foo foo
,缺点是你必须调用fooProvider.get()
或类似的,但优点是你不需要向任何人解释@Lazy
到底是如何工作的。最后,您提出的hashCodes和toString问题-
@Lazy
注入示例(代理)可能将toString()
调用代理到下游依赖项,但不代理hashCode()
和equals()
方法。不完全确定他们为什么选择这样做,但在我看来就是这样。希望它能帮助你找到真实的的答案,干杯!