1

I have a class the following class as RequestScope bean:

@RequestScope
class RequestContext {

  private String requestId;
  private String traceId; 
  private String authorisedId; 
  private String routeName; 
    
  // few more fields 

  @Inject RequestContext(SecurityContext securityContext) {
        this.requestId = UUID.randomUUID().toString();
        if(securityService.getAuthentication().isPresent()){
          this.authorisedId = (securityService
                              .getAuthentication().get()).getUserId().toString();
    }
  }
  
  /* to be updated in controller method interceptors */ 
  public void updateRouteName(String name){
      this.routeName = name; 
  }

The idea is to have an object containing the REST request level custom data accessible across the application, the scope of the this obviously should be within the current request. This can be used for say.. logging - whenever devs log anything from the application, some of the request meta data goes with it.

I am not clear what the @RequestScope bean really is:

From its definition - my assumption is it is created for every new http-request and same instance is shared for the life of that request.

when is it constructed by Micronaut ? Is it immutable ?

Across multiple requests I can see the same requestId ( expecting new UUID for every request)

Is it the right use-case for @RequestScope bean?

Sreerag
  • 1,381
  • 3
  • 11
  • 16
  • This may not working if your request-scoped bean is injected in a Singleton bean. In this situation, you need to inject the bean with Provider – Cyril G. Sep 01 '20 at 13:16
  • @CyrilG. thanks for the response. Could you give me further insight into when is the request-scoped bean created for the first time? Tried injecting Provider into singleton beans, it fails with a NonUniqueBeanException : Multiple possible bean candidates found - when exactly where they even created ? – Sreerag Sep 01 '20 at 14:34
  • @CyrilG. not sure what error that was, seems to have cleared. But with the Provider provider injected, every time provider.get() is called, it creates a new instance of RequestContext beating the purpose of request-scope – Sreerag Sep 01 '20 at 14:58
  • @CyrilG. you were right about using `Provider`. Found here https://github.com/micronaut-projects/micronaut-core/issues/1615 – Archmede Aug 26 '21 at 19:11
  • @Sreerag I presume you `NonUniqueBeanException` was caused because there are already other beans named `RequestContext` so micronaut didn't know which one to inject – Archmede Aug 26 '21 at 19:12

2 Answers2

3

I was running into an issue regarding @RequestScope so I'll post an answer here for others.

I was trying to inject a @RequestScope bean into an HTTP filter, set a value in the bean, and then read it later from another bean. For example

@RequestScope
class RequestScopeBean() {
    var id: Int? = null
}


@Filter
class SetRequestScopeBeanHere(
    private val requestScopeBean: Provider<RequestScopeBean>
) {

    override fun doFilterOnce(request: HttpRequest<*>, chain: ServerFilterChain): Publisher<MutableHttpResponse<*>> {
        requestScopeBean.get().id = // id from Http Request
    }
}


@Singleton
class GetRequestScopeBeanHere(
    private val requestScopeBean: Provider<RequestScopeBean>
) {

    fun getIdFromRequestScopeBean() {
        println(requestScopeBean.get().id)
    }
}

In this example before any controller is executed my filter (SetRequestScope) is called, this will set requestScopeBean.id but the key is that the request scope bean must be wrapped in a javax.inject.Provider, otherwise setting the field won't work.

Down the line, when GetRequestScopeBeanHere::getIdFromRequestScopeBean is called it'll have access to the requestScopeBean.id set earlier

This is intentional by Micronaut: https://github.com/micronaut-projects/micronaut-core/issues/1615

Archmede
  • 1,592
  • 2
  • 20
  • 37
1

when is it constructed by Micronaut ?

A @RequestScope bean is created during request processing, the first time the bean is needed.

Is it immutable ?

It could be. You get to decide if the bean is mutable or not when you write the class. As written in your example, RequestContext is mutable. If you remove the updateRouteName method, that bean would be immutable.

Is it the right use-case for @RequestScope bean?

I don't think so, but that is really an opinion based question.

EDIT: Based On Comments Added Below

See the project at https://github.com/jeffbrown/rscope.

https://github.com/jeffbrown/rscope/blob/2935a4c1fc60f350198d7d3c1dbf9a7eedd333b3/src/main/java/rscope/DemoController.java

package rscope;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/")
public class DemoController {

