java 为什么在SpringBoot中,bean的懒惰创建会导致类具有不同的引用/hashCode?

kr98yfug  于 2023-01-07  发布在  Java
关注(0)|答案(1)|浏览(117)

为什么使用@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内部是如何做到这一点的。
pcww981p

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()方法。不完全确定他们为什么选择这样做,但在我看来就是这样。
希望它能帮助你找到真实的的答案,干杯!

相关问题