5

我的“路线”文件中有这个:

POST        /accounts/        controllers.AccountsController.createOneAccount

在我的 ACCoutsController.java 中:

package controllers;

import com.google.inject.Inject;
import play.Application;
import play.mvc.Controller;
import play.mvc.Result;
import services.AccountService;
import java.io.IOException;

public class AccountsController extends Controller {
    @Inject
    private Application application;
    final String host = application.configuration().getString("db.default.host");
    final int port = application.configuration().getInt("db.default.port");
    final String dbName = application.configuration().getString("db.default.dbname");

    @Inject
    private AccountService accountService;
    public Result createOneAccount() throws IOException {
        return accountService.createOneAccount(request().body().asJson());
    }
}

这段代码编译得很好,但是在运行时我得到了这样的错误:

ProvisionException: Unable to provision,见以下错误:1) Error injection constructors, java.lang.NullPointerException at controllers.AccountsController.(AccountsController.java:11)
在为 router.Routes.(Routes.scala:28) 的参数 1 定位 controllers.AccountsController 的同时定位 router.Routes 在定位 play.api.inject.RoutesProvider 的同时为 play.api 的参数 0 定位 play.api.routing.Router .http.JavaCompatibleHttpRequestHandler.(HttpRequestHandler.scala:200) 同时在 play.api.DefaultApplication.(Application.scala:221) 中找到 play.api.http.HttpRequestHandler 的参数 4 时找到 play.api.http.JavaCompatibleHttpRequestHandler。 api.DefaultApplication.class(Application.scala:221) 同时定位 play.api.DefaultApplication 同时定位 play.api.Application 1 错误

我可以通过将 @ 添加到路由文件来解决此问题:

POST        /accounts/        @controllers.AccountsController.createOneAccount

但我不确定为什么我需要这样做,以及如何避免'@'。请给一些建议。

4

1 回答 1

8

@首先,请参阅此答案以了解在文件中使用或不使用之间的区别routes

https://stackoverflow.com/a/34867199/4600

然后,如Play 2.5.x 迁移文档所述:

路由现在使用依赖注入感知生成InjectedRoutesGenerator,而不是之前StaticRoutesGenerator假设控制器是单例对象。

因此,从 Play 2.5.0 开始,控制器默认使用依赖注入,您不需要@让它们使用依赖注入。


现在让我们看看你的情况发生了什么。首先,让我说构造函数注入是注入依赖项的首选方式。Guice 甚至建议(作为最佳实践)将final字段与构造函数注入结合起来,以最大限度地减少可变性。Guice 文档还建议您尝试仅注入直接依赖项。在您的情况下,您application用于访问configuration. 为什么不注入configuration对象呢?这将使您的依赖关系更加清晰(这将使每个实例的测试更容易)。

因此,按照此建议,您的代码将被重写为:

package controllers;

import com.google.inject.Inject;
import play.Configuration;
import play.mvc.Controller;
import play.mvc.Result;
import services.AccountService;
import java.io.IOException;

public class AccountsController extends Controller {

    private final Configuration configuration;
    private final AccountService accountService;

    private final String host;
    private final int port;
    private final String dbName;

    @Inject
    public AccountsController(Configuration configuration, AccountService accountService) {
        this.configuration = configuration;
        this.accountService = accountService;
        // initialize config variables
        this.host = configuration.getString("db.default.host");
        this.port = configuration.getInt("db.default.port");
        this.dbName = configuration.getString("db.default.dbname");
    }

    public Result createOneAccount() throws IOException {
        return accountService.createOneAccount(request().body().asJson());
    }
}

但是为什么现场注入会中断?

我们首先需要了解对象初始化。根据Java 规范

就在对新创建对象的引用作为结果返回之前,使用以下过程处理指示的构造函数以初始化新对象:

  1. 将构造函数的参数分配给此构造函数调用的新创建的参数变量。

  2. 如果此构造函数以同一类中另一个构造函数的显式构造函数调用(第 8.8.7.1 节)开始(使用 this),则评估参数并使用这五个相同的步骤递归地处理该构造函数调用。如果该构造函数调用突然完成,则此过程出于相同原因而突然完成;否则,继续执行步骤 5。

  3. 此构造函数不以显式构造函数调用同一类中的另一个构造函数开始(使用 this)。如果此构造函数用于 Object 以外的类,则此构造函数将以显式或隐式调用超类构造函数(使用 super)开始。使用这五个相同的步骤递归地评估超类构造函数调用的参数和过程。如果该构造函数调用突然完成,则此过程出于相同的原因突然完成。否则,继续执行步骤 4。

  4. 执行该类的实例初始化程序和实例变量初始化程序,将实例变量初始化程序的值分配给相应的实例变量,按照它们在源代码中以文本形式出现的从左到右的顺序。如果执行这些初始化程序中的任何一个导致异常,则不会处理更多初始化程序,并且此过程会突然完成相同的异常。否则,继续执行步骤 5。

  5. 执行此构造函数的其余部分。如果该执行突然完成,则此过程出于同样的原因突然完成。否则,此过程正常完成。

特别注意第 4 步,它解释了您的变量是在对象初始化期间初始化的。

为什么这很重要?因为 Guice 首先创建对象(然后将发生上述所有步骤),然后执行注入绑定(有关更多详细信息,请参阅Guice BootstrapGuice InjectionPoints)。因此,您的字段在对象初始化时需要application尚未注入的变量 (),从而导致NullPointerException.

于 2016-04-15T05:31:11.410 回答