Как говорилось во введении к главе, пакет org.springframework.beans.factory предоставляет базовую функциональность для управления и манипулирования бинами, в том числе программным способом. Пакет org.springframework.context добавляет интерфейс ApplicationContext, который расширяет интерфейс BeanFactory в дополнение к расширению других интерфейсов с целью обеспечения дополнительной функциональности в более ориентированном на структуру приложения стиле. Многие используют ApplicationContext полностью декларативно, даже не создавая его программно, а полагаясь на вспомогательные классы, такие как ContextLoader, для автоматического создания ApplicationContext в рамках обычного процесса запуска веб-приложения на Java EE.

Чтобы расширить функциональность BeanFactory в более ориентированном на структуру стиле, пакет контекста также предоставляет следующую функциональность:

  • Доступ к сообщениям в стиле интернационализации (i18n) через интерфейс MessageSource.

  • Доступ к ресурсам, таким как URL и файлы, через интерфейс ResourceLoader.

  • Публикация событий, а именно бинов, реализующих интерфейс ApplicationListener, с помощью интерфейса ApplicationEventPublisher.

  • Загрузка нескольких (иерархических) контекстов, что позволяет ориентировать каждый из них на один конкретный уровень, например, уровень взаимодействия с интернетом приложения, с помощью интерфейса HierarchicalBeanFactory.

Интернационализация с использованием MessageSource

Интерфейс ApplicationContext расширяет интерфейс MessageSource и, следовательно, обеспечивает функциональность интернационализации ("i18n"). Spring также предоставляет интерфейс HierarchicalMessageSource, который может разрешать сообщения иерархически. Вместе эти интерфейсы обеспечивают основу, на которой Spring осуществляет разрешение сообщений. Методы, определенные в этих интерфейсах, включают:

  • String getMessage(String code, Object[] args, String default, Locale loc): Основной метод, используемый для получения сообщения из MessageSource. Если сообщение для указанной локали (региональных настроек) не найдено, используется сообщение по умолчанию. Любые переданные аргументы становятся заменяющими значениями, использующими функциональность MessageFormat, предоставляемую стандартной библиотекой.

  • String getMessage(String code, Object[] args, Locale loc): По сути, это то же самое, что и предыдущий метод, но с одним отличием: Нельзя задать сообщение по умолчанию. Если сообщение не удается найти, генерирует исключение NoSuchMessageException.

  • String getMessage(MessageSourceResolvable resolvable, Locale locale): Все свойства, используемые в предыдущих методах, также обернуты в класс с именем MessageSourceResolvable, который можно использовать с этим методом.

Когда загружается ApplicationContext, он автоматически ищет бин MessageSource, определенный в контексте. Бины должны иметь имя messageSource. Если такой бин будет найден, все вызовы предыдущих методов делегируются источнику сообщений. Если источник сообщения не будет найден, интерфейс ApplicationContext попытается найти родительский класс, содержащий бин с таким же именем. Если такой будет найден, то он использует этот бин в качестве MessageSource. Если ApplicationContext не может найти какой-либо источник сообщений, создается экземпляр пустого DelegatingMessageSource, чтобы можно было принимать вызовы методов, определенных выше.

Spring предоставляет три реализации MessageSource: ResourceBundleMessageSource, ReloadableResourceBundleMessageSource и StaticMessageSource. Все они реализуют HierarchicalMessageSource для осуществления вложенного обмена сообщениями. StaticMessageSource используется редко, но предоставляет программные способы добавления сообщений в источник. В следующем примере показан ResourceBundleMessageSource:

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>

В примере предполагается, что в вашем classpath определены три пакета ресурсов под названиями format, exceptions и windows. Любой запрос на разрешение сообщения обрабатывается стандартным для JDK способом разрешения сообщений через объекты ResourceBundle. Для примера предположим, что содержимое двух из вышеупомянутых файлов пакета ресурсов выглядит следующим образом:

    # in format.properties
    message=Alligators rock!
    # in exceptions.properties
    argument.required=The {0} argument is required.

