【问题标题】:Spring @Autowired fields - which access modifier, private or package-private?Spring @Autowired 字段 - 哪个访问修饰符,私有或包私有?
【发布时间】:2013-11-10 00:41:42
【问题描述】:

假设我们在类中的各个字段上使用了@Autowired 注解,并且我们没有编写也可以设置字段的设置器或构造器。

问题 - 访问修饰符应该是什么,privatepackage-private(即没有)?

例如:

public class MyClass {
    @Autowired
    private MyService myService;
}

public class MyClass {
    @Autowired
    MyService myService;
}

在第一种情况下(private 字段)Spring 使用反射来连接字段,即使它没有设置器。

第二种情况(package-private 字段)允许我们在需要扩展类以进行测试时访问这些字段(例如,设置模拟)。

所以这两种情况都可以正常工作,但哪个更推荐,尤其是在测试方面?

【问题讨论】:

  • 如果您的测试框架可以在私有字段中注入模拟,那么就私有化。否则,请使用包保护。无论如何,这无关紧要,除非您担心初级开发人员会从同一个包的另一个类中访问受包保护的字段。

标签: java spring unit-testing private access-modifiers


【解决方案1】:

我通常不会将 @Autowired 用于私有字段或方法。 @Autowired 的意思是,外面的人会设置这个字段。另一方面,“私有”意味着除了这个类之外没有人可以使用它。

如果 JIT 编译器以某种方式优化此代码,理论上混合 @Autowired 和 private 可能会导致问题。这可能是 Java 内存模型并发相关的问题,这将是仅限生产且无法重现的问题。

我会让 Autowired 字段至少包可见。作为免费奖励,它将允许编写没有技巧和变通方法的单元测试。

更新:此外,我会声明 volatile 等字段以避免与 Java 内存模型相关的可见性冲突。 Spring 开发人员在不显式同步访问的情况下使用一些技巧来使自动装配字段工作,但我不确定这些技巧在任何硬件上的任何 JVM 中都能顺利工作。

【讨论】:

    【解决方案2】:


    出于以下几个原因,我宁愿在 @Autowired 字段上使用 private:

    • 我使用这些字段进行依赖注入,通常在服务类中,以使用其他服务类。在这种情况下,我想将这些字段保留在当前类中。
    • 此外,扩展此类可能会导致不同的逻辑,因此可能需要 @Autowired 字段的另一个实现,因此私有而不是包私有。
    • 此外,重构时,有助于查看何时不再使用此类字段,因为包私有字段在未使用时不会显示警告(假设您的 IDE 是 Eclipse - 我实际上不知道其他 IDE )。

    【讨论】:

      【解决方案3】:

      所以这两种情况都可以正常工作,但更推荐哪种情况,尤其是在测试方面?

      我觉得属性应该是private:

      @Autowired
      private MyService myService;
      

      因为使用 getter 方法来提供对属性的访问而不是允许其他类直接访问它们总是好的。

      出于测试目的,注入private propertiesmocks 的工作方式与package-private 属性的注入方式相同。

      例如,使用Mockito,您可以将private MyService 的模拟注入MyClass,如下所示:

      public class MyClassTest {
      
          @Mock
          MyService service;
      
          @InjectMocks
          MyClass serv = new MyClass();
      
          @Before
          public void init() {
          MockitoAnnotations.initMocks(this);
          }
      }
      

      【讨论】:

      • 您好,感谢您提供有关@InjectMocks 的提示。这是否也适用于来自 xml 测试应用程序上下文和 @Configuration 类的弹簧接线?我遇到的问题是 MyService 的模拟(相同的模拟,而不是不同的模拟)也必须在由 spring 自动装配的不同类中可用。
      • 你指的是单元测试还是集成测试?每当您使用 MyService 的模拟时,您需要对其进行方法存根以按照您希望的方式运行。因此,具有相同方法存根的 MyService 的两个模拟将表现得就像在两个不同的测试类中使用的相同模拟。
      • 嗯,这是单元测试,但我们使用的是“处理器”设计模式,所以有一堆“处理器”类和一个方法(你猜对了 - process()),所以在旁边除非所有这些处理器都连接好,否则实际上什么都不会得到测试。不管怎样,谢谢你的意见,很抱歉这里有2个很好的答案,我选择接受西蒙的。
      【解决方案4】:

      我通常更喜欢将字段设为私有并使用 setter 注入:

      public class MyClass {
      
          private MyService myService;
      
          @Autowired
          public void setMyService(MyService myService) {
              this.myService = myService;
          }
      }   
      

      允许服务被@Autowired,但设置一个模拟实例用于单元测试。

      【讨论】:

      • 我可以看到这可能更适合测试,但它也更冗长,在我正在处理的情况下,我希望对代码进行最少的更改以方便测试。
      【解决方案5】:

      第一种情况还允许您根据框架注入模拟。例如使用 Mockito 的 @InjectMocks 注解。您在 Spring 测试中也有 ReflectionTestUtils.setField,...

      我个人不太喜欢为了测试目的而过多地修改类,所以我会选择第一种情况。但归根结底,这主要取决于您喜欢的测试框架。

      【讨论】:

      • 谢谢西蒙。我最终使用了ReflectionTestUtils.setField() 并对课程进行了最小的更改。情况是我想在被测类中禁用“实时”https 调用。所以我把 https 调用放到了一个单行的protected 方法中,并在我测试它时覆盖了该方法什么都不做(因此出现了private 字段的问题)。也许你也可以给我一些建议,看看我是否在这里采取了有效的方法来禁用 https 调用?
      • 如果不看更多代码就很难评论,但是重写一个方法使其在测试中什么都不做对我来说听起来不是最好的解决方案。可能可以使用模拟对象来阻止调用。
      • 为什么不把 HTTPS 调用的责任交给一个可以在测试中模拟的外部注入组件?您还可以使用 Spy 或部分 Mockito 模拟来“覆盖”此方法,而无需创建显式子类。
      • 是的,好主意@JB。代码实际上调用了一个静态方法 (HttpsUtil.doHttpsCall(...)) - 我绝对可以在这里看到使用组件的智慧,但是我想对被测代码进行最小的更改。
      猜你喜欢
      • 2017-12-22
      • 2014-08-27
      • 2020-05-04
      • 2011-04-18
      • 2011-01-31
      • 1970-01-01
      • 2023-04-03
      • 2015-11-26
      相关资源
      最近更新 更多