Spring Framework - Handling Prototype Scoped Beans
Introduction
Whenever we are creating a bean using @Service, @Component, or any other similar annotations, we are creating a recipe for creating an instance of a class. Along with this recipe, we can also define the scope of that respective instance. It can be any of these, singleton, prototype, session, request, or global session.
Most of us are already familiar with these bean scopes. But today, we are going to understand whether the prototype scope is creating multiple instances or is being treated as a singleton.
So, let’s start with a summary of singleton & prototype scoped beans. After which, we will go through the issue which may cause the prototype to be singleton & its possible solutions.
GitHub Repo
https://github.com/shethapurv/spring-boot/tree/main/lookup-scope-service
Basics
Let’s define two different service classes named EmployeeService & Department Service. EmployeeService would be singleton scoped & DepartmentService would be of prototype scoped.
@Service
**@Scope("prototype")** // prototype scoped
public class **DepartmentService** {
private Integer departmentId = new Random().nextInt(1000);
public int getDepartmentId(){
return departmentId;
}
}
**@Service** // by default, singleton scoped
public class **EmployeeService** {
}
Now, Let’s write a test case and compare the hashcode of each service bean defined above.
Multiple instances of singleton scoped bean “EmployeeService” will have the same hashcode.
@Test
public void singletonTest() {
EmployeeService employeeService1 = applicationContext.getBean("employeeService", EmployeeService.class);
EmployeeService employeeService2 = applicationContext.getBean("employeeService", EmployeeService.class);
**Assertions._assertEquals_** (employeeService1.hashCode(), employeeService2.hashCode());
}
Multiple instance of prototype scoped bean “DepartmentService” will have different hashcode.
@Test
public void prototypeTest() {
DepartmentService departmentService1 = applicationContext.getBean("departmentService", DepartmentService.class);
DepartmentService departmentService2 = applicationContext.getBean("departmentService", DepartmentService.class);
**Assertions._assertNotEquals_(**departmentService1.hashCode(), departmentService2.hashCode());
}
Yea, so far so good. Test results are very much as expected. isn’t it !
Problem
So, here is the problem. Whenever we will inject a prototype scoped bean within a singleton scoped bean, the prototype scoped bean will start behaving as a singleton bean.
Let’s try to understand with a simple example.
If you have observed above in DepartmentService bean, “departmentId” class variable is declared which will return a random integer as department id. Now, whenever, we will try to get departmetnId then it should return two different department Id since DepartmentService is a prototype scoped bean but in reality, it will return only the same departmentId.
@RestController
@RequestMapping(path = "/beanScope", method = RequestMethod._GET_)
public class **BeanScopeController** {
@Autowired
EmployeeService employeeService;
@RequestMapping(path = "/prototypeTreatedAsSingleton", method = RequestMethod._GET_)
public List<Integer> getDepartmentIdWithDeptTreatedAsSingleton() throws InterruptedException {
return employeeService.getDepartmentIdWithDeptTreatedAsSingleton();
}
}
// **EmployeeService**
// it will be treated as singleton even though its prototype scoped @Autowired
DepartmentService **departmentService** ;
public List<Integer> getDepartmentIdWithDeptTreatedAsSingleton() throws InterruptedException {
**int dep1 = departmentService.getDepartmentId();**
Thread._sleep_(1000L);
**int dep2 = departmentService.getDepartmentId();**
return List._of_(dep1, dep2);
}
As we are clear about the problem explained above, now, let’s try to see the different approaches to get away from this issue.
Solution
To treat prototype scoped bean as really it is supposed to be. We have 3 different approaches instead of auto-wiring prototype scoped bean.
1. Using ApplicationContext
We can use application context to get the prototype scoped bean object. But here, we are violating the principle of inversion of control. We are creating an object instead of a spring container creating an object.
@Autowired
ApplicationContext **applicationContext** ;
public List<Integer> getDepartmentIdWithApplicationContext() throws InterruptedException {
int dep1 = **applicationContext.getBean** (DepartmentService.class).getDepartmentId();
Thread._sleep_(1000L);
int dep2 = **applicationContext.getBean** (DepartmentService.class).getDepartmentId();
return List._of_(dep1, dep2);
}
2. Using ObjectFactory
We can use object factory instantiation to get a prototype scoped bean but the problem with this approach is, instances will be eagerly initialized.
@Autowired
private ObjectFactory<DepartmentService> **departmentServiceObjectFactory** ;
public List<Integer> getDepartmentIdWithObjectFactory() throws InterruptedException {
int dep1 = **departmentServiceObjectFactory.getObject** ().getDepartmentId();
Thread._sleep_(1000L);
int dep2 = **departmentServiceObjectFactory.getObject** ().getDepartmentId();
return List._of_(dep1, dep2);
}
3. Using @LookUp Annotation
We can use @LookUp annotation provided by spring. this will help to resolve the inversion of control issue which we faced in option 1 since the spring container itself will take care of instance creation. And, it will also not be eagerly initialized as we saw in option 2.
public List<Integer> getDepartmentIdWithLookUp() throws InterruptedException {
int dep1 = **getDepartmentService** ().getDepartmentId();
Thread._sleep_(1000L);
int dep2 = **getDepartmentService** ().getDepartmentId();
return List._of_(dep1, dep2);
}
@ **Lookup**
public DepartmentService **getDepartmentService** () {
return null;
}
Conclusion
We have gone through the problem which might occur with the prototype scoped bean if it is not handled correctly. We have also seen the different approaches to resolving those issues.
It is advisable to use @LookUp annotation to handle the prototype scoped bean which doesn’t violate the spring IoC principle and beans are also not eagerly initialized.
References
If this post was helpful, please clap for few times or follow to show your support which keeps me constantly motivated to share my learnings.
Learning, sharing & growing together.
Top comments (0)