В следующем примере показана программа для запуска функциональности MessageSource. Помните, что все реализации ApplicationContext также являются реализациями MessageSource и поэтому могут быть приведены к интерфейсу MessageSource.

Java
public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    System.out.println(message);
}
Kotlin
fun main() {
    val resources = ClassPathXmlApplicationContext("beans.xml")
    val message = resources.getMessage("message", null, "Default", Locale.ENGLISH)
    println(message)
}

Результат работы вышеуказанной программы выглядит следующим образом:

Alligators rock!

Подводя итог, MessageSource определяется в файле под названием beans.xml, который находится в корне вашего пути классов. Определение бина messageSource ссылается на ряд ресурсных локализационных пакетов через свойство basenames. Три файла, которые передаются в списке свойству basenames, существуют как файлы в корне вашего пути классов и называются format.properties, exceptions.properties и windows.properties, соответственно.

В следующем примере показаны аргументы, передаваемые для поиска сообщений. Эти аргументы преобразуются в объекты String и вставляются в плейсхолдеры в поисковом сообщении.

<beans>
    <!-- этот MessageSource используется в веб-приложении  -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>
    <!-- давайте внедрим вышеупомянутый MessageSource в этот POJO -->
    <bean id="example" class="com.something.Example">
        <property name="messages" ref="messageSource"/>
    </bean>
</beans>
Java
public class Example {
    private MessageSource messages;
    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }
    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", Locale.ENGLISH);
        System.out.println(message);
    }
}
Kotlin
    class Example {
    lateinit var messages: MessageSource
    fun execute() {
        val message = messages.getMessage("argument.required",
                arrayOf("userDao"), "Required", Locale.ENGLISH)
        println(message)
    }
}

Результат вызова метода execute() выглядит следующим образом:

The userDao argument is required.

Что касается интернационализации ("i18n"), то различные реализации MessageSource в Spring следуют тем же правилам разрешения локали и возврата, что и стандартный JDK ResourceBundle. Короче говоря, продолжая рассматривать пример messageSource, определенный ранее, если нужно разрешать сообщения по британской английской (en-GB) локали, вам необходимо создать файлы format_en_GB.properties, exceptions_en_GB.properties и windows_en_GB.properties, соответственно.

Как правило, разрешение локали управляется окружением приложения. В следующем примере локаль, по которой разрешаются (британский английский) сообщения, указывается вручную:

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
Java
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}
Kotlin
fun main() {
    val resources = ClassPathXmlApplicationContext("beans.xml")
    val message = resources.getMessage("argument.required",
            arrayOf("userDao"), "Required", Locale.UK)
    println(message)
}

В результате выполнения вышеуказанной программы получаем следующие результаты:

Ebagum lad, the 'userDao' argument is required, I say, required.

Вы также можете использовать интерфейс MessageSourceAware для получения ссылки на любой определенный MessageSource. Любой бин, определенный в ApplicationContext, который реализует интерфейс MessageSourceAware, внедряется с помощью MessageSource контекста приложения, когда бин создается и конфигурируется.

Поскольку MessageSource в Spring основан на ResourceBundle из Java, он не объединяет пакеты с одинаковым основным именем, а использует только первый найденный пакет. Последующие пакеты сообщений с тем же основным именем игнорируются.
В качестве альтернативы ResourceBundleMessageSource, Spring предоставляет класс ReloadableResourceBundleMessageSource. Этот вариант поддерживает тот же формат файла пакета, но является более гибким, чем стандартная реализация ResourceBundleMessageSource, основанная на JDK. В частности, он позволяет считывать файлы из любого места расположения ресурсов Spring (не только из пути классов) и поддерживает горячую перезагрузку файлов свойств пакета (при этом эффективно кэшируя их в промежутках). Подробности см. в javadoc по ReloadableResourceBundleMessageSource.

Стандартные и кастомные события

Обработка событий в ApplicationContext осуществляется с помощью класса ApplicationEvent и интерфейса ApplicationListener. Если в контексте развернут бин, реализующий интерфейс ApplicationListener, то каждый раз, когда в ApplicationContext публикуется событие ApplicationEvent, этот бин получает уведомление. По сути, это стандартный шаблон проектирования Observer.

