Wykonaj metodę podczas uruchamiania wiosną

176

Czy jest jakaś funkcja Spring 3 do wykonywania niektórych metod przy pierwszym uruchomieniu aplikacji? Wiem, że mogę załatwić sztuczkę polegającą na ustawianiu metody z @Scheduledadnotacją i jest ona wykonywana zaraz po uruchomieniu, ale wtedy będzie wykonywana okresowo.

Javi
źródło
1
na czym polega sztuczka z @Scheduled? właśnie tego chcę!
chrismarx

Odpowiedzi:

185

Jeśli przez „uruchamianie aplikacji” masz na myśli „uruchamianie kontekstu aplikacji”, to tak, jest na to wiele sposobów , najłatwiejszym (w każdym razie w przypadku pojedynczych fasoli) jest dodanie adnotacji do metody @PostConstruct. Spójrz na link, aby zobaczyć inne opcje, ale podsumowując, są to:

  • Metody z adnotacją @PostConstruct
  • afterPropertiesSet()zgodnie z definicją w InitializingBeaninterfejsie wywołań zwrotnych
  • Niestandardowa skonfigurowana metoda init ()

Technicznie rzecz biorąc, są to haczyki do cyklu życia fasoli , a nie do cyklu życia kontekstu, ale w 99% przypadków oba są równoważne.

Jeśli chcesz podłączyć się konkretnie do uruchamiania / zamykania kontekstu, możesz zamiast tego zaimplementować Lifecycleinterfejs , ale prawdopodobnie jest to niepotrzebne.

skaffman
źródło
7
Po wielu badaniach nie widziałem jeszcze implementacji Lifecycle lub SmartLifecycle. Wiem, że to rok, ale skaffman, jeśli masz cokolwiek, możesz opublikować, co byłoby bardzo wdzięczne.
4
Powyższe metody są wywoływane przed utworzeniem całego kontekstu aplikacji (np. / Before / transakcja została ustawiona).
Hans Westerbeek
Otrzymuję dziwne ostrzeżenie, próbując użyć @PostConstruct w 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
Istnieją ważne przypadki, w których cykl życia fasoli i kontekstu jest bardzo różny. Jak zauważył @HansWesterbeek, fasola może zostać skonfigurowana, zanim kontekst, od którego zależy, będzie w pełni gotowy. W mojej sytuacji fasolka zależała od JMS - była w pełni skonstruowana, tak @PostConstructnazwano jej metodę, ale infrastruktura JMS, od której pośrednio zależała, nie była jeszcze w pełni okablowana (a będąc Springem wszystko po cichu zawiodło). Po przełączeniu na @EventListener(ApplicationReadyEvent.class)wszystko działało ( ApplicationReadyEventczy Spring Boot jest specyficzny dla wanilii Spring zobacz odpowiedź Stefana).
George Hawkins
@Skaffman: co jeśli moja fasola nie jest polecana przez żadną fasolę i chcę ją zainicjować, nie będąc nigdzie używana
Sagar Kharab
104

Można to łatwo zrobić za pomocą pliku ApplicationListener. Mam to do pracy, słuchając Springa 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 
  }
}

Odbiorniki aplikacji działają synchronicznie wiosną. Jeśli chcesz mieć pewność, że kod zostanie wykonany tylko raz, po prostu zachowaj jakiś stan w swoim komponencie.

AKTUALIZACJA

Począwszy od Spring 4.2+ możesz również użyć @EventListeneradnotacji, aby obserwować ContextRefreshedEvent(dzięki @bphilipnyc za wskazanie tego):

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 
  }
}
Stefan Haberl
źródło
1
To też zadziałało dla mnie - idealne do jednorazowej inicjalizacji bez fasoli.
Rory Hunter
9
Uwaga: tym, którzy mają ochotę użyć ContextStartedEventzamiast tego, trudniej jest dodać słuchacza przed uruchomieniem zdarzenia.
OrangeDog,
2
Jak wywołać repozytorium @Autowired JPA w wydarzenie? repozytorium jest puste.
e-info128
Nie działa dla mnie. Używam wiosennego mvc 3. Ta metoda onApplicationEvent (___) nie jest wywoływana podczas uruchamiania aplikacji. Jakaś pomoc. Oto mój kod @Component publiczna klasa AppStartListener implementuje ApplicationListener <ContextRefreshedEvent> {public void onApplicationEvent (końcowe zdarzenie ContextRefreshedEvent) {System.out.println ("\ n \ n \ nInside on application event"); }}
Vishwas Tyagi
@VishwasTyagi Jak zacząć swój kontener? Czy na pewno Twój AppStartListener jest częścią skanowania Twojego składnika?
Stefan Haberl
38

Wiosną 4.2+ możesz teraz po prostu:

@Component
class StartupHousekeeper {

