Выполнить метод при запуске в Spring

176

Есть ли какая-нибудь особенность Spring 3 для выполнения некоторых методов при первом запуске приложения? Я знаю, что могу сделать метод установки метода с @Scheduledаннотацией, и он выполняется сразу после запуска, но затем он будет выполняться периодически.

Javi
источник
1
что за хитрость с @Scheduled? это именно то, что я хочу!
Крисмаркс

Ответы:

185

Если под «запуском приложения» вы подразумеваете «запуск контекста приложения», то да, есть много способов сделать это , самый простой (в любом случае, для bean-компонентов синглтонов) состоит в том, чтобы аннотировать ваш метод @PostConstruct. Взгляните на ссылку, чтобы увидеть другие варианты, но в итоге они:

  • Методы с комментариями @PostConstruct
  • afterPropertiesSet()как определено InitializingBeanинтерфейсом обратного вызова
  • Настраиваемый метод init ()

Технически, это крюки в жизненном цикле бина , а не в жизненном цикле контекста, но в 99% случаев они эквивалентны.

Если вам нужно специально подключиться к контексту при запуске / завершении работы, вы можете вместо этого реализовать Lifecycleинтерфейс , но это, вероятно, не нужно.

skaffman
источник
7
Мне еще предстоит увидеть реализацию Lifecycle или SmartLifecycle после довольно долгих исследований. Я знаю, что это год, но Скаффман, если у вас есть что-то, что вы можете опубликовать, было бы очень признательно.
4
Вышеуказанные методы вызываются до того, как будет создан весь контекст приложения (например, / перед / демаркацией / транзакцией была установлена)
Ханс Вестербик,
Я получаю странное предупреждение, пытаясь использовать @PostConstruct в Java 1.8:Access restriction: The type PostConstruct is not accessible due to restriction on required library /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/jre/lib/rt.jar
encrest
2
Существуют важные случаи, когда жизненный цикл бина и контекста сильно различаются. Как отметил @HansWesterbeek, бин можно настроить до того, как контекст, от которого он зависит, будет полностью готов. В моей ситуации бин зависел от JMS - он был полностью сконструирован, поэтому @PostConstructбыл вызван его метод, но инфраструктура JMS, от которой он косвенно зависел, еще не была полностью подключена (а в Spring все просто молчаливо провалилось). При переключении на @EventListener(ApplicationReadyEvent.class)все работало ( ApplicationReadyEventспецифичен ли Spring Boot для ванильной Spring, см. Ответ Стефана).
Джордж Хокинс
@ Скаффман: что если мой бин не передается никаким бином, и я хочу инициализировать бин, не используя его нигде
Сагар Хараб
104

Это легко сделать с помощью ApplicationListener. Я получил это на работу, слушая Spring ContextRefreshedEvent:

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

@Component
public class StartupHousekeeper implements ApplicationListener<ContextRefreshedEvent> {

  @Override
  public void onApplicationEvent(final ContextRefreshedEvent event) {
    // do whatever you need here 
  }
}

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

ОБНОВИТЬ

Начиная с Spring 4.2+, вы также можете использовать @EventListenerаннотацию для наблюдения ContextRefreshedEvent(спасибо @bphilipnyc за это):

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

@Component
public class StartupHousekeeper {

  @EventListener(ContextRefreshedEvent.class)
  public void contextRefreshedEvent() {
    // do whatever you need here 
  }
}
Стефан Хаберл
источник
1
Это сработало и для меня - идеально подходит для однократной инициализации без бина.
Рори Хантер
9
Обратите внимание, что тем, кто испытывает желание использовать ContextStartedEventвместо этого, сложнее добавить слушателя до того, как событие сработает.
OrangeDog
2
Как вызвать репозитарий @Autowired JPA в событие? хранилище пустое.
e-info128
Не работает для меня Я использую Spring MVC 3. Этот метод onApplicationEvent (___) не вызывается при запуске приложения. Любая помощь. Вот мой код @Component открытый класс AppStartListener реализует ApplicationListener <ContextRefreshedEvent> {public void onApplicationEvent (final ContextRefreshedEvent event) {System.out.println ("\ n \ n \ nInside on application application"); }}
Вишвас Тяги
@VishwasTyagi Как начать свой контейнер? Вы уверены, что ваш AppStartListener является частью сканирования вашего компонента?
Стефан
38

В Spring 4.2+ вы можете теперь просто сделать:

@Component
class StartupHousekeeper {

    @EventListener(ContextRefreshedEvent.class)
    public void contextRefreshedEvent() {
        //do whatever
    }
}
vphilipnyc
источник
Гарантируется ли, что этот слушатель вызывает только один раз после запуска?
gstackoverflow
Нет, см. Мой ответ выше. Сохраняйте состояние в своем слушателе, чтобы проверить, выполняется ли оно впервые
Stefan Haberl
13

Если вы используете spring-boot, это лучший ответ.