Начиная с версии Spring 4.2, инфраструктура событий была значительно улучшена и предлагает модель, основанную на аннотациях, а также возможность публикации любого произвольного события (то есть объекта, который не обязательно наследован от ApplicationEvent). Если такой объект публикуется, то затем он оборачивается в событие.

В следующей таблице описаны стандартные события, которые предоставляет Spring:

Таблица 7. Встроенные события
Событие Пояснение

ContextRefreshedEvent

Публикуется при инициализации или обновлении ApplicationContext (например, с помощью метода refresh() интерфейса ConfigurableApplicationContext). Здесь "инициализированный" означает, что все бины загружены, бины постпроцессора обнаружены и активированы, объекты-одиночки предварительно инициализированы, а объект ApplicationContext готов к использованию. До тех пор, пока контекст не будет закрыт, обновление можно запустить несколько раз, при условии, что выбранный ApplicationContext действительно поддерживает такие "горячие" обновления. Например, XmlWebApplicationContext поддерживает горячее обновление, а GenericApplicationContext - нет.

ContextStartedEvent

Публикуется при запуске ApplicationContext с помощью метода start() интерфейса ConfigurableApplicationContext. Здесь "запущен" означает, что все бины Lifecycle получают явный сигнал запуска. Обычно этот сигнал используется для перезапуска бинов после явной остановки, но он также может использоваться для запуска компонентов, которые не были настроены на автозапуск (например, компоненты, которые еще не запускались при инициализации).

ContextStoppedEvent

Публикуется, если ApplicationContext остановлен с помощью метода stop() интерфейса ConfigurableApplicationContext. Здесь "остановлен" означает, что все бины Lifecycle получают явный сигнал остановки. Остановленный контекст может быть перезапущен с помощью вызова start().

ContextClosedEvent

Публикуется, если ApplicationContext закрыт с помощью метода close() интерфейса ConfigurableApplicationContext или через перехватчик завершения из JVM. Здесь "закрыт" означает, что все бины-одиночки будут уничтожены. После закрытия контекста его жизненный цикл заканчивается, и он не может быть обновлен или перезапущен.

RequestHandledEvent

Событие, специфичное для веб-среды, сообщающее всем бинам о том, что HTTP-запрос был обработан. Это событие публикуется после выполнения запроса. Это событие применимо только к веб-приложениям, использующим DispatcherServlet в Spring.

ServletRequestHandledEvent

Подкласс RequestHandledEvent, который добавляет контекстную информацию, специфичную для сервлетов.

Вы также можете создавать и публиковать собственные специальные события. В следующем примере показан простой класс, который расширяет базовый класс ApplicationEvent в Spring:

Java
public class BlockedListEvent extends ApplicationEvent {
    private final String address;
    private final String content;
    public BlockedListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }
    // метод получения значения (акцессор (accessor)) и другие методы...
}
Kotlin
class BlockedListEvent(source: Any,
                    val address: String,
                    val content: String) : ApplicationEvent(source)

Чтобы опубликовать специальное событие ApplicationEvent, вызовите метод publishEvent() для ApplicationEventPublisher. Обычно это делается путем создания класса, реализующего ApplicationEventPublisherAware, и регистрации его в качестве бина Spring. В следующем примере показан такой класс:

Java
public class EmailService implements ApplicationEventPublisherAware {
    private List<String> blockedList;
    private ApplicationEventPublisher publisher;
    public void setBlockedList(List<String> blockedList) {
        this.blockedList = blockedList;
    }
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }
    public void sendEmail(String address, String content) {
        if (blockedList.contains(address)) {
            publisher.publishEvent(new BlockedListEvent(this, address, content));
            return;
        }
        // отправляем электронное письмо...
    }
}
Kotlin
class EmailService : ApplicationEventPublisherAware {
    private lateinit var blockedList: List<String>
    private lateinit var publisher: ApplicationEventPublisher
    fun setBlockedList(blockedList: List<String>) {
        this.blockedList = blockedList
    }
    override fun setApplicationEventPublisher(publisher: ApplicationEventPublisher) {
        this.publisher = publisher
    }
    fun sendEmail(address: String, content: String) {
        if (blockedList!!.contains(address)) {
            publisher!!.publishEvent(BlockedListEvent(this, address, content))
            return
        }
        // send email...
    }
}

