我刚刚在Utils类中发现了一个静态方法sendMail(long list of params)。不知道为什么,但我觉得如果我们有一个单独的类(具有单例作用域的Spring bean),它将负责发送电子邮件,并且看起来会更好。但我找不到任何可以证明我的立场的论据。
那么,在这种(相当一般的)情况下使用依赖注入有哪些优点(或缺点)呢?
一般来说,持有混杂通常是静态功能的Util类型类并不是一个好的设计。我甚至在一个我参与开发的项目中见过这种类型的类被命名为UglyGlobals,至少他们坦诚了!总的来说,你是正确的。像邮件这样的东西很适合转化成单例bean来注入。
MailSender
(示例名称)的接口,而不是static Utils.sendMail(...)
方法,则您可以在单元测试中交换模拟/不同实现的MailSender
,而不是使用真实实现来轻松地对这些类进行单元测试。是的!我可以给你举一个最近我自己经历的例子。
我们最近将一些应用程序从直接发送电子邮件改为使用数据库队列发送消息。消息在数据库中排队,然后通过批处理过程发送。这使我们能够处理SMTP服务器故障、重新发送消息、控制可以从我们的开发服务器发送哪些消息、验证消息是否已实际发送等。
即使是像发送电子邮件这样简单的事情,也可能是您未来想要更改的东西。注入一个实现将使这变得更加容易。
Spring Mail
首先,我建议您查看org.springframework.mail package。它提供了一个有用的实用程序库来发送电子邮件,并作为抽象来隐藏底层邮件系统的细节。
既然您已经在使用Spring,那么使用这个库就不会带来任何麻烦,只要它提供了您需要在应用程序中处理发送邮件的所有功能。
静态方法与依赖注入
使用依赖注入可以让您轻松地切换到其他实现方式。静态方法调用将您的消费者实现与消费者正在使用的服务紧密绑定。
测试消费者意味着您将通过服务集成测试消费者,而不是孤立地对消费者进行单元测试。这使您始终依赖于这些静态方法的基础逻辑,并且可能因为其中一个静态方法出错而导致测试失败。
您要避免依赖于状态或具有外部影响的静态实用方法,在某些情况下(通常是测试),由于任何原因,您可能希望避免使用。
在这种情况下,发送邮件可能取决于外部状态(邮件服务器可用性)并产生可能希望避免的外部影响(发送电子邮件)。对于开发和测试,您可能根本不想发送电子邮件。在其他情况下,您可能希望测试是否已发送电子邮件,但如果实际上正在发送邮件,该怎么办?设置一些复杂的系统以检查实际电子邮件收件箱中的邮件吗?
如果注入表示MailSender
的接口,则可以在您不关心发送电子邮件时提供什么都不做的NoopMailSender
,而在您希望能够测试代码已发送某些电子邮件时,提供一个伪造的StubMailSender
,它通过List<EmailMessage>
收集了通过它发送的电子邮件。
DI(依赖注入)使得编写测试时轻松模拟实现。例如,假设您想要测试密码重置流程。如果使用硬编码的 Utils.sendMail(),则您的测试代码将被迫创建一个模拟 SMTP 服务器,读取和解析电子邮件,然后单击密码重置链接。如果您使用了 DI,则可以传递一个模拟的 Emailer 对象。这样,您就可以编写超快速的单元测试,而不必担心外部集成。
您还可以轻松地交换实现。例如,Google App Engine 有一个自定义的 sendMail API - 因此,您很难同时支持 GAE 版本的代码和非 GAE 版本的代码(为了论证,只需假设迁移到 GAE 是那么容易。显然不是这样的)。
最后,您的代码更加模块化。特定类(ResetPassword)可能仅依赖于 Util.sendMail(),但 Util 可能是一个包罗万象的工具类,其中包含执行各种操作的方法。因此,如果您想在另一个项目中重用 ResetPassword,则必须复制整个 Utils 类,以及 Utils 需要工作的几个依赖 jar 包。这不是一个好的选择。