Spring ApplicationListener для ContextRefreshEvent. Как вызвать только один раз в иерархии?


Мне нужно выполнить определенную процедуру, как только мое веб-приложение spring запустит все свои бобы. Для этого я создал ApplicationListener<ContextRefreshedEvent>.

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

Есть ли способ достичь того, что я пытаюсь сделать?

Я использую пружину 3.1.0.ОСВОБОЖДАТЬ.

3 2

3 ответа:

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

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

К счастью, на помощь приходит спецификация сервлета с параметром load-on-startup

Элемент load-on-startup указывает, что этот сервлет должен быть загружен. (создается экземпляр и вызывается его init ()) при запуске веб-приложения. Необязательное содержание этих элемент должен быть целым числом, указывающим порядок в который сервлет должен быть загружен. Если значение является отрицательным целым числом, или элемент не является в настоящее время контейнер свободен для загрузки сервлета когда захочет. Если значение положительное целое число или 0, контейнер должен загрузиться и инициализируйте сервлет так, как это делает приложение. развертываемый. Контейнер должен гарантировать, что загружаются сервлеты, помеченные нижними целыми числами перед сервлетами, помеченными высшими целыми числами. То контейнер может выбрать порядок погрузки сервлеты с одинаковой нагрузкой при запуске значение.

Таким образом, вы должны сделать две вещи в основном:

  1. укажите элемент load-on-startup на каждом сервлете и убедитесь, что он имеет отличительный, более высокий номер
  2. убедитесь, что ваш слушатель уловил нужное событие

Пример

Рассмотрим следующее (упрощенное) web.xml определение

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>  
    <servlet>
        <servlet-name>anotherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/first/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>anotherServlet</servlet-name>
        <url-pattern>/second/*</url-pattern>
    </servlet-mapping>

</web-app>

Обнаружение правильного контекста

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

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
    ApplicationContext context = event.getApplicationContext();
    if (context instanceof ConfigurableWebApplicationContext) { // sanity check
        final ConfigurableWebApplicationContext ctx =
                (ConfigurableWebApplicationContext) event.getApplicationContext();
        if ("anotherServlet-servlet".equals(ctx.getNamespace())) {
            // Run your initialization business here
        }
    }
}
Если вам интересно понять, откуда это происходит, загляните в FrameworkServlet#initServletBean.

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

Упорядочивание

Наконец, вы также можете убедиться, что ваше событие обрабатывается последним, если для этого конкретного события зарегистрировано несколько слушателей. Для этого необходимо реализовать Ordered интерфейс:

public class YourListener implements ApplicationListener<ContextRefreshedEvent>, Ordered {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) { }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    } 
}

Вам нужно будет немного прояснить иерархию контекста, но если у вас есть типичная настройка корневого контекста и контекста сервлета, объявите Боб ApplicationListener в контексте сервлета.

Корневой контекст будет обновлен с помощью ContextLoaderListener. Контекст сервлета будет обновлен с помощью DispatcherServlet, использующего корневой контекст в качестве родительского. Когда это обновление будет сделано, ваш ApplicationListener получит событие.

Хм, В таких случаях я всегда использую javax.аннотация.Постконструктивная аннотация к публичному методу моего весеннего компонента.