Во время конфигурирования контейнер Spring определяет, что EmailService реализует ApplicationEventPublisherAware, и автоматически вызывает setApplicationEventPublisher(). На самом деле передаваемый параметр – это сам контейнер Spring. Вы взаимодействуете с контекстом приложения через его интерфейс ApplicationEventPublisher.

Для получения специального ApplicationEvent можно создать класс, реализующий ApplicationListener, и зарегистрировать его как бин Spring. В следующем примере показан такой класс:

Java
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {
    private String notificationAddress;
    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }
    public void onApplicationEvent(BlockedListEvent event) {
        // уведомляем соответствующие стороны через notificationAddress...
    }
}
Kotlin
class BlockedListNotifier : ApplicationListener<BlockedListEvent> {
    lateinit var notificationAddress: String
    override fun onApplicationEvent(event: BlockedListEvent) {
        // уведомляем соответствующие стороны через notificationAddress...
    }
}

Обратите внимание, что ApplicationListener параметризован общим типом вашего специального события(BlockedListEvent в предыдущем примере). Это означает, что метод onApplicationEvent() может оставаться типобезопасным, что позволяет избежать необходимости выполнения приведения вниз к соответствующему типу. Вы можете зарегистрировать столько слушателей событий, сколько пожелаете, но учтите, что по умолчанию слушатели событий получают события синхронно. Это означает, что метод publishEvent() будет блокироваться до тех пор, пока все слушатели не закончат обработку события. Одно из преимуществ этого синхронного и однопоточного подхода заключается в том, что когда слушатель получает событие, он работает внутри контекста транзакции публикатора, если контекст транзакции доступен. Если возникает необходимость в другой стратегии публикации событий, см. javadoc по интерфейсу ApplicationEventMulticaster и реализации SimpleApplicationEventMulticaster в Spring для вариантов конфигурации.

В следующем примере показаны определения бинов, используемые для регистрации и настройки каждого из вышеперечисленных классов:

<bean id="emailService" class="example.EmailService">
    <property name="blockedList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>
<bean id="blockedListNotifier" class="example.BlockedListNotifier">
    <property name="notificationAddress" value="blockedlist@example.org"/>
</bean>

Сводя все воедино, при вызове метода sendEmail() бина emailService, если существуют какие-либо сообщения электронной почты, которые должны быть заблокированы, публикуется специальное событие типа BlockedListEvent. Бин blockedListNotifier регистрируется как ApplicationListener и получает событие BlockedListEvent, после чего может уведомить соответствующие стороны.

Механизм событий Spring предназначен для простого взаимодействия между бинами Spring в одном и том же контексте приложения. Однако для более сложных потребностей корпоративной интеграции существует отдельный проект Spring Integration, которые предоставляет полную поддержку инструментальных средств для построения упрощенных, ориентированных на образное представление, управляемых по событиям архитектур, основанных на известной модели программирования Spring.

Слушатели (получатели) событий на основе аннотаций

Вы можете зарегистрировать слушателя событий для любого метода управляемого бина, используя аннотацию @EventListener. BlockedListNotifier можно переписать следующим образом:

Java
public class BlockedListNotifier {
    private String notificationAddress;
    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }
    @EventListener
    public void processBlockedListEvent(BlockedListEvent event) {
        // уведомляем соответствующие стороны через notificationAddress...
    }
}
Kotlin
class BlockedListNotifier {
    lateinit var notificationAddress: String
    @EventListener
    fun processBlockedListEvent(event: BlockedListEvent) {
        // уведомляем соответствующие стороны через notificationAddress...
    }
}

В сигнатуре метода снова объявляется тип события, который он "прослушивает", но на этот раз с гибким именем и без реализации конкретного интерфейса слушателя. Тип события также модно сужать с помощью дженериков до тех пор, пока реальный тип события будет позволять использовать ваш типизированный параметр в своей иерархии реализации.

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

