Spring Framework — Работа с прототипными скопированными бобами
Введение
Всякий раз, когда мы создаем боб, используя @Service, @Component или любые другие подобные аннотации, мы создаем рецепт для создания экземпляра класса. Вместе с этим рецептом мы также можем определить область видимости соответствующего экземпляра. Это может быть любой из них: синглтон, прототип, сессия, запрос или глобальная сессия.
Большинство из нас уже знакомы с этими областями применения бобов. Но сегодня мы хотим понять, создает ли область видимости прототипа несколько экземпляров или рассматривается как синглтон.
Итак, давайте начнем с краткого обзора singleton & prototype scoped beans. Затем мы рассмотрим проблему, которая может привести к тому, что прототип будет singleton & ее возможные решения.
GitHub Repo
https://github.com/shethapurv/spring-boot/tree/main/lookup-scope-service
Основы
Давайте определим два различных класса служб, названных EmployeeService и Department Service. EmployeeService будет синглтоном, а DepartmentService — прототипом.
@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** {
}
Теперь давайте напишем тестовый пример и сравним хэш-код каждого сервисного боба, определенного выше.
Несколько экземпляров одноцелевого скопированного боба «EmployeeService» будут иметь одинаковый хэш-код.
@Test
public void singletonTest() {
EmployeeService employeeService1 = applicationContext.getBean("employeeService", EmployeeService.class);
EmployeeService employeeService2 = applicationContext.getBean("employeeService", EmployeeService.class);
**Assertions._assertEquals_** (employeeService1.hashCode(), employeeService2.hashCode());
}
Несколько экземпляров прототипа scoped bean «DepartmentService» будут иметь разный хэш-код.
@Test
public void prototypeTest() {
DepartmentService departmentService1 = applicationContext.getBean("departmentService", DepartmentService.class);
DepartmentService departmentService2 = applicationContext.getBean("departmentService", DepartmentService.class);
**Assertions._assertNotEquals_(**departmentService1.hashCode(), departmentService2.hashCode());
}
Да, пока все хорошо. Результаты тестирования вполне соответствуют ожиданиям. не так ли!
Проблема
Итак, вот в чем проблема. Всякий раз, когда мы вводим прототип скопированного боба внутри синглтонного скопированного боба, прототип скопированного боба начинает вести себя как синглтонный боб.
Давайте попробуем понять это на простом примере.
Если вы заметили выше, в бине DepartmentService объявлена переменная класса «departmentId», которая будет возвращать случайное целое число в качестве идентификатора отдела. Теперь, когда бы мы ни попытались получить departmentId, он должен вернуть два разных id отдела, поскольку DepartmentService — это прототипный скопированный боб, но в действительности он вернет только один 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);
}
Поскольку нам ясна проблема, описанная выше, давайте попробуем рассмотреть различные подходы, чтобы избавиться от этой проблемы.
Решение
Обращаться с прототипом scoped bean так, как он должен быть на самом деле. У нас есть 3 различных подхода вместо автоматического подключения prototype scoped bean.
1. Использование ApplicationContext
Мы можем использовать контекст приложения для получения объекта prototype scoped bean. Но здесь мы нарушаем принцип инверсии контроля. Мы создаем объект вместо того, чтобы контейнер spring создавал объект.
@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. Использование ObjectFactory
Мы можем использовать инстанцирование фабрики объектов, чтобы получить боб с прототипом, но проблема этого подхода в том, что экземпляры будут инициализироваться с нетерпением.
@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. Использование аннотации @LookUp
Мы можем использовать аннотацию @LookUp, предоставляемую spring. Это поможет решить проблему инверсии контроля, с которой мы столкнулись в варианте 1, поскольку контейнер spring сам позаботится о создании экземпляра. Кроме того, он не будет инициализироваться с нетерпением, как это было в варианте 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;
}
Заключение
Мы рассмотрели проблемы, которые могут возникнуть с прототипным скопированным бобом, если его неправильно обработать. Мы также рассмотрели различные подходы к решению этих проблем.
Рекомендуется использовать аннотацию @LookUp для работы с прототипным скопированным бином, что не нарушает принцип Spring IoC, а также бобы не инициализируются с нетерпением.
Ссылки
- 4.4 Области охвата бина
- Lookup (Spring Framework 5.3.22 API)
Если этот пост был полезен, пожалуйста, похлопайте несколько раз или проследите за ним, чтобы показать свою поддержку, которая поддерживает мою постоянную мотивацию делиться своими знаниями.
Учиться, делиться и расти вместе.