在多模块环境中使用依赖注入(通过Guice)

Tim*_*thy 1 java dependency-injection structure guice

我在涉及Guice并避免使用非Guice单身人士方面有些两难。考虑那里有3个模块的多模块项目:sharedfrontendbackend。无论是frontendbackend使用的一个实例Profiling的类的内部shared模块(其时间的方法,和在整个项目中广泛使用)。

几乎每个类都需要使用此Profiling实例(包括User用户连接时动态创建的对象)。

如果每个单独的类都需要该类的一个实例Profiling,则执行该方法的不同方法会有一些缺点:

解决方案1(在构造函数中,复制到实例字段):

private final Profiling profiling;

@Inject
public User(Profiling profiling, String username)
Run Code Online (Sandbox Code Playgroud)

缺点:您必须Profiling每个构造函数包含一个对象。这很麻烦,而且毫无意义。您还必须Injector静态存储(或注入)Guice ,以便您可以动态创建User对象,而不仅仅是在首次加载程序时。

解决方案2(仅作为实例字段):

@Inject
private Profiling profiling;

public User(String username)
Run Code Online (Sandbox Code Playgroud)

缺点:与上面类似,您必须使用Guice Injector实例化每个对象。这意味着要User动态创建对象,需要在创建User对象的类中使用Injector的实例。

解决方案3(作为一个(主)类中的静态字段,由我们手动创建)

public static final Profiling PROFILING; // Can also use a get method

public Application() {
    Application.PROFILING = injector.getInstance(Profiling.class)
}
Run Code Online (Sandbox Code Playgroud)

缺点:违反了Guice /依赖注入的建议-创建一个Profiling静态(由Application.PROFILING.start())访问的单例对象会破坏Guice的目的吗?

解决方案4(由Guice作为每个类的静态字段注入)

@Inject
private static Profiling profiling;

// You need to request every single class:
// requestStaticInjection(XXXX.class)
Run Code Online (Sandbox Code Playgroud)

缺点:同样,这与Guice的/依赖注入建议相反,因为它是静态注入。我还必须请求Guice需要将Profiler注入到的每个类(这也很麻烦)。

有没有更好的方法来设计项目并避免退回到我以前使用的单例设计模式?

TL; DR:我希望能够跨每个单个类访问此Profiling实例(每个模块一个),而又不退回到单例设计模式。

谢谢!

Jef*_*ica 5

在实用上,我将使用普通的单例,可能通过单字段Enum或类似的模式。

要知道为什么,您应该问一个问题:Guice的目的是什么,并且一般来说依赖注入是什么?目的是分离应用程序的各个部分,以便可以独立开发和测试它们,并进行集中配置和重新排列。考虑到这一点,你需要权衡耦合的成本解耦的成本。这取决于您选择耦合或解耦的对象。

在这里,耦合的代价是,如果没有真实的Profiling实例(包括在测试中,包括针对User之类的模型对象),您将无法操作应用程序的任何部分。因此,如果分析对环境进行了任何假设(例如,高分辨率系统定时调用的可用性),那么在不禁用分析的情况下,您将无法使用诸如User之类的类。此外,如果为了测试隔离而希望使用新的(非Singleton)Profiling实例对测试进行概要分析,则需要单独实施该实例。但是,如果您的Profiling类非常轻巧,不会给您带来沉重的负担,那么您仍然可以选择这种方式。

去耦的代价是,它可以迫使每个对象成为可注射对象,而不是可更新对象。然后,您将能够在类和测试中替代新的/虚拟/伪造的Profiling实现,并重新配置为在不同的容器中使用不同的Profiling行为,尽管如果您没有理由替换的话,灵活性可能不会立即带来好处。这些实现。对于诸如稍后创建的User之类的类,您将需要工厂实现,例如通过Guice辅助注入AutoFactory代码生成提供的那些。(请记住,您可以通过注入Provider<T>而不是通过创建任意数量的对象T对于您将以其他方式注入的任何对象,而注入Factory实例就像自定义Provider以采用get您选择的参数。)

关于您的解决方案:

  • 解决方案1和2,按对象注入:这是工厂发光的地方。(我倾向于选择构造方法注入,因此我会在它们之间使用解决方案1。)当然,创建新User的所有内容都需要注入a User.Factory,这样可能会将范围狭窄的项目转变为一个将代码库中的每个类都转换为DI的项目,这对于您现在想做的事情来说可能是不可接受的成本。

    // Nested interface for your Factory:
    public interface Factory { User get(String username); }
    
    // Mark fields that the user provides:
    @Inject public User(Profiling profiling, @Assisted String username) { ... }
    
    // Wire up your Factory in a Module's configure:
    install(new FactoryModuleBuilder().implement(User.Factory.class));
    
    // Now you can create new Users on the fly:
    @Inject User.Factory userFactory;
    User myNewUser = userFactory.get("timothy");
    
    Run Code Online (Sandbox Code Playgroud)
  • 解决方案3,请求静态注入一个ProfilingHolder主持有人,这与我的想法差不多:对于不是通过依赖注入创建的对象,请为单个类(诸如此类)请求静态注入。出于灵活性的考虑,您甚至可以设置无操作行为:

    public class ProfilingHolder {
      // Populate with requestStaticInjection(ProfilingHolder.class).
      @Inject static Profiling profilingInstance;
      private ProfilingHolder() { /* static access only */ }
      public static Profiling getInstance() {
        if (profilingInstance == null) {
          // Run without profiling in isolation and tests.
          return new NoOpProfilingInstance();
        }
        return profilingInstance;
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    当然,如果您依赖于对VM单例的调用,那么您实际上就是在拥抱普通的VM全局静态单例模式,只要有相互作用就可以使用Guice。您可以轻松地扭转这种模式,并拥有一个Guice模块bind(Profiling.class).toInstance(Profiling.INSTANCE);并获得相同的效果(假设无需Guice即可实例化分析)。

  • 解决方案4,针对每个类的requestStaticInjection是我不会考虑的唯一。类列表太长,它们变化的可能性Profiling太小。您会将模块变成维护成本很高的杂货店清单,而不是任何有价值的配置,并且迫使自己破坏封装或使用Guice进行测试。

因此,总而言之,我将为您当前的注入对象选择Guice单例注入,为您当前的可更新对象选择普通单例,并且如果/当您有任何新可再生对象实现向注入对象的跃迁时,则选择迁移到工厂。