Dependency injection 依赖注入带来的性能问题

Dependency injection 依赖注入带来的性能问题,dependency-injection,Dependency Injection,在我的profiler报告中,我越来越多地看到依赖注入的基于模拟的测试结果。许多依赖项都是静态的,但由于我们希望单独测试方法,因此将它们更改为实例成员,如以下示例所示: class ShortLivedThing { IDependency1 dep1; IDependency1 dep2; IDependency1 dep3; ... int TheRealData; // Constructor used in production publi

在我的profiler报告中,我越来越多地看到依赖注入的基于模拟的测试结果。许多依赖项都是静态的,但由于我们希望单独测试方法,因此将它们更改为实例成员,如以下示例所示:

class ShortLivedThing {
   IDependency1 dep1;
   IDependency1 dep2;
   IDependency1 dep3;
   ...

   int TheRealData;

   // Constructor used in production 
   public ShortLivedThing() {
     dep1 = new Dep1(); dep2 = new Dep2(); dep3 = new Dep3();
   }

   // DI for testing 
   public ShortLivedThing(IDependency1 d1, IDependency2 d2, IDependency3 d3) { 
     dep1 = d1(); dep2 = d2(); dep3 = d3();
   }
}
反过来,大多数情况下依赖关系都有其他依赖关系等等。这导致每次在测试之外进行方法调用时,都会实例化一个(主要是“静态”)对象树。每个对象都非常小(只有几个指针),但树效应将其转化为不断增加的性能影响


对此我们能做些什么?

在我看来,您需要利用适当的依赖注入框架所能提供的功能。不要在测试/生产中使用不同的构造逻辑

class ShortLivedThing {
   IDependency1 dep1;
   IDependency1 dep2;
   IDependency1 dep3;
   ...

   int TheRealData;

   public ShortLivedThing(IDependency1 d1, IDependency2 d2, IDependency3 d3) { 
     dep1 = d1; dep2 = d2; dep3 = d3;
   }
}
对于spring,单例注入仅在容器启动时执行。每次都进行原型注射。如果单元测试正在连接,那么每次运行单元测试时都会完成完整的连接。因此,分析单元测试通常不是一个好主意

也许您使用的单例作用域太少,而原型作用域太多?(Prototype=每次新实例)

spring injection的好处在于,您可以使用范围代理,这意味着您的对象图可以如下所示:

 A Singleton
 |
 B Singleton
 |
 C Prototype (per-invocation)
 |
 D Singleton
 |
 E Session scope (web app)
 |
 F Singleton
每个请求只会在每个会话中创建一个C实例和一个E实例。A、 B、D和F是单态。如果它不是一个webapp,默认情况下你没有会话作用域,但是你也可以自定义作用域(“窗口”作用域对于一个有窗口的桌面应用来说听起来很酷)。这里的线索是,您可以在任何级别“引入”作用域,实际上,您可以有十层单例对象,然后突然出现会话作用域。(这确实可以彻底改变您在分层体系结构中实现某些横切功能的方式,但情况不同)

我认为,这确实提供了DI模型中尽可能少的对象创建


虽然这是SpringforJava,但我相信其他一些DI框架应该支持类似的特性。可能不是最简单的方法。

传入引用如何?

我能想到的最好方法是将所有依赖项放在一个“上下文”对象中,然后在所有实例之间共享。这将在一定程度上缓解性能问题。

如果您担心的是测试速度慢,请尝试并行运行它们,不要让测试过程中断程序员

自动化此过程:

  • 当有人签入时,对存储库进行构建
  • 在此版本上运行测试
  • 将结果通过电子邮件发送给签入的开发人员
最好不要第一次签入实际的存储库。让它成为一个临时的,并建立了这个。您可以选择执行性能测试、样式检查等,并将其包含在电子邮件中。如果执行此操作,请向自动化流程添加一个步骤:

  • 如果测试通过(并且满足可选条件),则将新代码与实际存储库合并

通过这种方式,慢测试就不必担心了。此外,当开发人员需要知道她的代码是否破坏了某些功能或实现了预期的性能提升时,她只需签入并等待为她生成的电子邮件。

我认为您应该只使用“DI构造函数”。您可以在测试和生产中调用此构造函数

class ShortLivedThing {
   IDependency1 dep1;
   IDependency1 dep2;
   IDependency1 dep3;
   ...

   int TheRealData;

   public ShortLivedThing(IDependency1 d1, IDependency2 d2, IDependency3 d3) { 
     dep1 = d1; dep2 = d2; dep3 = d3;
   }
}
这样,每次在测试之外进行方法调用时,就不会有实例化对象树的问题。当然,对于生产,您必须在参与对象之外正确地连接对象,这是一件好事


总之:不要选择50%DI/50%硬编码,而是选择100%DI。

如果您的目标是.NET,请查看。它有不同的作用域(单例、工厂、容器)来调整创建方面,确定性处理可以阻止资源使用,并允许使用GeneratedFactory和lambda表达式来配置组件和避免反射成本。

您是分析生产代码还是测试?我是分析生产代码。测试运行得很快,因为它们不构建依赖项的依赖项。F是否被赋予了E对象?如果是这样的话,这是如何与多个用户一起工作的?我一直在考虑自上而下,D被赋予了E。动态代理或cglib发挥了神奇的作用。是的,它与多个用户一起工作得非常好。300个字符变得非常小。退房