这个页面包含了一些使用 @Inject 注释的目的以及如何使用的指南。

目的

@Inject 允许我们更容易使用控制反转。

控制反转

简要地说,控制反转意味着我们将一个类需要的服务从外部传递进来,而不是实例化这个类或者主动调用获得(getInstance())该服务。这使得依赖显式化(我们更清楚我们的依赖是什么),并且在未来更方便更换组件,而且方便了单元测试。

示例

考虑以下类

public class MessageTask {

    public void setTask(String name) {
        if (PlayerCache.getInstance().isAuthenticated(name) && LimboCache.getInstance().hasLimboPlayer(name)) {
            LimboCache.getInstance().getLimboPlayer(name).initMessageTask();
        }
    }
}

应用控制反转后,这个类看起来像这样:

public class MessageTask {

    private final PlayerCache playerCache;
    private final LimboCache limboCache;

    public MessageTask(PlayerCache playerCache, LimboCache limboCache) {
        this.playerCache = playerCache;
        this.limboCache = limboCache;
    }

    public void setTask(String name) {
        if (playerCache.isAuthenticated(name) && limboCache.hasLimboPlayer(name)) {
            limboCache.getLimboPlayer(name).initMessageTask();
        }
    }
}

第二种版本里,我们立刻看到了 MessageTask 需要一个 LimboCachePlayerCache 实例。我们可以很简单的传递模拟实现来测试这个类,而第一种情况下,我们不可能来更换实现。

依赖注入

没有依赖注入的情况

使用「控制反转」的一个缺点是,我们很清楚的知道了实例化这个类需要两个实例作为依赖。考虑一下代码:

 private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages,
                                                 PasswordSecurity passwordSecurity, NewSetting settings) {
    HelpProvider helpProvider = new HelpProvider(permissionsManager, settings.getProperty(HELP_HEADER));
    Set<CommandDescription> baseCommands = CommandInitializer.buildCommands();
    CommandMapper mapper = new CommandMapper(baseCommands, permissionsManager);
    CommandService commandService = new CommandService(
        this, mapper, helpProvider, messages, passwordSecurity, permissionsManager, settings);
    return new CommandHandler(commandService);
}

我们想要实例化一个 CommandHandler。我们需要像其传递一个 CommandService ,所以我们需要先实例化这个。然而,它又需要一个 CommandMapper ,一个 HelpProvider... 一共四个实例。这让我们必须解决一大堆的类之后(CommandService, CommandMapper, 等.)才能 - 而我们只是想要一个 CommandHandler 实例而已。

依赖注入的目的

依赖注入允许我们避免大量的实例化代码块,而是通过 注入 依赖。我们可以像这样简单的从注入器请求一个类:

CommandHandler handler = injector.getSingleton(CommandHandler.class);

注入器会扫描 CommandHandler 类来查找依赖,实例化他们并传递给我们。基本来说,它自动做到了上面一大堆的代码块。我们需要小小的帮助一下注入器,通过使用 @Inject 注释对应的类来告诉注入器应该注入什么。

注入方法

依赖可以通过很多不同的方法传递给一个类。重要的是每个类只能使用一个。

构造方法注入

构造方法可以使用 @Inject 注释。这告诉注入器这个构造方法的参数需要被注入。考虑第一个例子中的 MessageTask 类。我们住需要添加一个注释来启用构造器注入:

public class MessageTask {

    private final PlayerCache playerCache;
    private final LimboCache limboCache;

    @Inject
    MessageTask(PlayerCache playerCache, LimboCache limboCache) {
        this.playerCache = playerCache;
        this.limboCache = limboCache;
    }

    public void setTask(String name) {
        if (playerCache.isAuthenticated(name) && limboCache.hasLimboPlayer(name)) {
            limboCache.getLimboPlayer(name).initMessageTask();
        }
    }
}

现在一个 MessageTask 实例可以通过 injector.getSingleton(MessageTask.class) 来获得。这个注入器将会注意到 PlayerCacheLimboCache 需要传递给构造方法。

字段注入

另一种方法,字段可以使用 @Inject 注释:

public class MessageTask {

    @Inject
    private PlayerCache playerCache;
    @Inject
    private LimboCache limboCache;

    public void setTask(String name) {
        if (playerCache.isAuthenticated(name) && limboCache.hasLimboPlayer(name)) {
            limboCache.getLimboPlayer(name).initMessageTask();
        }
    }
}

再一次的,注入器将会检测到字段上的注释,然后就会给这些字段赋值。这些字段可以是 private 的,或者是任何其他的可见性。字段注入需要一个无参构造方法存在。

@Inject 获取单实例

无论是你使用构造方法注入或者字段注入,很重要的一点就是要理解实例被传递,并且注入器每次都会传递相同的类实现。举个例子,如果你在某些类里有 @Inject private LimboCache limboCache ,另一个类里有另一个 @Inject private LimboCache limboCache ,两个字段都会有相同的 LimboCache 对象(即 limboCache == limboCache 为 true)。

results matching ""

    No results matching ""