πŸͺ΄ Spring Bean Scope

GunhoΒ·2024λ…„ 12μ›” 28일
0

Object Oriented Programming (OOP) & Java

λͺ©λ‘ 보기
27/29

πŸͺ΄ Spring Bean Scope

πŸͺ΄ Spring Bean Scope defines the scope of the created beans where the scope defines the lifecycle and visibility of the bean in a given context.

Spring Bean Scope from its latest updates appears to have 6 scopes:

  • β˜•οΈ Singleton
  • 🍢 Prototype
  • πŸ™‹β€β™‚οΈRequest
  • πŸ’½ Session
  • πŸ’» Application
  • πŸ“Ÿ WebSocket

where these spring bean scopes will be dicsussed in the following sections.


β˜•οΈ Singleton

β˜•οΈ Singleton scopes a single bean definition to a single object for each Spring IoC Container.

Singleton, in other words, implies that there will be only one instance that is shared across a context. Singleton is a default scope of the Spring Framework.

Below visual could best describe the Singleton:

Spring Available at here


In depth, Singleton in Spring Bean Scope varies to the singleton from a programming pattern as the latter defines a single instance over each ClassLoader while the earlier defines a single instance over a single container.


🍢 Prototype

🍢 Prototype scopes a single bean definition to any number of object instances.

Prototype, hence refers to non-singleton scopes where every time a bean with a scope of prototype becomes necessary, such bean will be instantiated individually.

Prototype is often implemented if a bean has to preserve its stateful information that is often represented as instance variables

Below visual could be a good representation of a Spring Bean of Prototype scopes:

Spring Available at here


Spring IoC Container is only responsible for the earliest configurations, dependency injections, and initialisations of the prototype beans in which the whole lifecycle of the prototype beans will not be managed by Spring IoC Container. Thus, annotations like @PreDestroy may not be invoked on the prototype beans.


πŸ™‹β€β™‚οΈ Request

πŸ™‹β€β™‚οΈ Request scopes a single bean definition to a single HTTP request in which for individual HTTP request, a bean instances available exclusively to such request will be available.

Request is only available to web-aware Spring Application-Context and below well visualises the Spring Bean instances with Request scopes:

μΈν”„λŸ° (κΉ€μ˜ν•œ) Available at here


πŸ’½ Session

πŸ’½ Session scopes single bean definition to a single HTTP session.

Session is only available to web-aware Spring Application-Context.


πŸ’» Application

πŸ’» Application scopes single bean definition to the lifecycle of a Servlet Context.

Application is only available to web-aware Spring Application-Context.


πŸ“Ÿ Web Socket

πŸ“Ÿ Web Socket scopes single bean definition to the lifecycle of a Web Socket.

Web Socket is only available to web-aware Spring Application-Context.


πŸ§‘β€πŸ”¬ Scope Implementation

Spring Bean Scope can be simply implemented via @Scope annotation to any Spring Bean classes such as @Component, @Bean, and etc.

Spring Bean Scope then given the @Scope annotation's internal structure, can be configured via implicit or explict configurations like @Scope("prototype") or @Scope(value = "prototype").

@Scope

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
  @AliasFor("scopeName")
  String value() default "";

  @AliasFor("value")
  String scopeName() default "";

  ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

PrototypeBean

@Scope("prototype")
static class PrototypeBean {
	...
}

// followed by 

// PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);

// from a client code

🀦 Issue #1

Singleton Beans with prototype bean dependencies can incur issues that could violate the designed implementation of the prototype beans.

Specifically, given that the singleton only allows a single instance over a single Spring Container, the injected prototype bean may function as a singleton as the prototype bean is coupled to such single bean instances.

Below code snippet illustrates how prototype bean dependencies coupled to a spring singleton bean could behave as a singleton.

Example 1

public class SingletonWithPrototypeTest1 {
  	@Test
  	void singletonClientUsePrototype() {
    	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
    
    	ClientBean clientBean1 = ac.getBean(ClientBean.class);
    	int count1 = clientBean1.logic();
    	assertThat(count1).isEqualTo(1);
    
    	ClientBean clientBean2 = ac.getBean(ClientBean.class);
    	int count2 = clientBean2.logic();
    	assertThat(count2).isEqualTo(2);
  	}
  
    static class ClientBean {
    	private final PrototypeBean prototypeBean;
    	
        @Autowired
    	public ClientBean(PrototypeBean prototypeBean) {
    		this.prototypeBean = prototypeBean;
    	}
        
      	public int logic() {
        	prototypeBean.addCount();
        	int count = prototypeBean.getCount();
        	return count;
      	}
    }
    
    @Scope("prototype")
    static class PrototypeBean {
    	private int count = 0;
      	
        public void addCount() {
      		count++;
      	}
        
      	public int getCount() {
      		return count;
      	}
        
      	@PostConstruct
      		public void init() {
      	System.out.println("PrototypeBean.init " + this);
      	}
        
    	@PreDestroy
    	public void destroy() {
    		System.out.println("PrototypeBean.destroy");
    	}
  	}
}

πŸ§ͺ Solution 1

To enable prototype beans to be instantiated every time the client bean, invokes a logic, the following approach works where it autowires the application context and acquires a prototype bean every time prototype bean is required:

Code 1

