Когда я впервые начал изучать Spring, у меня возникли два «трудных» вопроса:
- Как создается синглтон?
- И как этот единственный компонент автоматически подключается в разных классах?
Вам также может понравиться: Spring Bean Lifecycle
Представьте себе следующий сценарий:
Есть два пользователя. Один из них хочет войти в систему, а другой хочет одновременно создать отчет в нашем приложении. И оба, login
и createReport
методы используют userService
bean-компонент, который определен как синглтон. В этом случае используют ли эти методы этот одноэлементный компонент последовательно? Иначе, как одноэлементный бин обслуживает несколько запросов одновременно?
Отвечать на эти вопросы было не так сложно, как я изначально думал. Нужно было только уточнить простые, но важные моменты. Вот почему я попытаюсь описать их с помощью основных примеров кода. Давайте начнем:
1. Было бы полезно, если мы сначала поговорим о контейнерах Spring. Потому что я думаю, что это поможет вам лучше описать процесс в вашем уме.
Контейнер Spring создает в нем bean-компоненты. После создания необходимых компонентов он вводит зависимости от них. Контейнер получает свои инструкции, читая метаданные конфигурации (аннотации XML или Java). Таким образом, после инициализации контейнера Spring ваше приложение готово к использованию, как описано на следующем рисунке:
Когда вы определяете определение компонента, как показано ниже, вы сообщаете контейнеру, что он должен создать только один экземпляр для этого определения компонента в контейнере:
<bean id=”accountDao” class=”…” scope=”singleton”/>
Этот единственный экземпляр хранится в кэше таких синглтон-бинов. Затем контейнер Spring возвращает этот кэшированный объект всем запросам и ссылкам на bean-компоненты с этим определением bean-компонента:
Если мы хотим показать приведенный выше пример с new()
оператором для описания упрощенного представления о том, что контейнер Spring делает при запуске приложения, мы можем написать следующий код:
Джава
xxxxxxxxxx
1
UserService userService = new UserService ();
2
UserController userController = новый UserController ();
4
userController . userService = userService ;
5
ReportController reportController = new ReportController ();
7
reportController . userService = userService
Я думаю, что нет необходимости в дополнительных объяснениях. В приведенном выше примере показано, как один и тот же компонент используется для разных классов.
2. Весной каждый запрос выполняется в отдельном потоке. Например, когда два пользователя хотят войти в систему одновременно, JVM создает два потока: один поток для первого пользователя и другой для второго пользователя.
Чтобы показать это, нам может помочь следующий простой фрагмент кода. Если вы одновременно отправили два запроса на метод входа в систему и напечатали имя текущего потока (который выполняется запрошенным методом входа в систему), вы увидите там два разных имени потока. Это показывает нам, что каждый запрос выполняется в отдельном потоке:
Джава
xxxxxxxxxx
1
2
открытый класс UserController {
3
5
приватный UserService userService ;
6
value = "/ login / {username}" ) (
8
public User login ( ( value = "username" ) Строковое имя пользователя ) {
9
Система . из . println ( Thread . currentThread (). getName () + "-----------" + username + "-----------" + new Date ());
10
вернуть userService . логин ( логин );
11
}
12
}
Джава
xxxxxxxxxx
1
Результат :
2
http - nio - 8080 - exec - 9 ----------- Бен ---------- Сб 23 ноября 20 : 01 : 35
3
http - nio - 8080 - exec - 6 ----------- Кейт ---------- Сб 23 ноября 20 : 01 : 35
4
Вы можете отправить одновременно запросы с помощью от завитка команды , как это :
6
curl http : // localhost: 8080 / login / Ben & curl http: // localhost: 8080 / login / Kate
И эти потоки работают с одиночным компонентом отдельно. Как? Давайте немного поговорим о распределении памяти в Java.
В Java каждый объект создается в куче. Куча имеет глобально разделяемую память. Вот почему каждый поток может получить доступ к объектам в куче.
Но стек используется для выполнения только одного потока. В этом потоке, когда вызывается метод, в стеке создается новый блок в порядке LIFO (Last-In-First-Out). Этот блок содержит локальные значения примитивов и ссылки на другие объекты в методе. И стековая память не может быть доступна другим потокам.
Итак, когда мы создаем синглтон-бин, он находится в куче. Поскольку куча доступна из любого места приложения, каждый созданный поток может указывать на этот одноэлементный компонент. И как это происходит? Когда поток запрашивает одноэлементный компонент, он собирается ссылаться (с помощью ссылочной переменной в стеке) на байт-код одноэлементного компонента в куче. Таким образом, несколько потоков могут ссылаться на одноэлементный компонент одновременно. Компилятор собирается указать на один и тот же байт-код и просто выполнить его и сохранить значения, специфичные для метода, в соответствующих блоках в стеке отдельно. Нет ограничений, мешающих компилятору делать это. Единственное ограничение, которое одноэлементный класс накладывает на JVM, заключается в том, что он может иметь только один экземпляр этого класса в куче. И именно поэтому идеальный синглтон-бин должен быть без гражданства. В противном случае,могут возникнуть проблемы с параллелизмом.
Я надеюсь, что это поможет вам понять процесс ясно. Если у вас есть какие-либо вопросы, пожалуйста, дайте мне знать в комментариях.
Удачного кодирования!