I have this in my "routes" file:
POST /accounts/ controllers.AccountsController.createOneAccount
And in my 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)
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
I can resolve this by adding @ to routes file:
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 yourroutes
file:https://stackoverflow.com/a/34867199/4600
Then, as stated by Play 2.5.x migration docs:
So, starting at Play 2.5.0, controllers use dependency injection by default and you don't need
@
to make them use dependency injection.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 docs also recommends that you try to inject only direct dependencies. In your case, you are usingapplication
to access aconfiguration
. Why not inject theconfiguration
object instead? This will make your dependencies more clear (which will make, per instance, testing easier).So, following this recommendations, your code would be rewritten to:
But why the field injection was breaking?
We first need to understand object initialization. According to Java specs:
Special attention to step 4 which explains that your variables are initialized during the object initialization.
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). So, your fields are requiring, at object initialization, variables (
application
) that aren't injected yet resulting in aNullPointerException
.