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

时间:2017-03-11 13:51:35

标签: java dependency-injection structure guice

我有一个涉及Guice和避免非Guice单身的轻微困境。考虑一个多模块项目,其中有3个模块:sharedfrontendbackendfrontendbackend都在Profiling模块内部使用shared类的实例(对方法进行计时,并在整个项目中广泛使用)。

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

如果每个类都需要Profiling类的实例,则不同的方法存在缺陷:

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

private final Profiling profiling;

@Inject
public User(Profiling profiling, String username)

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

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

@Inject
private Profiling profiling;

public User(String username)

缺点:与上述类似,您必须使用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)
}

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

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

@Inject
private static Profiling profiling;

// You need to request every single class:
// requestStaticInjection(XXXX.class)

缺点:再次,这违反了Guice的/依赖注入建议,因为它是静态注入的。我还必须要求Guice需要注入Profiler的每一个类(这也很麻烦)。

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

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

谢谢!

1 个答案:

答案 0 :(得分:4)

实际上,我会使用普通的单例,可能是通过单字段枚举或类似的模式。

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

此处的耦合成本是,如果没有实际的分析实例,包括在测试中,包括用户等模型对象,您将无法操作任何应用程序。因此,如果Profiling对其环境做出任何假设 - 例如高分辨率系统时序调用的可用性 - 您将无法使用类似用户的类而不允许禁用性能分析。此外,如果您希望测试使用新的(非单例)分析实例进行分析以便进行测试隔离,那么您需要单独实现它。但是,如果你的Profiling类很轻,不会造成巨大的负担,那么你仍然可以选择这种方式。

解耦的成本是它可以强制每个对象变为an injectable, as opposed to a newable。然后,您可以在类和测试中替换新的/虚拟/虚假的Profiling实现,并重新配置以在不同的容器中使用不同的Profiling行为,但如果您没有理由,这种灵活性可能不会立即带来好处替换这些实现。对于稍后用户创建的类,您需要进行工厂实施,例如通过Guice assisted injectionAutoFactory code generation提供的实施。 (请记住,您可以通过为注入的任何对象注入Provider<T>而不是T来创建任意数量的对象,并且注入Factory实例就像自定义提供程序以{{1你选择的参数。)

关于您的解决方案:

  • 解决方案1和2,每个对象注入:这是工厂发光的地方。 (我赞成构造函数注入,给出了选择,所以我在它们之间使用解决方案1。)当然,创建新用户的所有内容都需要注入get,所以这可能会将一个范围紧密的项目转变为一个项目,将您的代码库中的每个类转换为DI,这可能是您现在尝试做的不可接受的成本。

    User.Factory
  • 解决方案3,请求静态注入主持有者近似我的想法:对于不是通过依赖注入创建的对象,请求对单个类进行静态注入,如{{ 1}}或者其他一些。为了灵活性,你甚至可以给它无操作行为:

    // 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");
    

    当然,如果您依赖于对VM单例的调用,那么您真正拥抱一个正常的VM全局静态单例模式,只需要在可能的情况下使用Guice进行相互作用。您可以轻松地转换此模式并使用Guice模块ProfilingHolder并获得相同的效果(假设可以在没有Guice的情况下实例化分析)。

  • 解决方案4,每个类的requestStaticInjection 是我唯一不会考虑的问题。课程列表太长,而且它们变化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; } } 的可能性太小。您将模块转变为高维护成本的购物清单而不是任何有价值的配置,并且您要强迫自己打破封装或使用Guice进行测试。

因此,总而言之,我为您当前的可注入对象选择Guice单例注入,为当前 newable 对象选择普通单例,以及迁移选项如果/当任何 newables 突然转移到注射剂时,可以使用工厂。