Guice @Assisted doesn't work after migrating from 2.5 to 2.6


(Ihor Kamynin) #1

I was migrating my application from 2.5 to 2.6 and stuck with errors regarding @Assisted annotation in my code.
I reproduced the issue in the Play2.6 startup application from Idea IDE

Here is my controller

package controllers

import javax.inject._
import play.api.mvc._
import services.TestService

@Singleton
class HomeController @Inject()(testService: TestService, cc: ControllerComponents) extends AbstractController(cc) {

  def test = Action {
    Ok(views.html.index("Assisted service says: " + testService.helloFromAssisted))
  }

}

My TestService

package services

import java.time.Clock

import com.google.inject.assistedinject.Assisted
import javax.inject._

class TestService @Inject()(@Assisted val assistedTestService: AssistedTestService, clock: Clock) {

  def helloFromAssisted = assistedTestService.helloString
}

trait TestServiceFactory {
  def create(assistedTestService: AssistedTestService): TestService
}

My AssistedTestService

package services

import java.time.Clock
import javax.inject._

class AssistedTestService @Inject()(clock: Clock) {
  def helloString = "hello"
}

My Module

import com.google.inject.AbstractModule
import java.time.Clock

import com.google.inject.assistedinject.FactoryModuleBuilder
import services._

class Module extends AbstractModule {

  override def configure() = {
    bind(classOf[Clock]).toInstance(Clock.systemDefaultZone)
    bind(classOf[ApplicationTimer]).asEagerSingleton()
    bind(classOf[Counter]).to(classOf[AtomicCounter])
    
    install(
      new FactoryModuleBuilder()
        .implement(classOf[TestService], classOf[TestService])
        .build(classOf[TestServiceFactory])
    )

  }

}

As a result I am getting an error

CreationException: Unable to create injector, see the following errors:

1) No implementation for services.AssistedTestService annotated with @com.google.inject.assistedinject.Assisted(value=) was bound.
  while locating services.AssistedTestService annotated with @com.google.inject.assistedinject.Assisted(value=)
    for the 1st parameter of services.TestService.<init>(TestService.scala:8)
  while locating services.TestService
    for the 1st parameter of controllers.HomeController.<init>(HomeController.scala:8)
  while locating controllers.HomeController
    for the 2nd parameter of router.Routes.<init>(Routes.scala:37)
  at play.api.inject.RoutesProvider$.bindingsFromConfiguration(BuiltinModule.scala:121):
Binding(class router.Routes to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)

1 error

That was in according Guice docs and it didn’t work out for me. I googled so much and could find nothing appropriate for me. Any help is appreciated.
Thanks


(Aditya Athalye) #2

It is looking for the AssistedTestService (or its impl) to be explicitly bound in the Guice Module. Since the module doesnt have that, it doesnt how to inject that. You could try adding @AssistedInject on TestService class constructor.

HTH


(Ihor Kamynin) #3

Thanks for your reply, but at the moment it doesn’t help…

  1. Explicit bound in Guice module was’t required in Play 2.5 (and that strange). However I tried to put into my module an explicit bound and it changed nothing
bind(classOf[AssistedTestService]).to(classOf[AssistedTestServiceImpl])

and I am wondering that @Inject near the AssistedTestService have to do the thing.

  1. I tried @AssistedInject as well and it led me to another Guice error I didn’t know how to tackle
CreationException: Unable to create injector, see the following errors:

1) Could not find a suitable constructor in services.TestService. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
  at services.TestService.class(TestService.scala:8)
  while locating services.TestService
    for the 1st parameter of controllers.HomeController.<init>(HomeController.scala:8)
  while locating controllers.HomeController
    for the 2nd parameter of router.Routes.<init>(Routes.scala:37)
  at play.api.inject.RoutesProvider$.bindingsFromConfiguration(BuiltinModule.scala:121):
Binding(class router.Routes to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)

1 error

(Aditya Athalye) #4

In the second case did you add both annotations @Inject and @AssociatedInject on TestService?


(Ihor Kamynin) #5

did you mean @AssistedInject? because @AssociatedInject is not available.
And yes, I tried both of them


(Aditya Athalye) #6

Yes I mean @AssistedInject. But it is not be used with @Inject. Can you post the test service class?


(Ihor Kamynin) #7

I told in previous comments that I tried @AssistedInject for TestService. It look the same as with @Inject except one line of code


import java.time.Clock

import com.google.inject.assistedinject.{Assisted, AssistedInject}

class TestService @AssistedInject()(@Assisted val assistedTestService: AssistedTestService, clock: Clock) {

  def helloFromAssisted = assistedTestService.helloString
}

trait TestServiceFactory {
  def create(assistedTestService: AssistedTestService): TestService
}

and it leads to an error

CreationException: Unable to create injector, see the following errors:

1) Could not find a suitable constructor in services.TestService. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
  at services.TestService.class(TestService.scala:8)
  while locating services.TestService
    for the 1st parameter of controllers.HomeController.<init>(HomeController.scala:8)
  while locating controllers.HomeController
    for the 2nd parameter of router.Routes.<init>(Routes.scala:37)
  at play.api.inject.RoutesProvider$.bindingsFromConfiguration(BuiltinModule.scala:121):
Binding(class router.Routes to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)

1 error

(Ihor Kamynin) #8

It turned out, that I used my TestService in a wrong way.
When I have custom factory for TestService first of all I had to call it somewhere and only then it is possible to use it.
So I came up with a solution


@Singleton
class HomeController @Inject()(testServiceFactory: TestServiceFactory, assistedTestService: AssistedTestService, cc: ControllerComponents) extends AbstractController(cc) {
  def test = Action {
    val testService = testServiceFactory.create(assistedTestService)
    Ok(views.html.index("Assisted service says: " + testService.helloFromAssisted))
  }

}