public class SingletonWithPrototypeTest1 {
  	@Test
  	void singletonClientUsePrototype() {
    	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
    
    	ClientBean clientBean1 = ac.getBean(ClientBean.class);
    	int count1 = clientBean1.logic();
    	assertThat(count1).isEqualTo(1);
    
    	ClientBean clientBean2 = ac.getBean(ClientBean.class);
    	int count2 = clientBean2.logic();
    	assertThat(count2).isEqualTo(1);
  	}
  
    static class ClientBean {
    	@Autowired
		private ApplicationContext ac;
        
      	public int logic() {
        	PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
          	prototypeBean.addCount();
  		  	int count = prototypeBean.getCount();
  	      	return count;
      	}
    }
    
    @Scope("prototype")
    static class PrototypeBean {
    	private int count = 0;
      	
        public void addCount() {
      		count++;
      	}
        
      	public int getCount() {
      		return count;
      	}
        
      	@PostConstruct
      		public void init() {
      	System.out.println("PrototypeBean.init " + this);
      	}
        
    	@PreDestroy
    	public void destroy() {
    		System.out.println("PrototypeBean.destroy");
    	}
  	}
}

πŸ§ͺ Solution 2

Another way around to achieve the above consequence is using the Provider, which is already implemented in the Spring Framework where the Provider automatically finds a defined Spring Bean while maintaining its scope strategy.

Code 2

public class SingletonWithPrototypeTest1 {
  	@Test
  	void singletonClientUsePrototype() {
    	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
    
    	ClientBean clientBean1 = ac.getBean(ClientBean.class);
    	int count1 = clientBean1.logic();
    	assertThat(count1).isEqualTo(1);
    
    	ClientBean clientBean2 = ac.getBean(ClientBean.class);
    	int count2 = clientBean2.logic();
    	assertThat(count2).isEqualTo(1);
  	}
  
    static class ClientBean {
        @Autowired
        private Provider<PrototypeBean> provider;
        
        public int logic() {
        	PrototypeBean prototypeBean = provider.get();
        	prototypeBean.addCount();
        	int count = prototypeBean.getCount();
        	return count;
        }
    }
    
    @Scope("prototype")
    static class PrototypeBean {
    	private int count = 0;
      	
        public void addCount() {
      		count++;
      	}
        
      	public int getCount() {
      		return count;
      	}
        
      	@PostConstruct
      		public void init() {
      	System.out.println("PrototypeBean.init " + this);
      	}
        
    	@PreDestroy
    	public void destroy() {
    		System.out.println("PrototypeBean.destroy");
    	}
  	}
}

🀦 Issue #2

Another common issue with the Spring Bean scope is with the web-aware aspects. For instance, given the below Spring Bean with the Request scope:

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {

  private String uuid;
  private String requestURL;

  public void setRequestURL(String requestURL) {
    this.requestURL = requestURL;
  }

  public void log(String message) {
    System.out.println("[" + uuid + "]" + "[" + requestURL + "] " + message);
  }

  @PostConstruct
  public void init() {
    uuid = UUID.randomUUID().toString();
    System.out.println("[" + uuid + "]  request scope bean created: " + this);
  }

  @PreDestroy
  public void close() {
    System.out.println("[" + uuid + "] request scope bean close: " + this);
  }
}

Application fails to run with the given error statements where a Spring Bean can not be simply created out of non-existing requests:

Error creating bean with name 'myLogger': Scope 'request' is not active for the
current thread; consider defining a scoped proxy for this bean if you intend to
refer to it from a singleton;

πŸ§ͺ Solution 1

One good solution is to use the above mentioned Provider:

Code 1

@Controller
@RequiredArgsConstructor
public class LogDemoController {
    private final LogDemoService logDemoService;
    private final Provider<MyLogger> myLoggerProvider;
    
    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
    	String requestURL = request.getRequestURL().toString();
    	MyLogger myLogger = myLoggerProvider.get();
    	myLogger.setRequestURL(requestURL);
    	myLogger.log("controller test");
    	logDemoService.logic("testId");
    	return "OK";
    }
}

@Service
@RequiredArgsConstructor
public class LogDemoService {

	private final Provider<MyLogger> myLoggerProvider;

  	public void logic(String id) {
    	MyLogger myLogger = myLoggerProvider.get();
    	myLogger.log("service id = " + id);
  }
}

πŸ§ͺ Solution 2

Another approach is to use proxies in which we, the developers instruct the Spring Framework to fill in the proxy instances for the Request or any other Web-Scoped instances and let Spring Framework run the actual logic with the Web-Scoped Beans once necessary.

μΈν”„λŸ° (κΉ€μ˜ν•œ) Available at here


In Spring, this can be easily implemented via proxyMode() property within the @Scope annotation:

Code 2

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {

  private String uuid;
  private String requestURL;

  public void setRequestURL(String requestURL) {
    this.requestURL = requestURL;
  }

  public void log(String message) {
    System.out.println("[" + uuid + "]" + "[" + requestURL + "] " + message);
  }

  @PostConstruct
  public void init() {
    uuid = UUID.randomUUID().toString();
    System.out.println("[" + uuid + "]  request scope bean created: " + this);
  }

  @PreDestroy
  public void close() {
    System.out.println("[" + uuid + "] request scope bean close: " + this);
  }
}

Then autowiring features provided by the Spring Framework via @Auwowired just functions normally like any other non Web-Scoped Beans.


πŸ“š References

Spring
μΈν”„λŸ° (κΉ€μ˜ν•œ)

profile
Hello

0개의 λŒ“κΈ€