Spring Framework — Обработка прототипных скопированных бобов


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)

Если этот пост был полезен, пожалуйста, похлопайте несколько раз или проследите за ним, чтобы показать свою поддержку, которая поддерживает мою постоянную мотивацию делиться своими знаниями.

Учиться, делиться и расти вместе.

Оцените статью
devanswers.ru
Добавить комментарий