kotlinmicronaut

Micronaut @RequestScope creates a new instance with each injection


I'm trying to create Micronaut bean with @RequestScope, but with each injection, there is a new instance created. I would expect it to create new instance only once per request.

Here is an example code:

@RequestScope
class RequestContext {
    var foo: String? = null
}

@Controller("/example")
class ExampleController(
    val requestContext: RequestContext,
    val exampleService: ExampleService
) {
    @Get()
    fun example() {
        requestContext.foo = "foo"
        exampleService.example()
    }
}

@Singleton
class ExampleService(
    val requestContext: RequestContext
) {
    fun example() {
        println(requestContext.foo) // prints nul instead of "foo"
    }
}

I have also tried wrapping RequestContext in Provider like here: micronaut @RequestScope - not creating bean per incoming http-request but it changes nothing.

What am doing wrong? How can I properly inject RequesScope bean into Singleton?

Micronaut Version: 4.5.1 Kotlin Version: 1.9.23


Solution

  • I could reproduce the behavior you describe.

    I made your code work by following the pattern with getter/setter from this guide: Micronaut Scope Types

    New version of class RequestContext with getter/setter:

    import io.micronaut.runtime.http.scope.RequestScope
    
    @RequestScope
    class RequestContext {
        private var foo: String? = null
    
        fun getFoo(): String? = foo
    
        fun setFoo(foo: String) {
            this.foo = foo
        }
    }
    

    ExampleService:

    import jakarta.inject.Singleton
    
    @Singleton
    class ExampleService(
        private val requestContext: RequestContext
    ) {
        fun example() {
            println(requestContext.getFoo()) // will now print "foo"
        }
    }
    

    ExampleController:

    import io.micronaut.http.annotation.Controller
    import io.micronaut.http.annotation.Get
    
    @Controller("/example")
    class ExampleController(
        private val requestContext: RequestContext,
        private val exampleService: ExampleService
    ) {
        @Get
        fun example() {
            requestContext.setFoo("foo")
            exampleService.example()
        }
    }
    

    An interesting observation (not part of the question) is that with a class like this:

    import io.micronaut.http.HttpRequest
    import io.micronaut.runtime.http.scope.RequestAware
    import io.micronaut.runtime.http.scope.RequestScope
    
    @RequestScope
    class RequestContext : RequestAware {
        var foo: String? = null
    
        override fun setRequest(request: HttpRequest<*>?) {
            this.foo = "bar"
        }
    }
    

    code compiles, but setRequest is never called.

    Making foo private with getter getFoo, setRequest will be invoked.

    import io.micronaut.http.HttpRequest
    import io.micronaut.runtime.http.scope.RequestAware
    import io.micronaut.runtime.http.scope.RequestScope
    
    @RequestScope
    class RequestContext : RequestAware {
        private var foo: String? = null
    
        override fun setRequest(request: HttpRequest<*>?) {
            this.foo = "bar"
        }
    
        fun getFoo(): String? = this.foo
    }