Führen Sie die Methode beim Start im Frühjahr aus

176

Gibt es eine Spring 3-Funktion, mit der einige Methoden ausgeführt werden können, wenn die Anwendung zum ersten Mal gestartet wird? Ich weiß, dass ich den Trick machen kann, eine Methode mit @ScheduledAnnotation festzulegen, und sie wird direkt nach dem Start ausgeführt, aber dann wird sie regelmäßig ausgeführt.

Javi
quelle
1
Was ist der Trick mit @Scheduled? genau das will ich!
Chrismarx

Antworten:

185

Wenn Sie mit "Anwendungsstart" "Anwendungskontextstart" meinen, dann gibt es viele Möglichkeiten, dies zu tun . Die einfachste (für Singletons Beans jedenfalls) besteht darin, Ihre Methode mit Anmerkungen zu versehen @PostConstruct. Schauen Sie sich den Link an, um die anderen Optionen zu sehen, aber zusammenfassend sind dies:

  • Mit annotierte Methoden @PostConstruct
  • afterPropertiesSet() wie durch die definiert InitializingBean Rückrufschnittstelle definiert
  • Eine benutzerdefinierte konfigurierte init () -Methode

Technisch gesehen sind dies Haken in die Bohne Lebenszyklus als in den Kontext-Lebenszyklus, aber in 99% der Fälle sind beide gleichwertig.

Wenn Sie sich speziell mit dem Starten / Herunterfahren des Kontexts befassen müssen, können Sie stattdessen die LifecycleSchnittstelle implementieren , dies ist jedoch wahrscheinlich nicht erforderlich .

Skaffman
quelle
7
Nach einigen Recherchen habe ich noch keine Implementierung von Lifecycle oder SmartLifecycle gesehen. Ich weiß, dass dies ein Jahr alt ist, aber Skaffman, wenn Sie etwas haben, das Sie posten können, wäre sehr dankbar.
4
Die oben genannten Methoden werden aufgerufen, bevor der gesamte Anwendungskontext erstellt wurde (z. B. / before / Transaktionsabgrenzung wurde eingerichtet).
Hans Westerbeek
Ich erhalte eine seltsame Warnung beim Versuch, @PostConstruct in Java 1.8 zu verwenden: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
Es gibt wichtige Fälle, in denen Bean- und Kontextlebenszyklus sehr unterschiedlich sind. Wie @HansWesterbeek feststellte, kann eine Bean eingerichtet werden, bevor der Kontext, von dem sie abhängt, vollständig bereit ist. In meiner Situation hing eine Bohne von JMS ab - sie war vollständig konstruiert, daher wurde ihre @PostConstructMethode aufgerufen, aber die JMS-Infrastruktur, von der sie indirekt abhing, war noch nicht vollständig verkabelt (und als Spring schlug alles nur stillschweigend fehl). Beim Umschalten auf @EventListener(ApplicationReadyEvent.class)alles , was funktioniert hat ( ApplicationReadyEventist Spring Boot spezifisch für Vanilla Spring, siehe Stefans Antwort).
George Hawkins
@ Skaffman: Was ist, wenn meine Bohne von keiner Bohne referenziert wird und ich die Bohne initialisieren möchte, ohne irgendwo verwendet zu werden
Sagar Kharab
104

Dies ist einfach mit einem ApplicationListener. Ich habe das zum Arbeiten gebracht, um Spring's zu hören 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 
  }
}

Anwendungslistener werden im Frühjahr synchron ausgeführt. Wenn Sie sicherstellen möchten, dass Ihr Code nur einmal ausgeführt wird, behalten Sie einfach einen bestimmten Status in Ihrer Komponente bei.

AKTUALISIEREN

Ab Spring 4.2+ können Sie auch die @EventListenerAnmerkung verwenden, um Folgendes zu beobachten ContextRefreshedEvent(danke an @bphilipnyc für den Hinweis):

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
quelle
1
Dies hat auch bei mir funktioniert - perfekt für die einmalige Initialisierung ohne Bean.
Rory Hunter
9
Hinweis: Für diejenigen, die versucht sind, ContextStartedEventstattdessen zu verwenden , ist es schwieriger, den Listener hinzuzufügen, bevor das Ereignis ausgelöst wird.
OrangeDog
2
Wie rufe ich eine @ Autowired JPA-Repositopry in ein Ereignis auf? Das Repository ist null.
E-Info128
Ich arbeite nicht für mich. Ich verwende spring mvc 3. Dieses mehod onApplicationEvent (___) wird beim Start der Anwendung nicht aufgerufen. Irgendeine Hilfe. Hier ist mein Code @Component public class AppStartListener implementiert ApplicationListener <ContextRefreshedEvent> {public void onApplicationEvent (letztes ContextRefreshedEvent-Ereignis) {System.out.println ("\ n \ n \ nInside on application event"); }}
Vishwas Tyagi
@VishwasTyagi Wie startest du deinen Container? Sind Sie sicher, dass Ihr AppStartListener Teil Ihres Komponentenscans ist?
Stefan Haberl
38

In Spring 4.2+ können Sie jetzt einfach Folgendes tun:

