πͺ΄
Spring Bean Scope
defines thescope
of the createdbeans
where thescope
defines thelifecycle
andvisibility
of thebean
in a givencontext
.
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
scopes asingle bean definition
to asingle object
for eachSpring 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
scopes asingle bean definition
to any number ofobject 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
scopes asingle bean definition
to a singleHTTP request
in which for individualHTTP request
, abean instances
available exclusively to suchrequest
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
scopes single bean definition to a single HTTP session.
Session
is only available to web-aware
Spring Application-Context
.
π»
Application
scopes single bean definition to the lifecycle of a Servlet Context.
Application
is only available to web-aware
Spring Application-Context
.
π
Web Socket
scopes single bean definition to the lifecycle of a Web Socket.
Web Socket
is only available to web-aware
Spring Application-Context
.
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
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");
}
}
}
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");
}
}
}
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");
}
}
}
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;
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);
}
}
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
.