Я чувствую, что @PostConstructи другие разнообразные междометия в жизненном цикле обходные пути. Это может привести непосредственно к проблемам во время выполнения или вызвать менее очевидные дефекты из-за непредвиденных событий жизненного цикла bean / context. Почему бы просто не напрямую вызывать ваш компонент с помощью простой Java? Вы по-прежнему вызываете бин «пружинным путем» (например, через прокси AoP пружины). И что самое приятное, это просто Java, проще не бывает. Нет необходимости в прослушивателях контекста или нечетных планировщиках.

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext app = SpringApplication.run(DemoApplication.class, args);

        MyBean myBean = (MyBean)app.getBean("myBean");

        myBean.invokeMyEntryPoint();
    }
}
Zombies
источник
5
В целом это хорошая идея, но при запуске весеннего контекста приложения из интеграционного теста main никогда не запускается!
Джонас Гейрегат
@JonasGeiregat: Плюс, есть другие сценарии, в которых их вообще нет main(), например, при использовании инфраструктуры приложения (например, JavaServer Faces).
слеске
9

Для пользователей Java 1.8, которые получают предупреждение при попытке сослаться на аннотацию @PostConstruct, я в итоге вместо этого воспользовался аннотацией @Scheduled, которую вы можете сделать, если у вас уже есть задание @Scheduled с fixedRate или fixedDelay.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@EnableScheduling
@Component
public class ScheduledTasks {

private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTasks.class);

private static boolean needToRunStartupMethod = true;

    @Scheduled(fixedRate = 3600000)
    public void keepAlive() {
        //log "alive" every hour for sanity checks
        LOGGER.debug("alive");
        if (needToRunStartupMethod) {
            runOnceOnlyOnStartup();
            needToRunStartupMethod = false;
        }
    }

    public void runOnceOnlyOnStartup() {
        LOGGER.debug("running startup job");
    }

}
encrest
источник
см. также stackoverflow.com/questions/3564361/…
Джорам
7

Что мы сделали, так это распространили org.springframework.web.context.ContextLoaderListenerчто-то, когда начинается контекст.

public class ContextLoaderListener extends org.springframework.web.context.ContextLoaderListener
{
    private static final Logger logger = LoggerFactory.getLogger( ContextLoaderListener.class );

    public ContextLoaderListener()
    {
        logger.info( "Starting application..." );
    }
}

Настройте подкласс затем в web.xml:

<listener>
    <listener-class>
        com.mycomp.myapp.web.context.ContextLoaderListener
    </listener-class>
</listener>
Вим Деблавве
источник
7

С SpringBoot мы можем выполнить метод при запуске через @EventListenerаннотацию

@Component
public class LoadDataOnStartUp
{   
    @EventListener(ApplicationReadyEvent.class)
    public void loadData()
    {
        // do something
    }
}
KAARTHIKEYAN
источник
4

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

Вы также можете использовать запланированный метод с фиксированным установленным значением очень высокого

@Scheduled(fixedDelay = Long.MAX_VALUE)
public void runOnceOnStartup() {
    dosomething();
}

Преимущество заключается в том, что все приложение подключено (транзакции, дао, ...)

видно в разделе Планирование задач для однократного запуска с использованием пространства имен задач Spring

Йорам
источник
Я не вижу никаких преимуществ перед использованием @PostConstruct?
Вим Deblauwe
@WimDeblauwe зависит от того, что вы хотите сделать в dosomething (), для вызова автоматического связанного дао с демаркацией Trasaction требуется запуск всего контекста, а не только этого компонента
Joram
5
@WimDeblauwe '@PostConstruct' метод запускается, когда компонент инициализируется, весь контекст может быть не готов (например, управление транзакциями)
Joram
Это более элегантно, чем пост-конструкция или какие-либо интерфейсы или события
aliopi
1

Опубликовал другое решение, которое реализует WebApplicationInitializer и вызывается задолго до того, как будет создан экземпляр любого bean-компонента, в случае, если у кого-то есть такой вариант использования

Инициализация локали и часового пояса по умолчанию с конфигурацией Spring

kisna
источник
1
AppStartListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof ApplicationReadyEvent){
            System.out.print("ciao");

        }
    }
}
dnocode
источник
2
ApplicationReadyEvent загружается весной, а не весной
Джон Мерсье,
0

Если вы хотите настроить bean-компонент до того, как ваше приложение будет полностью запущено, вы можете использовать @Autowired:

@Autowired
private void configureBean(MyBean: bean) {
    bean.setConfiguration(myConfiguration);
}
Кори Кляйн
источник
0

Вы можете использовать @EventListenerв своем компоненте, который будет вызываться после запуска сервера и инициализации всех bean-компонентов.

@EventListener
public void onApplicationEvent(ContextClosedEvent event) {

}
krmanish007
источник
0

Для файла, StartupHousekeeper.javaрасположенного в пакете com.app.startup,

Сделайте это в StartupHousekeeper.java:

@Component
public class StartupHousekeeper {

  @EventListener(ContextRefreshedEvent.class)
  public void keepHouse() {
    System.out.println("This prints at startup.");
  }
}

И сделать это в myDispatcher-servlet.java:

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <mvc:annotation-driven />
    <context:component-scan base-package="com.app.startup" />

</beans>
Кэмерон Хадсон
источник