    private final DemoBean demoBean;

    public DemoController(DemoBean demoBean) {
        this.demoBean = demoBean;
    }

    @Get("/doit")
    public String doit() {
        return String.format("Bean identity: %d", demoBean.getBeanIdentity());
    }
}

https://github.com/jeffbrown/rscope/blob/2935a4c1fc60f350198d7d3c1dbf9a7eedd333b3/src/main/java/rscope/DemoBean.java

package rscope;

import io.micronaut.runtime.http.scope.RequestScope;

@RequestScope
public class DemoBean {
    public DemoBean() {
    }

    public int getBeanIdentity() {
        return System.identityHashCode(this);
    }
}

https://github.com/jeffbrown/rscope/blob/2935a4c1fc60f350198d7d3c1dbf9a7eedd333b3/src/test/java/rscope/DemoControllerTest.java

package rscope;

import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.annotation.MicronautTest;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;

import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@MicronautTest
public class DemoControllerTest {

    @Inject
    @Client("/")
    RxHttpClient client;

    @Test
    public void testIndex() throws Exception {
        // these will contain the identity of the the DemoBean used to handle these requests
        String firstResponse = client.toBlocking().retrieve("/doit");
        String secondResponse = client.toBlocking().retrieve("/doit");

        assertTrue(firstResponse.matches("^Bean identity: \\d*$"));
        assertTrue(secondResponse.matches("^Bean identity: \\d*$"));

        // if you modify DemoBean to be @Singleton instead of
        // @RequestScope, this will fail because the same instance
        // will be used for both requests
        assertNotEquals(firstResponse, secondResponse);
    }
}
Jeff Scott Brown
  • 26,804
  • 2
  • 30
  • 47
  • Thanks for the response. On the immutability, I understand the class written is mutable as you have pointed out, but my question was once the bean is constructed the first time it is needed, it doesn't seem to the injecting the mutated object ( updateRouteName ) elsewhere within the scope of the request. Also, how can the instance created for a new request be injected into a Singleton bean? – Sreerag Sep 02 '20 at 14:04
  • "how can the instance created for a new request be injected into a Singleton bean" - All of the usual injection methods word (constructor, property, or field). – Jeff Scott Brown Sep 02 '20 at 14:57
  • Yes but when injected into a singleton bean, it retains the same reference to request-scoped bean throughout the application life time ( naturally, because the container bean is singleton itself ). Hence, it perhaps should a proxy to request-scoped bean that gets injected into a singleton container bean and the proxy should be able to operate on the request-scoped bean for the current request. Not sure how to achieve this though – Sreerag Sep 03 '20 at 05:53
  • "Yes but when injected into a singleton bean, it retains the same reference to request-scoped bean throughout the application life time" - I don't think that is correct. "t perhaps should a proxy to request-scoped bean that gets injected into a singleton container bean and the proxy should be able to operate on the request-scoped bean for the current request." - That is what happens. – Jeff Scott Brown Sep 03 '20 at 11:58
  • See the project at https://github.com/jeffbrown/rscope. The test at https://github.com/jeffbrown/rscope/blob/2935a4c1fc60f350198d7d3c1dbf9a7eedd333b3/src/test/java/rscope/DemoControllerTest.java demonstrates that the request scoping is working. – Jeff Scott Brown Sep 03 '20 at 12:21
  • Thanks a tonne for the effort ! Yes it is working as expected ! I was blinded by the hashCode of the proxy object ( which i thought was not the case ) . I tested it out on top of the your codebase for few use cases and it seems fine. Another thing puzzling was the number of times the request-scoped (DemoBean) constructor getting called within the same request while debugging - this is not really the case at runtime though. Thanks again for the quick and easy test provided. Cheers ! – Sreerag Sep 03 '20 at 14:26
  • "Thanks a tonne for the effort !" - Of course. I am happy to help. It really wasn't a lot of effort though. That sample app took about 5 minutes to write and push to GitHub. Often a simple example like that is helpful to relay and clarify what is going on. I am glad you got it all worked out. :) – Jeff Scott Brown Sep 03 '20 at 16:04
  • "I was blinded by the hashCode of the proxy object" - Right. Makes sense. You don't need a new proxy for every request. You just need a new thing that the proxy is proxying, for every request. – Jeff Scott Brown Sep 03 '20 at 16:06