|
1 |
| -FIXME: add a description |
2 | 1 |
|
3 |
| -// If you want to factorize the description uncomment the following line and create the file. |
4 |
| -//include::../description.adoc[] |
| 2 | +In Spring Boot the scope of a bean defines the lifecycle and visibility of that bean in the Spring container. |
| 3 | +There are six scopes: |
| 4 | + |
| 5 | +- *Singleton*: default, one instance per Spring container |
| 6 | +- *Prototype*: a new instance per bean request |
| 7 | +- *Request*: a new instance per HTTP request |
| 8 | +- *Session*: a new instance per HTTP session |
| 9 | +- *Application*: a new instance per ServletContext |
| 10 | +- *Websocket*: a new instance per Websocket session |
| 11 | +
|
| 12 | +The last four scopes mentioned, request, session, application and websocket, are only available in a web-aware application. |
5 | 13 |
|
6 | 14 | == Why is this an issue?
|
7 | 15 |
|
8 |
| -FIXME: remove the unused optional headers (that are commented out) |
| 16 | +In Spring Boot, Singleton beans and their dependencies are initialized when the application context is created. |
| 17 | + |
| 18 | +If a Singleton bean depends on a bean with a shorter-lived scope (like a Request or Session scoped bean), |
| 19 | +it retains the same instance of that bean, even when new instances are created for each Request or Session. |
| 20 | +This mismatch can cause unexpected behavior and bugs, as the Singleton bean doesn't interact correctly with the new instances of the shorter-lived bean. |
| 21 | + |
| 22 | +This rule raises an issue for the injection of non-Singleton beans in a Singleton bean. |
| 23 | + |
| 24 | +=== What is the potential impact? |
9 | 25 |
|
10 |
| -//=== What is the potential impact? |
| 26 | +When a Singleton bean has a dependency on a bean with a shorter-lived scope, it can lead to the following issues: |
| 27 | + |
| 28 | +- *Data inconsistency*: any state change in the shorter-lived bean will not be reflected in the Singleton bean. |
| 29 | + |
| 30 | +- *Incorrect behavior*: using the same instance of the shorter-lived bean, when a new instance is supposed to be created for each new request or session. |
| 31 | + |
| 32 | +- *Memory leaks*: preventing garbage collection of a shorter-lived bean that allocates a significant amount of data over time. |
11 | 33 |
|
12 | 34 | == How to fix it
|
13 |
| -//== How to fix it in FRAMEWORK NAME |
| 35 | + |
| 36 | +Inject a shorter-lived bean into a Singleton bean using *ApplicationContext*, *Factories* or *Providers*. |
14 | 37 |
|
15 | 38 | === Code examples
|
16 | 39 |
|
17 | 40 | ==== Noncompliant code example
|
18 | 41 |
|
19 |
| -[source,text,diff-id=1,diff-type=noncompliant] |
| 42 | +When a Singleton bean auto-wires a Request scoped bean, the dependency is resolved at instantiation time and thus the same instance is used for each HTTP request. |
| 43 | + |
| 44 | +[source,java,diff-id=1,diff-type=noncompliant] |
| 45 | +---- |
| 46 | +@Component |
| 47 | +@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) |
| 48 | +public class RequestBean { |
| 49 | + //... |
| 50 | +} |
| 51 | +
|
| 52 | +public class SingletonBean { |
| 53 | + @Autowired |
| 54 | + private final RequestBean requestBeanFactory; // Noncompliant, the same instance of RequestBean is used for each HTTP request. |
| 55 | +
|
| 56 | + public RequestBean getRequestBean() { |
| 57 | + return requestBeanFactory; |
| 58 | + } |
| 59 | +} |
| 60 | +---- |
| 61 | + |
| 62 | +==== Compliant solution |
| 63 | + |
| 64 | +Using as injection point a `ObjectFactory<RequestBean>`, s `ObjectProvider<RequestBean>` or a `Provider<RequestBean>` (as for JSR-330). |
| 65 | + |
| 66 | +Such a dependency is resolved at runtime, allowing for actual injection of a new instance of the shorter-lived bean on each HTTP request. |
| 67 | + |
| 68 | +[source,java,diff-id=1,diff-type=compliant] |
| 69 | +---- |
| 70 | +@Component |
| 71 | +@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) |
| 72 | +public class RequestBean { |
| 73 | + //... |
| 74 | +} |
| 75 | +
|
| 76 | +public class SingletonBean { |
| 77 | + private final ObjectFactory<RequestBean> requestBeanFactory; |
| 78 | +
|
| 79 | + @Autowired |
| 80 | + public SingletonBean(ObjectFactory<RequestBean> requestBeanFactory) { |
| 81 | + this.requestBeanFactory = requestBeanFactory; |
| 82 | + } |
| 83 | +
|
| 84 | + public RequestBean getRequestBean() { |
| 85 | + return requestBeanFactory.getObject(); |
| 86 | + } |
| 87 | +} |
| 88 | +---- |
| 89 | + |
| 90 | + |
| 91 | +==== Noncompliant code example |
| 92 | + |
| 93 | +When a Singleton bean auto-wires a Prototype bean, the dependency is resolved at instantiation time and thus the same instance is used for each bean request. |
| 94 | + |
| 95 | +[source,java,diff-id=2,diff-type=noncompliant] |
20 | 96 | ----
|
21 |
| -FIXME |
| 97 | +@Component |
| 98 | +@Scope("prototype") |
| 99 | +public class PrototypeBean { |
| 100 | + public Object execute() { |
| 101 | + //... |
| 102 | + } |
| 103 | +} |
| 104 | +
|
| 105 | +public class SingletonBean { |
| 106 | + private PrototypeBean prototypeBean; |
| 107 | +
|
| 108 | + @Autowired |
| 109 | + public SingletonBean(PrototypeBean prototypeBean) { // Noncompliant, the same instance of PrototypeBean is used for each bean request. |
| 110 | + this.prototypeBean = prototypeBean; |
| 111 | + } |
| 112 | +
|
| 113 | + public Object process() { |
| 114 | + return prototypeBean.execute(); |
| 115 | + } |
| 116 | +} |
22 | 117 | ----
|
23 | 118 |
|
24 | 119 | ==== Compliant solution
|
25 | 120 |
|
26 |
| -[source,text,diff-id=1,diff-type=compliant] |
| 121 | +Using the `ApplicationContext` to retrieve a new instance of a Prototype bean on each bean request. |
| 122 | + |
| 123 | +[source,java,diff-id=2,diff-type=compliant] |
27 | 124 | ----
|
28 |
| -FIXME |
| 125 | +
|
| 126 | +@Component |
| 127 | +@Scope("prototype") |
| 128 | +public class PrototypeBean { |
| 129 | + public Object execute() { |
| 130 | + //... |
| 131 | + } |
| 132 | +} |
| 133 | +
|
| 134 | +public class SingletonBean implements ApplicationContextAware { |
| 135 | + private ApplicationContext applicationContext; |
| 136 | +
|
| 137 | + @Autowired |
| 138 | + public SingletonBean(ApplicationContext applicationContext) { |
| 139 | + this.applicationContext = applicationContext; |
| 140 | + } |
| 141 | +
|
| 142 | + public Object process() { |
| 143 | + PrototypeBean prototypeBean = createPrototypeBean(); |
| 144 | + return prototypeBean.execute(); |
| 145 | + } |
| 146 | +
|
| 147 | + protected PrototypeBean createPrototypeBean() { |
| 148 | + return this.applicationContext.getBean("prototypeBean", PrototypeBean.class); |
| 149 | + } |
| 150 | +} |
29 | 151 | ----
|
30 | 152 |
|
31 |
| -//=== How does this work? |
32 | 153 |
|
33 |
| -//=== Pitfalls |
| 154 | +== Resources |
| 155 | + |
| 156 | +=== Documentation |
| 157 | + |
| 158 | +* Spring Framework - https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html[Factory Scopes] |
| 159 | +* Spring Framework - https://docs.spring.io/spring-framework/reference/core/beans/standard-annotations.html#beans-inject-named[Beans Inject Named] |
| 160 | +* Spring Framework - https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-method-injection.html[Method Injection] |
34 | 161 |
|
35 |
| -//=== Going the extra mile |
| 162 | +=== Articles & blog posts |
36 | 163 |
|
| 164 | +* Baeldung - https://www.baeldung.com/spring-bean-scopes[Spring Bean Scopes] |
37 | 165 |
|
38 |
| -//== Resources |
39 |
| -//=== Documentation |
40 |
| -//=== Articles & blog posts |
41 |
| -//=== Conference presentations |
42 |
| -//=== Standards |
43 |
| -//=== External coding guidelines |
44 |
| -//=== Benchmarks |
|
0 commit comments