Java
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}
Kotlin
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
    // ...
}

Также можно добавить дополнительную фильтрацию во время выполнения, используя атрибут condition в аннотации, определяющей выражение SpEL, которое должно совпадать с фактическим вызовом метода для определенного события.

В следующем примере показано, каким образом можно переписать наше средство уведомления, чтобы оно вызывалось только в том случае, если атрибут content события равен my-event:

Java
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
    // уведомляем соответствующие стороны через notificationAddress...
}
Kotlin
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlockedListEvent(blEvent: BlockedListEvent) {
    // уведомляем соответствующие стороны через notificationAddress...
}

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

Таблица 8. Доступные метаданные события SpEL
Имя Местонахождение Описание Пример
Событие корневой объект Фактическое событие ApplicationEvent. #root.event или event
Массив аргументов корневой объект Аргументы (в виде массива объектов), используемые для вызова метода. #root.args или args; args[0] для доступа к первому аргументу и т.д.
Имя аргумента контекст вычислений Имя любого из аргументов метода. Если по какой-то причине имена недоступны (например, из-за отсутствия отладочной информации в скомпилированном байт-коде), отдельные аргументы можно также использовать с помощью синтаксиса #a<#arg>, где <#arg> обозначает индекс аргумента (начиная с 0). #blEvent или #a0 (в качестве псевдонима можно также использовать обозначения параметров #p0 или #p<#arg>)

Обратите внимание, что #root.event дает доступ к базовому событию, даже если сигнатура вашего метода на самом деле ссылается на произвольный объект, который был опубликован.

Если вам нужно опубликовать событие в результате обработки другого события, можно изменить сигнатуру метода, чтобы вернуть событие, которое необходимо опубликовать, как показано в следующем примере:

Java
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
    // уведомляем соответствующие стороны через notificationAddress и
    // затем публикуем ListUpdateEvent...
}
Kotlin
@EventListener
fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent {
    // уведомляем соответствующие стороны через notificationAddress и
    // затем публикуем ListUpdateEvent...
}
Данная функция не поддерживается для асинхронных слушателей.

Метод handleBlockedListEvent() публикует новое событие ListUpdateEvent для каждого события BlockedListEvent, которое он обрабатывает. Если вам нужно опубликовать несколько событий, вы можете вернуть Collection или массив событий.

Асинхронные слушатели

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

Java
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent обрабатывается в отдельном потоке
}
Kotlin
@EventListener
@Async
fun processBlockedListEvent(event: BlockedListEvent) {
    // BlockedListEvent обрабатывается в отдельном потоке
}

Помните о следующих ограничениях при использовании асинхронных событий:

  • Если асинхронный слушатель событий генерирует Exception, оно не передается вызывающему коду. Более подробную информацию см. AsyncUncaughtExceptionHandler.

  • Методы асинхронных слушателей событий не могут публиковать последующее событие, возвращая значение. Если вам нужно опубликовать другое событие в результате обработки, введите ApplicationEventPublisher, чтобы опубликовать событие вручную.

Упорядочение слушателей

Если вам нужно, чтобы один слушатель была вызван раньше другого, то можно добавить аннотацию @Order к объявлению метода, как показано в следующем примере:

Java
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // уведомляем соответствующие стороны через notificationAddress...
}
Kotlin
@EventListener
@Order(42)
fun processBlockedListEvent(event: BlockedListEvent) {
    // уведомляем соответствующие стороны через notificationAddress...
}

Типизированные события

Также можно использовать джеерики для дальнейшего определения структуры вашего события. Рассмотрим возможность использования EntityCreatedEvent<T>, где T - тип фактической сущности, которая была создана. Например, можно создать следующее определение слушателя, чтобы получать только EntityCreatedEvent для Person:

Java
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}
Kotlin
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
    // ...
}

Из-за стирания типов это работает только в том случае, если запускаемое на выполнение событие разрешает общие параметры, по которым слушатель событий осуществляет фильтрацию (то есть что-то наподобие class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ }).