@Component
class StartupHousekeeper {

    @EventListener(ContextRefreshedEvent.class)
    public void contextRefreshedEvent() {
        //do whatever
    }
}
vphilipnyc
quelle
Ist garantiert, dass dieser Listener nach dem Start nur einmal aufgerufen wird?
gstackoverflow
Nein, siehe meine Antwort oben. Behalten Sie einen Status in Ihrem Listener, um zu überprüfen, ob er zum ersten Mal ausgeführt wird
Stefan Haberl
13

Wenn Sie Spring-Boot verwenden, ist dies die beste Antwort.

Ich denke, dass @PostConstructund andere verschiedene Lebenszyklusinterjektionen ein Umweg sind. Diese können direkt zu Laufzeitproblemen führen oder aufgrund unerwarteter Bean / Kontext-Lebenszyklusereignisse weniger als offensichtliche Fehler verursachen. Warum rufen Sie Ihre Bean nicht einfach direkt mit Java auf? Sie rufen die Bohne immer noch auf "Spring Way" auf (z. B. über den Spring AoP-Proxy). Und das Beste ist, es ist einfach Java, einfacher geht es nicht. Keine Notwendigkeit für Kontext-Listener oder ungerade Scheduler.

@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
quelle
5
Dies ist im Allgemeinen eine gute Idee, aber wenn Sie Ihren Spring-Anwendungskontext mit einem Integrationstest starten, wird main nie ausgeführt!
Jonas Geiregat
@ JonasGeiregat: Außerdem gibt es andere Szenarien, in denen es überhaupt keine gibt main(), beispielsweise bei Verwendung eines Anwendungsframeworks (z. B. JavaServer Faces).
Sleske
9

Für Java 1.8-Benutzer, die eine Warnung erhalten, wenn sie versuchen, auf die @ PostConstruct-Annotation zu verweisen, habe ich stattdessen die @ Scheduled-Annotation huckepack genommen, die Sie ausführen können, wenn Sie bereits einen @ Scheduled-Job mit fixedRate oder fixedDelay haben.

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");
    }

}
verschlingen
quelle
7

Was wir getan haben, war zu erweitern org.springframework.web.context.ContextLoaderListener, um etwas zu drucken, wenn der Kontext beginnt.

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

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

Konfigurieren Sie die Unterklasse dann in web.xml:

<listener>
    <listener-class>
        com.mycomp.myapp.web.context.ContextLoaderListener
    </listener-class>
</listener>
Wim Deblauwe
quelle
7

Mit SpringBoot können wir beim Start eine Methode über ausführen @EventListener Annotation

@Component
public class LoadDataOnStartUp
{   
    @EventListener(ApplicationReadyEvent.class)
    public void loadData()
    {
        // do something
    }
}
KAARTHIKEYAN
quelle
4

Achtung, dies wird nur empfohlen, wenn Ihre runOnceOnStartupMethode von einem vollständig initialisierten Federkontext abhängt. Beispiel: Sie möchten ein Dao mit Transaktionsabgrenzung aufrufen

Sie können auch eine geplante Methode verwenden, bei der fixedDelay sehr hoch eingestellt ist

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

Dies hat den Vorteil, dass die gesamte Anwendung verkabelt ist (Transaktionen, Dao, ...)

Dies wird unter Planen der einmaligen Ausführung von Aufgaben mithilfe des Spring-Task-Namespace angezeigt

Joram
quelle
Ich sehe keinen Vorteil gegenüber der Verwendung @PostConstruct?
Wim Deblauwe
@WimDeblauwe hängt davon ab, was Sie in dosomething () tun möchten, wenn Sie ein Autowired-Dao mit Trasaction-Abgrenzung aufrufen, muss der gesamte Kontext gestartet werden, nicht nur diese Bean
Joram
5
@WimDeblauwe '@PostConstruct' Methode wird ausgelöst, wenn die Bean initialisiert wird, der gesamte Kontext ist möglicherweise nicht bereit (z. B. Transaktionsmanagement)
Joram
Dies ist eleganter imo als Post-Konstrukt oder irgendwelche Schnittstellen oder Ereignisse
Aliopi
1
AppStartListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof ApplicationReadyEvent){
            System.out.print("ciao");

        }
    }
}
dnocode
quelle
2
ApplicationReadyEvent ist im Frühjahr Boot nicht im Frühjahr 3
John Mercier
0

Wenn Sie eine Bean konfigurieren möchten, bevor Ihre Anwendung vollständig ausgeführt wird, können Sie Folgendes verwenden @Autowired:

@Autowired
private void configureBean(MyBean: bean) {
    bean.setConfiguration(myConfiguration);
}
Cory Klein
quelle
0

Sie können @EventListenerfür Ihre Komponente verwenden, die aufgerufen wird, nachdem der Server gestartet und alle Beans initialisiert wurden.

@EventListener
public void onApplicationEvent(ContextClosedEvent event) {

}
krmanish007
quelle
0

Für eine Datei StartupHousekeeper.javaim Paket com.app.startup:

Tun Sie dies in StartupHousekeeper.java:

@Component
public class StartupHousekeeper {

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

Und das in 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
quelle