Одиночные EJB-компоненты не поддерживают кластер. Кластер — это группа контейнеров, которые тесно работают друг с другом (совместно используют одни и те же ресурсы, EJB-компоненты и т. д.). Таким образом, в ситуациях, когда несколько распределенных контейнеров будут образовывать кластер, функционируя на нескольких машинах, каждый контейнер будет располагать своим экземпляром одиночного EJB-компонента.
Чтобы превратить приведенный в листинге 7.9 код одиночного Java-класса в код одиночного сессионного EJB-компонента (листинг 7.10), не потребуется много усилий. Фактически вам придется лишь снабдить класс аннотацией @Singleton, при этом не придется беспокоиться о закрытом конструкторе или статическом методе getInstance(). Контейнер убедится в том, что вы создали только один экземпляр. Аннотация @javax.ejb.Singleton обладает тем же самым API-интерфейсом, что и аннотация @Stateless, описанная ранее в листинге 7.7.
@Singleton
public class CacheEJB {
··private Map
··public void addToCache(Long id, Object object) {
····if (!cache.containsKey(id))
······cache.put(id, object);
··}
··public void removeFromCache(Long id) {
····if (cache.containsKey(id))
······cache.remove(id);
··}
··public Object getFromCache(Long id) {
····if (cache.containsKey(id))
······return cache.get(id);
····else
······return null;
··}
}
Как вы можете видеть, разработка сессионных EJB-компонентов без сохранения состояния, с сохранением состояния и одиночных очень легка: вам потребуется лишь одна аннотация. Вместе с тем об одиночных EJB-компонентах можно сказать еще немного. Можно инициализировать их при запуске, объединять в цепочку и настраивать их конкурентный доступ.
Инициализация при запуске
Когда клиентскому классу требуется доступ к методу в одиночном сессионном EJB-компоненте, контейнер обеспечивает либо создание его экземпляра, либо использование того, что уже имеется в этом контейнере. Однако инициализация одиночного EJB-компонента иногда может отнимать много времени. Представим, что CacheEJB (приведенному в листинге 7.10) необходим доступ к базе данных для загрузки в свой кэш тысяч объектов. Первый вызов EJB-компонента окажется затратным, и первому клиенту придется ожидать завершения инициализации.
Чтобы избежать такой задержки, вы можете дать указание контейнеру инициализировать одиночный EJB-компонент при запуске. Если снабдить класс EJB-компонента аннотацией @Startup, то контейнер инициализирует его во время запуска приложения, а не в тот момент, когда клиент вызовет его. В приведенном далее коде показано, как следует использовать эту аннотацию:
@Singleton
@Startup
public class CacheEJB {…}
В Java EE 7 экспертная группа попыталась изъять аннотацию @Startup из спецификации EJB, чтобы она могла быть использована с любым управляемым MBean-компонентом или сервлетом. Сделать это не получилось, однако теоретически это окажется возможным в Java EE 8.
Объединение одиночных EJB-компонентов в цепочку
В некоторых случаях, когда у вас есть несколько одиночных EJB-компонентов, может быть важно явно задать порядок инициализации. Представим, что CacheEJB необходимо сохранить данные, которые исходят от другого одиночного EJB-компонента (скажем, CountryCodeEJB, который возвращает ISO-коды стран). Тогда CountryCodeEJB потребуется инициализировать раньше CacheEJB. Между несколькими одиночными EJB-компонентами могут существовать зависимости, которые выражаются аннотацией @javax.ejb.DependsOn. Использование этой аннотации демонстрируется в приведенном далее примере:
@Singleton
public class CountryCodeEJB {…}
@DependsOn("CountryCodeEJB")
@Singleton