【问题标题】:How proxyMode and scopeName works under the hood?proxyMode 和 scopeName 如何在幕后工作?
【发布时间】:2021-11-06 21:38:41
【问题描述】:

我正在研究 Spring 框架,我看到了一些我无法解释实际发生的事情。 假设我们有这个简单的服务类:

@Service
@Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RandomNumberGenerator {

  double number;
  public RandomNumberGenerator() {
    System.out.println("Constructor is called!");
    number = Math.random();
  }

  public double getNumber() {
    return number;
  }
}

以及使用上述服务的以下控制器:

@RestController
public class NumberController {

  @Autowired
  RandomNumberGenerator numberGenerator;

  @GetMapping(path = "/number")
  public double getNumber(){
    System.out.println(System.identityHashCode(numberGenerator));
    return numberGenerator.getNumber();
  }
}

我假设每个请求都会创建一个新的 RandomNumberGenerator numberGenerator 实例,因此我应该在 System.out.println(System.identityHashCode(numberGenerator)); 中打印不同的数字,但奇怪的是,每个请求都得到相同的数字,但我可以看到每个请求构造函数RandomNumberGenerator 被调用!

例如你可以看到我的控制台输出:

257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344

我的问题是为什么即使调用了构造函数,我也会得到相同的数字? System.out.println(System.identityHashCode(numberGenerator)) 不是应该返回不同的整数吗?

【问题讨论】:

  • @josejuan 控制器给我们一个不同的号码,但这条线System.out.println(System.identityHashCode(numberGenerator)); 打印相同的号码。我在问题中添加了控制台输出
  • Spring 仅使用代理对象初始化 numberGenerator 字段一次,该代理对象在内部知道如何创建 RandomNumberGenerator 类型的新实例并将 getNumber 调用委托给。但是,identityHashCode 方法适用于代理对象,而不适用于新对象。
  • @josejuan OP 的印象是,numberGenerator 始终是每个请求的新对象。
  • @josejuan 我想要一种机制来区分实例,我搜索了stackoverflow,他们建议使用System.identityHashCode

标签: java spring aop


【解决方案1】:

关于如何在 Spring 中实现代理已经写了很多,请在以下帖子中查看我的答案:

简而言之,使用ScopedProxyMode.TARGET_CLASS,Spring 将使用CGLIB 生成RandomNumberGenerator 的子类。该类的名称类似于RandomNumberGenerator$$EnhancerByCGLIB$$d0daae41

这个类(几乎)覆盖了RandomNumberGenerator 的所有方法以充当工厂和委托者。这些被覆盖的方法中的每一个都将生成一个真正的RandomNumberGenerator 类型的新实例(每个请求),并将方法调用委托给它。

Spring 将创建这个新 CGLIB 类的实例并将其注入到您的 @Controller 类字段中

@Autowired
RandomNumberGenerator numberGenerator;

您可以在此对象上调用getClass(未被覆盖的方法之一)。你会看到类似的东西

RandomNumberGenerator$$EnhancerByCGLIB$$d0daae41

表示这是代理对象。

当你调用(被覆盖的)方法时

numberGenerator.getNumber()

CGLIB 类将使用 Spring 的 ApplicationContext 生成一个新的 RandomNumberGenerator bean 并在其上调用 getNumber() 方法。


scopeName 控制 bean 的范围。 Spring 将使用RequestScope 来处理这些bean。如果你的控制器两次调用getNumber(),你应该得到相同的值。代理(通过RequestScope)将在内部缓存新的、每个请求的对象

@GetMapping(path = "/number")
public double getNumber(){
  System.out.println(numberGenerator.getNumber() == numberGenerator.getNumber()); // true
  return 123d;
}

如果你使用了像“session”这样的作用域,Spring 会在多个请求中缓存真实的对象。您甚至可以使用作用域“singleton”,并且该对象将被缓存在所有请求中。


如果您确实需要,可以使用此处描述的技术检索实际实例

例如,

Object real = ((Advised)numberGenerator).getTargetSource().getTarget();

至于System.identityHashCode,您通过将代理对象传递给它来调用它。只有一个代理对象,因此调用将始终返回相同的值。

请注意,identityHashCode 不保证为不同的对象返回不同的值。它的 javadoc 状态

为给定对象返回与返回相同的哈希码 通过默认方法hashCode(),无论给定对象的 类覆盖hashCode()。空引用的哈希码是 零。

hashCode()'s javadoc 状态

如果两个对象不相等根据不要求 equals(java.lang.Object) 方法,然后调用 hashCode 两个对象中的每一个的方法都必须产生 distinct 整数 结果。

【讨论】:

    猜你喜欢
    • 2022-07-07
    • 2014-11-18
    • 2011-02-17
    • 2012-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多