简体   繁体   English

Play Framework 2.5.1路由和依赖注入(适用于Java)

[英]Play Framework 2.5.1 routing and dependency injection (for Java)

I have this in my "routes" file: 我在“路线”文件中有这个:

POST        /accounts/        controllers.AccountsController.createOneAccount

And in my AccoutsController.java: 在我的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());
    }
}

This code is compiling fine, but in runtime I got error like this: 这段代码编译得很好,但在运行时我遇到这样的错误:

ProvisionException: Unable to provision, see the following errors: 1) Error injecting constructor, java.lang.NullPointerException at controllers.AccountsController.(AccountsController.java:11) ProvisionException:无法配置,请参阅以下错误:1)在controllers.AccountsController注入构造函数,java.lang.NullPointerException时出错。(AccountsController.java:11)
while locating controllers.AccountsController for parameter 1 at router.Routes.(Routes.scala:28) while locating router.Routes while locating play.api.inject.RoutesProvider while locating play.api.routing.Router for parameter 0 at play.api.http.JavaCompatibleHttpRequestHandler.(HttpRequestHandler.scala:200) while locating play.api.http.JavaCompatibleHttpRequestHandler while locating play.api.http.HttpRequestHandler for parameter 4 at play.api.DefaultApplication.(Application.scala:221) at play.api.DefaultApplication.class(Application.scala:221) while locating play.api.DefaultApplication while locating play.api.Application 1 error 同时定位controllers.AccountsController for router.Routes。(Routes.scala:28),同时定位router.Routes同时定位play.api.inject.RoutesProvider,同时在play.api找到参数0的play.api.routing.Router .http.JavaCompatibleHttpRequestHandler。(HttpRequestHandler.scala:200)同时定位play.api.http.JavaCompatibleHttpRequestHandler,同时在play.api.DefaultApplication。(Application.scala:221)中找到参数4的play.api.http.HttpRequestHandler。 api.DefaultApplication.class(Application.scala:221),同时定位play.api.DefaultApplication,同时定位play.api.Application 1错误

I can resolve this by adding @ to routes file: 我可以通过将@添加到routes文件来解决这个问题:

POST        /accounts/        @controllers.AccountsController.createOneAccount

but I am not sure about why I need to do this, and how to avoid the '@'. 但我不确定为什么我需要这样做,以及如何避免'@'。 Please give some suggestions. 请提出一些建议。

First, see this answer to understand the difference between using or not @ in your routes file: 首先,看看这个答案,了解使用与否的差别@在你的routes文件:

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

Then, as stated by Play 2.5.x migration docs : 然后,如Play 2.5.x迁移文档所述

Routes are now generated using the dependency injection aware InjectedRoutesGenerator , rather than the previous StaticRoutesGenerator which assumed controllers were singleton objects. 现在使用依赖注入感知的InjectedRoutesGenerator生成路由,而不是之前的StaticRoutesGenerator ,它假设控制器是单例对象。

So, starting at Play 2.5.0, controllers use dependency injection by default and you don't need @ to make them use dependency injection. 因此,从Play 2.5.0开始,控制器默认使用依赖注入,您不需要@来使它们使用依赖注入。


Now lets see what is happening in your case. 现在让我们看看你的案例中发生了什么。 First of all, let me say that constructor injection is the preferred way of injecting dependencies. 首先,我要说构造函数注入是注入依赖项的首选方法。 Guice even recommends (as a best practice) to combine final fields with constructor injection to minimize mutability . Guice甚至建议(作为最佳实践)将final字段与构造函数注入相结合,以最大限度地减少可变性 Guice docs also recommends that you try to inject only direct dependencies . Guice文档还建议您尝试仅注入直接依赖项 In your case, you are using application to access a configuration . 在您的情况下,您正在使用application来访问configuration Why not inject the configuration object instead? 为什么不注入configuration对象呢? This will make your dependencies more clear (which will make, per instance, testing easier). 这将使您的依赖关系更加清晰(这将使每个实例的测试更容易)。

So, following this recommendations, your code would be rewritten to: 因此,遵循此建议,您的代码将被重写为:

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());
    }
}

But why the field injection was breaking? 但为什么现场注入破裂?

We first need to understand object initialization. 我们首先需要了解对象初始化。 According to Java specs : 根据Java规范

Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure: 在作为结果返回对新创建的对象的引用之前,处理指示的构造函数以使用以下过程初始化新对象:

  1. Assign the arguments for the constructor to newly created parameter variables for this constructor invocation. 将构造函数的参数分配给此构造函数调用的新创建的参数变量。

  2. If this constructor begins with an explicit constructor invocation (§8.8.7.1) of another constructor in the same class (using this), then evaluate the arguments and process that constructor invocation recursively using these same five steps. 如果此构造函数以同一个类中的另一个构造函数的显式构造函数调用(第8.8.7.1节)开头(使用此方法),则使用这五个相同步骤计算参数并以递归方式处理该构造函数调用。 If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason; 如果该构造函数调用突然完成,则此过程突然完成,原因相同; otherwise, continue with step 5. 否则,继续步骤5。

  3. This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). 此构造函数不以同一个类中的另一个构造函数的显式构造函数调用开头(使用此方法)。 If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). 如果此构造函数用于Object以外的类,则此构造函数将以超类构造函数的显式或隐式调用开始(使用super)。 Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. 使用这五个相同的步骤评估参数并递归处理超类构造函数调用。 If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. 如果该构造函数调用突然完成,则此过程突然完成,原因相同。 Otherwise, continue with step 4. 否则,继续执行步骤4。

  4. Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class . 为此类执行实例初始值设定项和实例变量初始值设定项,将实例变量初始值设定项的值按从左到右的顺序分配给相应的实例变量,在这些顺序中,它们以文本方式出现在类的源代码中 If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. 如果执行任何这些初始值设定项导致异常,则不会处理其他初始化程序,并且此过程会突然完成同样的异常。 Otherwise, continue with step 5. 否则,继续步骤5。

  5. Execute the rest of the body of this constructor. 执行此构造函数的其余部分。 If that execution completes abruptly, then this procedure completes abruptly for the same reason. 如果执行突然完成,则此过程突然完成,原因相同。 Otherwise, this procedure completes normally. 否则,此过程正常完成。

Special attention to step 4 which explains that your variables are initialized during the object initialization. 特别注意第4步,它解释了在对象初始化期间初始化变量。

Why is this important? 为什么这很重要? Because Guice first create objects (and then all the steps above will happen) and later performs the injection binds (see Guice Bootstrap and Guice InjectionPoints for more details). 因为Guice首先创建对象(然后将发生上述所有步骤),然后执行注入绑定(有关详细信息,请参阅Guice BootstrapGuice InjectionPoints )。 So, your fields are requiring, at object initialization, variables ( application ) that aren't injected yet resulting in a NullPointerException . 因此,在对象初始化时,您的字段需要未注入的变量( application ),从而导致NullPointerException

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM