Play Framework 2.5.1 routing and dependency (for Java)

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 compiles fine, but at runtime I got an error:

ProvisionException: The following errors cannot be executed: 1) Error entering constructor, java.lang.NullPointerException in controllers.AccountsController. (AccountsController.java:11)
upon detection of controllers. for parameter 1 on the .Routes router. (Routes.scala: 28) when searching for a router. Routes when searching for play.api.inject.RoutesProvider when searching for play.api.routing.Router for parameter 0 in play.api.http.JavaCompatibleHttpRequestHandler. (HttpRequestHandler.scala: 200) when searching for play.api.http.JavaCompatibleHttpRequestHandler while searching for play.api.http.HttpRequestHandler for parameter 4 in play.api.DefaultApplication. (Application.scala: 221) at play.api.DefaultApplication.class (Application.scala: 221) while searching for play.api.DefaultApplication while searching for play.api.Application 1 error

I can solve this problem by adding @ to route the file:

 POST /accounts/ @controllers.AccountsController.createOneAccount 

but I'm not sure why I need this, and how to avoid the "@". Please give some advice.

+5
source share
1 answer

First look at this answer to understand the difference between using or not @ in the routes file:

fooobar.com/questions/1247158 / ...

Then, as indicated by Play 2.5.x docs :

Routes are now generated using the InjectedRoutesGenerator dependency nesting rather than the previous StaticRoutesGenerator , which assume that the controllers are singleton objects.

So, starting with Play 2.5.0, controllers use dependency injection by default, and you don't need @ to force them to use dependency injection.


Now let's see what happens in your case. First of all, let me say that constructor injection is the preferred way of dependency injection. Guice even recommends (as best practice) combining the final fields with the introduction of a constructor to minimize variability . Guice docs also recommend that you use embed direct dependencies only . In your case, you use application to access configuration . Why not introduce a configuration object instead? This will make your dependencies clearer (which will simplify, for example, testing).

So, following these recommendations, your code will 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 was field injection disrupted?

First we need to understand the initialization of the object. According to the Java specification :

Before the result is a reference to the newly created object, the specified constructor is processed to initialize the new object using the following procedure:

  • Assign constructor arguments to the newly created parameter variables for this constructor call.

  • If this constructor starts by explicitly calling the constructor (ยง8.8.7.1) of another constructor in the same class (using this), then evaluate the arguments and process the constructor call recursively using these five steps. If the constructor call terminates abruptly, then this procedure terminates abruptly for the same reason; otherwise go to step 5.

  • This constructor does not start by explicitly calling the constructor of another constructor in the same class (using this). If this constructor belongs to a class other than Object, then this constructor will start by explicitly or implicitly calling the constructor of the superclass (using super). Evaluate the arguments and handle the call to the superclass constructor recursively using these five steps. If the constructor call terminates abruptly, then this procedure terminates abruptly for the same reason. Otherwise, go to step 4.

  • Run instance initializers and instance variable initializers for this class, assigning the initializer values โ€‹โ€‹of the instance variable to the corresponding instance variables in the order from left to right, in which they are displayed in textual form in the source code for the class . If the execution of any of these initializers results in an exception, then no new initializers are processed, and this procedure terminates abruptly with the same exception. Otherwise, go to step 5.

  • Run the rest of the body of this constructor. If this execution completes abruptly, then this procedure terminates abruptly for the same reason. Otherwise, this procedure is performed normally.

Particular attention in step 4 explains that your initializations are initialized during object initialization.

Why is it important? Because Guice first creates the objects (and then all of the above steps are done) and then does the injection bindings (see Guice Bootstrap and Guice InjectionPoints for more details). So, your fields require application variables when initializing the object, which are not entered, but lead to a NullPointerException .

+8
source

Source: https://habr.com/ru/post/1247156/


All Articles