DEV Community

Apurv
Apurv

Posted on • Originally published at apurvsheth.Medium on

Spring Framework - Handling Prototype Scoped Beans

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** {
}
Enter fullscreen mode Exit fullscreen mode

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());
}
Enter fullscreen mode Exit fullscreen mode

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());
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);

}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)