При определенных обстоятельствах это может стать довольно хлопотным, если все события будут иметь одинаковую структуру (как это должно быть в случае с событием в предыдущем примере). В таком случае можно реализовать ResolvableTypeProvider, чтобы направить фреймворк за пределы того, что предоставляет среда выполнения. Следующее событие демонстрирует, как это сделать:

Java
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
    public EntityCreatedEvent(T entity) {
        super(entity);
    }
    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
    }
}
Kotlin
class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider {
    override fun getResolvableType(): ResolvableType? {
        return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource()))
    }
}
Это работает не только для ApplicationEvent, но и для любого произвольного объекта, который вы посылаете в качестве события.

Удобный доступ к низкоуровневым ресурсам

Для оптимального использования и понимания контекстов приложений, следует ознакомиться с абстракцией Resource в Spring.

Контекст приложения представляет собой ResourceLoader, который можно использовать для загрузки объектов Resource. Resource – это, по сути, более функциональная версия класса java.net.URL из JDK. На деле реализации Resource обертывают экземпляр класса java.net.URL, когда это необходимо. Resource может получать низкоуровневые ресурсы практически из любого места понятным образом, включая из пути классов, местоположения файловой системы, любого места, описываемого стандартным URL, и некоторых других вариантов. Если строка расположения ресурсов представляет собой простой путь без каких-либо специальных префиксов, то место, откуда берутся эти ресурсы, является конкретным и соответствует реальному типу контекста приложения.

Вы можете настроить бин, развернутый в контексте приложения, на реализацию специального интерфейса обратного вызова, ResourceLoaderAware, для автоматического вызова во время инициализации с передачей самого контекста приложения в качестве ResourceLoader. Вы также можете открывать свойства типа Resource, чтобы использовать их для доступа к статическим ресурсам. Они внедряются в него, как и любые другие свойства. Вы можете задать эти свойства Resource в виде простых String путей и полагаться на автоматическое преобразование этих текстовых строк в реальные объекты Resource при развертывании бина.

Путь или пути расположения, передаваемые конструктору интерфейса ApplicationContext, на самом деле являются строками ресурсов и в простой форме обрабатываются соответствующим образом в зависимости от конкретной реализации контекста. Например, ClassPathXmlApplicationContext считает простой путь расположения расположение в пути классов. Вы также можете использовать пути расположения (строки ресурсов) со специальными префиксами для принудительной загрузки определений из пути классов или URL, независимо от фактического типа контекста.

Отслеживание запуска приложений

Интерфейс ApplicationContext управляет жизненным циклом приложений в Spring и обеспечивает полнофункциональную модель программирования для компонентов. В результате сложные приложения могут иметь столь же сложные графы компонентов и фазы запуска.

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

AbstractApplicationContext (и его подклассы) оснащен ApplicationStartup, который собирает данные StartupStep о различных фазах запуска:

  • жизненный цикл контекста приложения (сканирование базовых пакетов, управление конфигурационными классами)

  • жизненный цикл бинов (создание экземпляра, интеллектуальная инициализация, последующая обработка)

  • обработка событий приложения

Вот пример инструментирования в AnnotationConfigApplicationContext:

Java
// создаем этап запуска и начинаем запись
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
// добавляем информацию о метках к текущему этапу
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// выполняем фактическую фазу, которую инструментируем
this.scanner.scan(basePackages);
// завершаем текущий этап
scanPackages.end();
Kotlin
// создаем этап запуска и начинаем запись
val scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan")
// добавляем информацию о метках к текущему этапу
scanPackages.tag("packages", () -> Arrays.toString(basePackages))
// выполняем фактическую фазу, которую инструментируем
this.scanner.scan(basePackages)
// завершаем текущий этап
scanPackages.end()

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

Реализация ApplicationStartup по умолчанию – это фиктивный (no-op) вариант, позволяющий свести к минимуму задержки из-за обработки. Это означает, что по умолчанию при запуске приложения метрики собираться не будут. Spring Framework поставляется с реализацией для отслеживания этапов запуска с помощью Java Flight Recorder: FlightRecorderApplicationStartup. Чтобы использовать этот вариант, необходимо настроить его экземпляр на ApplicationContext сразу после его создания.