    @EventListener(ContextRefreshedEvent.class)
    public void contextRefreshedEvent() {
        //do whatever
    }
}
vphilipnyc
źródło
Czy jest zagwarantowane, że ten odbiornik wywoła tylko raz po uruchomieniu?
gstackoverflow
Nie, zobacz moją odpowiedź powyżej. Zachowaj stan w swoim słuchaczu, aby sprawdzić, czy uruchamia się po raz pierwszy
Stefan Haberl
13

Jeśli używasz butów sprężynowych, to jest najlepsza odpowiedź.

Czuję, że @PostConstructi inne różne wtrącenia związane z cyklem życia są okrężne. Mogą one bezpośrednio prowadzić do problemów w czasie wykonywania lub powodować mniej niż oczywiste defekty z powodu nieoczekiwanych zdarzeń związanych z cyklem życia komponentu bean / kontekst. Dlaczego po prostu nie wywołać bezpośrednio swojego beana przy użyciu zwykłej Javy? Nadal wywołujesz fasolę w „wiosenny sposób” (np. Przez sprężynowe proxy AoP). A co najważniejsze, to zwykła java, nie może być prostsza. Nie ma potrzeby słuchania kontekstu ani nieparzystych harmonogramów.

@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();
    }
}
Zombie
źródło
5
Ogólnie jest to dobry pomysł, ale podczas uruchamiania kontekstu aplikacji wiosennej z testu integracji main nigdy nie jest uruchamiany!
Jonas Geiregat
@JonasGeiregat: Poza tym istnieją inne scenariusze, w których nie ma ich main()wcale, na przykład podczas korzystania z frameworka aplikacji (np. JavaServer Faces).
sleske
9

Dla użytkowników Javy 1.8, którzy otrzymują ostrzeżenie podczas próby odniesienia się do adnotacji @PostConstruct, zamiast tego skończyłem na piggybackingu z adnotacją @Scheduled, co możesz zrobić, jeśli masz już zadanie @Scheduled z fixedRate lub 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");
    }

}
obradować
źródło
7

To, co zrobiliśmy, to rozszerzenie org.springframework.web.context.ContextLoaderListenermożliwości drukowania czegoś, gdy kontekst się zaczyna.

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

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

Skonfiguruj podklasę, a następnie w web.xml:

<listener>
    <listener-class>
        com.mycomp.myapp.web.context.ContextLoaderListener
    </listener-class>
</listener>
Wim Deblauwe
źródło
7

Dzięki SpringBoot możemy wykonać metodę przy starcie za pomocą @EventListeneradnotacji

@Component
public class LoadDataOnStartUp
{   
    @EventListener(ApplicationReadyEvent.class)
    public void loadData()
    {
        // do something
    }
}
KAARTHIKEYAN
źródło
4

Uwaga, jest to zalecane tylko wtedy, gdy runOnceOnStartupmetoda zależy od w pełni zainicjowanego kontekstu sprężyny. Na przykład: chcesz zadzwonić do dao z rozgraniczeniem transakcji

Możesz także użyć metody zaplanowanej z bardzo wysokim ustawieniem fixedDelay

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

Ma to tę zaletę, że cała aplikacja jest okablowana (Transakcje, Dao, ...)

widoczne w sekcji Planowanie zadań do jednorazowego uruchomienia przy użyciu przestrzeni nazw zadań Spring

Joram
źródło
Nie widzę żadnej przewagi nad używaniem @PostConstruct?
Wim Deblauwe
@WimDeblauwe zależy od tego, co chcesz zrobić w dosomething () wywołanie Autowired dao z demarkacją Trasaction wymaga uruchomienia całego kontekstu, a nie tylko tej fasoli
Joram
5
Metoda @WimDeblauwe '@PostConstruct' uruchamia się, gdy fasola jest inicjowana, cały kontekst może nie być gotowy (np. Zarządzanie transakcjami)
Joram
Jest to bardziej eleganckie imo niż post konstrukt lub jakiekolwiek interfejsy lub zdarzenia
aliopi
1
AppStartListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof ApplicationReadyEvent){
            System.out.print("ciao");

        }
    }
}
dnocode
źródło
2
ApplicationReadyEvent jest na wiosnę, a nie na wiosnę 3
John Mercier
0

Jeśli chcesz skonfigurować komponent bean przed pełnym uruchomieniem aplikacji, możesz użyć @Autowired:

@Autowired
private void configureBean(MyBean: bean) {
    bean.setConfiguration(myConfiguration);
}
Cory Klein
źródło
0

Możesz użyć @EventListenerswojego komponentu, który zostanie wywołany po uruchomieniu serwera i zainicjowaniu wszystkich komponentów bean.

@EventListener
public void onApplicationEvent(ContextClosedEvent event) {

}
krmanish007
źródło
0

Dla pliku StartupHousekeeper.javaznajduje się w opakowaniu com.app.startup,

Zrób to w StartupHousekeeper.java:

@Component
public class StartupHousekeeper {

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

I zrób to w myDispatcher-servlet.java:

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

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

</beans>
Cameron Hudson
źródło