1. Обзор
В этом руководстве мы более подробно рассмотрим два типа синглетонов , доступных в Jakarta EE. Мы объясним и продемонстрируем различия и рассмотрим варианты использования, подходящие для каждого из них.
Во-первых, давайте посмотрим, что такое синглтоны, прежде чем углубляться в детали.
2. Одноэлементный шаблон проектирования
Напомним, что распространенный способ реализации шаблона Singleton — статический экземпляр и закрытый конструктор:
public final class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
Но, увы, это не совсем объектно-ориентировано. И у него есть некоторые проблемы с многопоточностью .
Однако контейнеры CDI и EJB дают нам объектно-ориентированную альтернативу.
3. Синглтон CDI
С CDI (Contexts and Dependency Injection) мы можем легко создавать синглтоны, используя аннотацию @Singleton
. Эта аннотация является частью пакета javax.inject
. Он инструктирует контейнер один раз создать экземпляр синглтона и передать его ссылку другим объектам во время внедрения.
Как мы видим, реализация синглтона с CDI очень проста:
@Singleton
public class CarServiceSingleton {
// ...
}
Наш класс имитирует автосервис. У нас много экземпляров разных Car
, но все они обслуживаются в одном и том же магазине. Таким образом, Singleton хорошо подходит.
Мы можем проверить, что это один и тот же экземпляр с помощью простого теста JUnit, который дважды запрашивает контекст для класса. Обратите внимание, что здесь у нас есть вспомогательный метод getBean
для удобочитаемости:
@Test
public void givenASingleton_whenGetBeanIsCalledTwice_thenTheSameInstanceIsReturned() {
CarServiceSingleton one = getBean(CarServiceSingleton.class);
CarServiceSingleton two = getBean(CarServiceSingleton.class);
assertTrue(one == two);
}
Из-за аннотации @Singleton
контейнер оба раза будет возвращать одну и ту же ссылку. Однако если мы попробуем это сделать с простым управляемым компонентом, контейнер каждый раз будет предоставлять другой экземпляр.
И хотя это работает одинаково как для javax.inject.Singleton
, так и для javax.ejb.Singleton,
между ними есть ключевое различие.
4. EJB-синглтон
Чтобы создать синглтон EJB, мы используем аннотацию @Singleton из пакета
javax.ejb
. Таким образом мы создаем Singleton Session Bean .
Мы можем протестировать эту реализацию так же, как мы тестировали реализацию CDI в предыдущем примере, и результат будет таким же. Одиночные элементы EJB, как и ожидалось, предоставляют единственный экземпляр класса.
Однако EJB-синглы также предоставляют дополнительную функциональность в виде управления параллелизмом, управляемого контейнером.
Когда мы используем этот тип реализации, контейнер EJB гарантирует, что каждый общедоступный метод класса будет доступен одному потоку за раз. Если несколько потоков пытаются получить доступ к одному и тому же методу, только один поток может использовать его, в то время как другие ждут своей очереди.
Мы можем проверить это поведение с помощью простого теста. Мы представим симуляцию очереди обслуживания для наших одноэлементных классов:
private static int serviceQueue;
public int service(Car car) {
serviceQueue++;
Thread.sleep(100);
car.setServiced(true);
serviceQueue--;
return serviceQueue;
}
serviceQueue
реализован как простое статическое целое число, которое увеличивается, когда автомобиль «въезжает» на сервис, и уменьшается, когда он «выходит». Если контейнер обеспечивает правильную блокировку, эта переменная должна быть равна нулю до и после службы и равна единице во время службы.
Мы можем проверить это поведение с помощью простого теста:
@Test
public void whenEjb_thenLockingIsProvided() {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int serviceQueue = carServiceEjbSingleton.service(new Car("Speedster xyz"));
assertEquals(0, serviceQueue);
}
}).start();
}
return;
}
Этот тест запускает 10 параллельных потоков. Каждый поток создает экземпляр автомобиля и пытается его обслужить. После службы он утверждает, что значение serviceQueue
возвращается к нулю.
Если мы, например, выполним аналогичный тест на синглтоне CDI, наш тест завершится ошибкой.
5. Вывод
В этой статье мы рассмотрели два типа одноэлементных реализаций, доступных в Jakarta EE. Мы увидели их преимущества и недостатки, а также продемонстрировали, как и когда использовать каждый из них.
И, как всегда, полный исходный код доступен на GitHub .