Java 如何在没有显式调用的情况下注入值?

Java 如何在没有显式调用的情况下注入值?,java,dependency-injection,guice,Java,Dependency Injection,Guice,我熟悉依赖注入的概念及其好处,但是使用框架来处理这项业务会让我感到困惑 这个问题对于任何DI框架都是有效的,但我将坚持使用这个问题的Guice 问题 比如说,我有以下课程: public interface Tea { void prepare(); } public class GreenTea implements Tea { public void prepare() { System.out.println("Preparing Green Tea

我熟悉依赖注入的概念及其好处,但是使用框架来处理这项业务会让我感到困惑

这个问题对于任何DI框架都是有效的,但我将坚持使用这个问题的Guice

问题

比如说,我有以下课程:

public interface Tea {

    void prepare();

}

public class GreenTea implements Tea {

    public void prepare() {
        System.out.println("Preparing Green Tea");
    }

}

public class BlackTea implements Tea {

    public void prepare() {
        System.out.println("Preparing Black Tea");
    }

}
如果没有框架,在我的主要方法中,我会这样做:

public static void main(String[] args) {
        Tea tea = new GreenTea();
        tea.prepare();
}
public class TeaModule extends AbstractModule {

    protected void configure() {
        bind(Tea.class).to(GreenTea.class);
    }

}

public class Main {

    @Inject
    private Tea tea;

    public static void main(String[] args) {

        Injector injector = Guice.createInjector(new TeaModule());
        Main main = injector.getInstance(Main.class);

        main.tea.prepare();

    }

}
在Google Guice Inject的帮助下,我可以更进一步,做如下事情:

public static void main(String[] args) {
        Tea tea = new GreenTea();
        tea.prepare();
}
public class TeaModule extends AbstractModule {

    protected void configure() {
        bind(Tea.class).to(GreenTea.class);
    }

}

public class Main {

    @Inject
    private Tea tea;

    public static void main(String[] args) {

        Injector injector = Guice.createInjector(new TeaModule());
        Main main = injector.getInstance(Main.class);

        main.tea.prepare();

    }

}
现在,假设我有一个随机类,需要注入我的tea接口:

public class RandomClass {

    private Tea tea;

    public void doStuff() {
        System.out.print("Doing stuff and.. ");
        tea.prepare();
    }

    public Tea getTea() {
        return tea;
    }

    @Inject
    public void setTea(Tea tea) {
        this.tea = tea;
    }
}
不幸的是,这会引发NullPointerException,因为RandomClass不知道来自外部的任何注入

我目前找到的解决方案

1) 我读过关于创建自定义提供程序的内容,例如:

public class TeaProvider implements Provider<Tea> {

    public Tea get() {
        Tea tea = new BlackTea();
        return tea;
    }
}
2) 更糟糕的解决方案是在RandomClass内注入注入器并手动请求类实例,如下所示:

public class RandomClass {

    @Inject
    Injector injector;

    private Tea tea;

    public RandomClass() {
        tea = injector.getInstance(Tea.class);
    }

    public void doStuff() {
        System.out.print("Doing stuff and.. ");
        tea.prepare();
    }

}
即使如此,我必须在我的引导方法中从注入器获取RandomClass实例

问题

如果DI框架需要以任何一种方式提供类,那么我真的不了解整个DI框架的概念

1) 是否有任何可能的方法将Tea实例注入RandomClass,而不显式地告诉Injection in bootstrapping方法这样做?如果可能的话,怎么做


2) 如果我必须手动“加载”所有类以注入值,那么使用DI框架的优点是什么?我的意思是,我可以向依赖者提供某个类的新实例,而无需使用任何框架。那么人们为什么会使用它呢?

DI框架背后的想法很简单:类不应该负责实例化它的依赖项。因此,尽管我不建议走这么远,但100%DI解决方案应该包括对
new
的零调用。它应该完全在工厂类中发生

这是您的
随机类
,没有DI:

public class RandomClass {
    private Tea tea;

    public RandomClass() {
        tea = new BlackTea();
    }

    public void doStuff() {
        System.out.print("Doing stuff and.. ");
        tea.prepare();
    }    
}
此时,您应该注意到,如果不测试Tea的功能,就不可能测试RandomClass,因为您没有提供替代实现的方法

另一种方法是:

public class RandomClass {
    private Tea tea;

    public RandomClass(Tea tea) {
        this.tea = tea;
    }

    public void doStuff() {
        System.out.print("Doing stuff and.. ");
        tea.prepare();
    }    
}

public class RandomClassProvider {
    public RandomClass create() {
        return new RandomClass(new BlackTea());
    }
}
现在,使用DI注释:

public class RandomClass {
    private Tea tea;

    @Inject public RandomClass(Tea tea) {
        this.tea = tea;
    }

    public void doStuff() {
        System.out.print("Doing stuff and.. ");
        tea.prepare();
    }    
}

// Guice writes Provider<RandomClass> automatically.
公共类随机类{
私家茶;
@公共课(茶){
茶=茶;
}
公共空间{
系统输出打印(“做事情和…”);
茶;
}    
}
//Guice自动写入提供程序。
现在您可以手动使用RandomClass(通过调用@Inject注释的构造函数)或自动使用RandomClass(通过GUI请求实例)。这将使切换实现变得容易,包括仅在测试中切换实现,以测试您编写的或通过Mockito创建的双精度


关于提供程序的最后一句话:您不必担心自己的提供程序:无论您如何绑定实例或提供程序,您都可以使用构造函数或字段中的任何位置(或
getInstance
getProvider
)使用
@injectprovider
访问它。如果不确定是否需要实例,或者如果需要多个实例,则插入提供程序;如果需要后处理,则仅当需要调用某些外部方法来获取实例时,才手动编写提供程序。

当前如何创建
RandomClass
的实例?到目前为止,我从未使用过setter注入,而是使用过构造函数注入。只要您需要
RandomClass
的实例,就将该实例标记为
@Inject
,这将导致Guice创建实例并注入其依赖项。如果我错了,请纠正我,但使用构造函数注入会迫使我从main方法中的injector创建RandomClass实例。我想避免那样。我可以向你保证RandomClass是在某个地方创建的,但不是在主类中,它只是不适合那里。我确实理解其中的大部分内容,但有一些关键点还不清楚。比如说,我有一些处理程序类,为不同的任务提供了具体的实现。有些任务需要提供接口,有些则不需要。我想我会在运行时在需要的地方注入依赖项,因为整个过程是不可预测和动态的。1)有可能做到这一点吗?2) 使用构造函数注入和字段注入有什么区别吗?这两种方法都需要我使用注入器实例化一些根对象。显然,如果没有注入器,我不能用注入字段实例化对象?1)是的;与调用
newtaska()
newtaskb()
不同,您将插入一个
提供程序
提供程序
,并对每个提供程序调用
get
,以获取一个新实例。2) 您可以注入的内容没有区别,但是很难让最终字段与字段注入一起工作,并且除非您有公共setter方法或公共@inject注释初始化方法,否则您可能会发现在没有注入器的情况下很难创建对象(用于测试或非DI使用)。