Search code examples
javaspringscopejavabeansboot

Spring Boot @RequestScope Constructor not printing in console


I am trying to understand different Bean Scopes. When working with @RequestScope I don't see message printed to console from constructor and destroy() method in order to confirm when this Bean is really being created or destroyed. This works for @Singleton and @Prototype Beans.

Also person1.counter++; doesn't seam to be reset after every request meaning that Bean isn't really recreated for each request as the name might suggest.

But if I add increaseCounter() to Person.java and call that from the Controller then counter is reset and both constructor and destroy() method messages are printed in console. What is going on here. In tutorials about @RequestScope none of this is mentioned.

  int increaseCounter() {
    counter++;
    return counter;
  }

I was also able to change the code so that "Person Created" and "Person Destroyed" are being displayed but person1.counter++; is never reset as if the Bean is not really being destroyed.

Person.java

package com.ivoronline.springboot_accessories_beans_scope_request;

import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;

@Component
@RequestScope
public class Person {

  //PROPERTIES
  public String name;
  public int    counter;
  
  //CONSTRUCTOR
  Person() {
    System.out.println("Person Created");
  }
  
  //DESTROY
  @PreDestroy
  public void destroy() {
    System.out.println("Person Destroyed");
  }

}

Controller.java

package com.ivoronline.springboot_accessories_beans_scope_request;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller {

  //PROPERTIES
  @Autowired Person person1; //New Instance/Bean
  @Autowired Person person2; //New Instance/Bean
  
  //=========================================================================================================
  // SET PERSON NAME
  //=========================================================================================================
  @RequestMapping("setPersonName")
  void setPerson() {
    person1.name = "John";
    person2.name = "Bill";
    person1.counter++;
  }
  
  //=========================================================================================================
  // GET PERSON NAME
  //=========================================================================================================
  @RequestMapping("getPersonName")
  String getPerson() {
    return person1.counter + ":" + person1.name + " - " + person2.name;
  }

}

Solution

  • When using scopes other than singleton and prototype it starts to involve lazy proxies.

    @RestController
    public class Controller {
    
      @Autowired Person person1; //New Instance/Bean
      @Autowired Person person2; //New Instance/Bean
      
      @RequestMapping("setPersonName")
      void setPerson() {
        person1.name = "John";
        person2.name = "Bill";
        person1.counter++;
      }
    
      @RequestMapping("getPersonName")
      String getPerson() {
        return person1.counter + ":" + person1.name + " - " + person2.name;
      }
    
    }
    

    What happens in this class is that 2 proxies for Person are injected. Those proxies do nothing. When you call a method on the proxy what it actually does is go to the request attributes (as it is a request scoped bean), find an attribute named person1 or person2 and return the Person instance found (or create a new one if needed).

    NOTE: You can also check this and you will probably see that person1 is of type Person$SpringCGLib$.... something and not a Person directly.

    However in your code you are directly accessing public scoped fields. Which are set on the proxy and not on the actually request scoped object. This is because there is no way to intercept the direct setting/accessing of fields.

    Scoped proxies only work with method calls (as you already discovered yourself when you added the increaseCounter method. So to make this work you will need methods like setName and increment to operate on the object and not do field access like you are doing now.

    The Spring Framework reference guide also explains this in a section called Scoped Beans as Dependencies.