Разработчики также могут использовать инфраструктуру ApplicationStartup, если они указывают свой собственный подкласс AbstractApplicationContext, или если они хотят собрать более точные данные.

ApplicationStartup предназначен только для использования во время запуска приложения и для основного контейнера; это ни в коем случае не замена Java-профилировщиков или библиотек метрик, таких как Micrometer.

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

Разработчикам не стоит использовать пространство имен "spring.*" при создании специальных этапов запуска. Это пространство имен зарезервировано для внутреннего использования Spring и может быть изменено.

Удобное создание экземпляра ApplicationContext для веб-приложений

Вы можете создавать экземпляры ApplicationContext декларативно, используя, например, ContextLoader. Конечно, также можно создавать экземпляры ApplicationContext программно, используя одну из реализаций ApplicationContext.

Вы можете зарегистрировать ApplicationContext с помощью ContextLoaderListener, как показано в следующем примере:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Слушатель проверяет параметр contextConfigLocation. Если параметр не существует, слушатель использует /WEB-INF/applicationContext.xml по умолчанию. Если параметр существует, слушатель разделяет String с помощью предопределенных разделителей (запятая, точка с запятой и пробел) и использует значения в качестве мест поиска контекстов приложений. Также поддерживаются шаблоны путей в стиле Ant. Примерами являются /WEB-INF/*Context.xml (для всех файлов, имена которых заканчиваются на Context.xml и которые находятся в каталоге WEB-INF) и /WEB-INF/**/*Context.xml (для всех таких файлов в любом подкаталоге WEB-INF).

Развертывание Spring ApplicationContext как RAR-файла в Java EE

Можно развернуть ApplicationContext из Spring как RAR-файл, заключив контекст и все его необходимые классы бинов и библиотеки JAR в единицу развертывания Java EE RAR. Это эквивалентно загрузке отдельного ApplicationContext (только размещенного в среде Java EE), имеющего доступ к средствам серверов Java EE. Развертывание RAR является более естественной альтернативой сценарию развертывания консольного WAR-файла - по сути, WAR-файла без каких-либо точек входа HTTP, который используется только для загрузки ApplicationContext из Spring в среде Java EE.

Развертывание RAR идеально подходит для контекстов приложений, которые не нуждаются в точках входа HTTP, а состоят только из конечных точек сообщений и запланированных заданий. Бины в таком контексте могут задействовать ресурсы сервера приложений, такие как диспетчер транзакций JTA и связанные с JNDI экземпляры DataSource из JDBC и экземпляры ConnectionFactory из JMS, а также могут регистрироваться на JMX-сервере платформы - все это с помощью стандартного управления транзакциями из Spring и средств поддержки из JNDI и JMX. Компоненты приложения также могут взаимодействовать с WorkManager сервера приложений из JCA через абстракцию TaskExecutor из Spring.

Подробные сведения о конфигурации, связанные с развертыванием RAR, см. в javadoc по классу SpringContextResourceAdapter.

For a simple deployment of a Spring ApplicationContext as a Java EE RAR file:

  1. Упакуйте все классы приложения в файл RAR (который представляет собой стандартный файл JAR с другим расширением файла).

  2. Добавьте все необходимые библиотеки JAR в корень архива RAR.

  3. Добавьте дескриптор развертывания META-INF/ra.xml (как показано в javadoc для SpringContextResourceAdapter) и соответствующий файл(ы) определения бинов Spring XML (обычно META-INF/applicationContext.xml).

  4. Поместите полученный RAR-файл в каталог развертывания вашего сервера приложений.

Такие единицы развертывания RAR обычно являются автономными. Они не открывают компоненты внешнему миру, даже другим модулям того же приложения. Взаимодействие с ApplicationContext на основе RAR обычно происходит через JMS-приемники, которые он использует совместно с другими модулями. ApplicationContext на основе RAR может также, например, планировать некоторые задания или учитывать новые файлы в файловой системе (или тому подобное). Если ему необходимо дать синхронный доступ извне, он может (например) экспортировать конечные точки RMI, которые могут использоваться другими прикладными модулями на той же машине.