Skip to content

Commit 773cfe0

Browse files
author
Angelo Buono
committed
Create rule S6832: Spring beans should not be injected in an incompatible scope
1 parent 32feda9 commit 773cfe0

File tree

2 files changed

+146
-26
lines changed

2 files changed

+146
-26
lines changed

rules/S6832/java/metadata.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
{
2-
"title": "FIXME",
2+
"title": "Non-singleton Spring beans should not be injected using @Autowired in Singleton beans",
33
"type": "CODE_SMELL",
44
"status": "ready",
55
"remediation": {
66
"func": "Constant\/Issue",
77
"constantCost": "5min"
88
},
99
"tags": [
10+
"spring"
1011
],
1112
"defaultSeverity": "Major",
1213
"ruleSpecification": "RSPEC-6832",
1314
"sqKey": "S6832",
1415
"scope": "All",
1516
"defaultQualityProfiles": ["Sonar way"],
16-
"quickfix": "unknown",
17+
"quickfix": "infeasible",
1718
"code": {
1819
"impacts": {
19-
"MAINTAINABILITY": "HIGH",
20-
"RELIABILITY": "MEDIUM",
21-
"SECURITY": "LOW"
20+
"RELIABILITY": "MEDIUM"
2221
},
23-
"attribute": "CONVENTIONAL"
22+
"attribute": "LOGICAL"
2423
}
2524
}

rules/S6832/java/rule.adoc

Lines changed: 141 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,165 @@
1-
FIXME: add a description
21

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.
513

614
== Why is this an issue?
715

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?
925

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.
1133

1234
== 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*.
1437

1538
=== Code examples
1639

1740
==== Noncompliant code example
1841

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]
2096
----
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+
}
22117
----
23118

24119
==== Compliant solution
25120

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]
27124
----
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+
}
29151
----
30152

31-
//=== How does this work?
32153

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]
34161

35-
//=== Going the extra mile
162+
=== Articles & blog posts
36163

164+
* Baeldung - https://www.baeldung.com/spring-bean-scopes[Spring Bean Scopes]
37165

38-
//== Resources
39-
//=== Documentation
40-
//=== Articles & blog posts
41-
//=== Conference presentations
42-
//=== Standards
43-
//=== External coding guidelines
44-
//=== Benchmarks

0 commit comments

Comments
 (0)