Exécuter la méthode au démarrage au printemps

176

Existe-t-il une fonctionnalité Spring 3 pour exécuter certaines méthodes lorsque l'application démarre pour la première fois? Je sais que je peux faire l'astuce de définir une méthode avec @Scheduledannotation et qu'elle s'exécute juste après le démarrage, mais ensuite elle s'exécutera périodiquement.

Javi
la source
1
quel est le truc avec @Scheduled? c'est exactement ce que je veux!
chrismarx

Réponses:

185

Si par "démarrage de l'application" vous entendez "démarrage du contexte de l'application", alors oui, il existe de nombreuses façons de le faire , la plus simple (pour les beans singletons, en tout cas) étant d'annoter votre méthode avec @PostConstruct. Jetez un œil au lien pour voir les autres options, mais en résumé, ce sont:

  • Méthodes annotées avec @PostConstruct
  • afterPropertiesSet()comme défini par l' InitializingBeaninterface de rappel
  • Une méthode init () personnalisée

Techniquement, ce sont des crochets dans le cycle de vie du bean , plutôt que dans le cycle de vie du contexte, mais dans 99% des cas, les deux sont équivalents.

Si vous avez besoin de vous connecter spécifiquement au démarrage / arrêt du contexte, vous pouvez implémenter l' Lifecycleinterface à la place, mais ce n'est probablement pas nécessaire.

skaffman
la source
7
Je n'ai pas encore vu d'implémentation de Lifecycle ou SmartLifecycle après pas mal de recherches. Je sais que c'est un an, mais skaffman si vous avez quelque chose que vous pouvez poster, ce serait très apprécié.
4
Les méthodes ci-dessus sont appelées avant que tout le contexte d'application n'ait été créé (par exemple, / avant / la démarcation de transaction a été configurée).
Hans Westerbeek
Je reçois un étrange avertissement en essayant d'utiliser @PostConstruct en 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 le
2
Il existe des cas importants où le cycle de vie du bean et du contexte est très différent. Comme @HansWesterbeek l'a noté, un bean peut être configuré avant que le contexte dont il dépend ne soit complètement prêt. Dans ma situation, un bean dépendait de JMS - il était entièrement construit, donc sa @PostConstructméthode a été appelée, mais l'infrastructure JMS dont il dépendait indirectement n'était pas encore complètement câblée (et étant Spring, tout a échoué en silence). Lors du passage à @EventListener(ApplicationReadyEvent.class)tout fonctionnait ( ApplicationReadyEventSpring Boot est-il spécifique à vanilla Spring, voir la réponse de Stefan).
George Hawkins
@Skaffman: que se passe-t-il si mon haricot n'est référencé par aucun haricot et que je veux initialiser le haricot sans être utilisé nulle part
Sagar Kharab
104

Cela se fait facilement avec un fichier ApplicationListener. J'ai fait travailler ça en écoutant 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 
  }
}

Les écouteurs d'applications s'exécutent de manière synchrone dans Spring. Si vous voulez vous assurer que votre code n'est exécuté qu'une seule fois, gardez simplement un état dans votre composant.

METTRE À JOUR

À partir de Spring 4.2+, vous pouvez également utiliser l' @EventListenerannotation pour observer le ContextRefreshedEvent(merci à @bphilipnyc pour l'avoir signalé):

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
la source
1
Cela a fonctionné pour moi aussi - parfait pour une initialisation non-bean ponctuelle.
Rory Hunter
9
NB pour ceux qui sont tentés d'utiliser à la ContextStartedEventplace, il est plus difficile d'ajouter l'auditeur avant le déclenchement de l'événement.
OrangeDog
2
Comment appeler un référentiel JPA @Autowired en événement? le référentiel est nul.
e-info128
Ne travaille pas pour moi. J'utilise spring mvc 3. Cette méthode onApplicationEvent (___) n'est pas appelée lorsque l'application démarre. De l'aide. Voici mon code @Component public class AppStartListener implémente ApplicationListener <ContextRefreshedEvent> {public void onApplicationEvent (événement ContextRefreshedEvent final) {System.out.println ("\ n \ n \ nInside on application event"); }}
Vishwas Tyagi
@VishwasTyagi Comment démarrez-vous votre conteneur? Êtes-vous sûr que votre AppStartListener fait partie de votre analyse des composants?
Stefan Haberl
38

Dans Spring 4.2+, vous pouvez maintenant simplement faire:

@Component
class StartupHousekeeper {

    @EventListener(ContextRefreshedEvent.class)
    public void contextRefreshedEvent() {
        //do whatever
    }
}
vphilipnyc
la source
Est-il garanti que cet écouteur n'appelle qu'une seule fois après le démarrage?
gstackoverflow
Non, voyez ma réponse ci-dessus. Gardez un état dans votre auditeur pour vérifier s'il s'exécute pour la première fois
Stefan Haberl
13

Si vous utilisez des bottes à ressort, c'est la meilleure réponse.

Je pense que cela @PostConstructet d'autres interjections du cycle de vie sont des voies rondes. Celles-ci peuvent conduire directement à des problèmes d'exécution ou provoquer des défauts moins qu'évidents dus à des événements de cycle de vie bean / contexte inattendus. Pourquoi ne pas simplement appeler directement votre bean en utilisant Java brut? Vous invoquez toujours le bean de la «manière du ressort» (par exemple: via le proxy Spring AoP). Et le meilleur de tous, c'est du java simple, ne peut pas être plus simple que cela. Pas besoin d'écouteurs de contexte ou de programmateurs étranges.

@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();
    }
}
Des morts-vivants
la source
5
C'est une bonne idée en général, mais lors du démarrage de votre contexte d'application Spring à partir d'un test d'intégration, main n'est jamais exécuté!
Jonas Geiregat
@JonasGeiregat: De plus, il existe d'autres scénarios où il n'y en a pas main()du tout, par exemple lors de l'utilisation d'un framework d'application (par exemple JavaServer Faces).
sleske
9

Pour les utilisateurs de Java 1.8 qui reçoivent un avertissement lorsqu'ils essaient de référencer l'annotation @PostConstruct, j'ai fini par utiliser l'annotation @Scheduled, ce que vous pouvez faire si vous avez déjà un travail @Scheduled avec fixedRate ou 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");
    }

}
incruster
la source
7

Ce que nous avons fait a été d'étendre org.springframework.web.context.ContextLoaderListenerpour imprimer quelque chose lorsque le contexte démarre.

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

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

Configurez ensuite la sous-classe dans web.xml:

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

Avec SpringBoot, nous pouvons exécuter une méthode au démarrage via une @EventListenerannotation

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

KAARTHIKEYAN
la source
4

Attention, cela n'est conseillé que si votre runOnceOnStartupméthode dépend d'un contexte de ressort entièrement initialisé. Par exemple: vous souhaitez appeler un DAO avec une démarcation de transaction

Vous pouvez également utiliser une méthode planifiée avec un paramètre fixedDelay très élevé

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

Cela présente l'avantage que toute l'application est câblée (Transactions, Dao, ...)

vu dans Planification des tâches à exécuter une fois, à l'aide de l'espace de noms de tâches Spring

Joram
la source
Je ne vois aucun avantage à utiliser @PostConstruct?
Wim Deblauwe
@WimDeblauwe dépend de ce que vous voulez faire dans dosomething () pour appeler un dao Autowired avec une démarcation Trasaction nécessite que tout le contexte soit démarré, pas seulement ce bean
Joram
5
@WimDeblauwe La méthode '@PostConstruct' se déclenche lorsque le bean est initialisé, tout le contexte peut ne pas être prêt (par exemple, la gestion des transactions)
Joram
C'est plus élégant imo que post-construction ou toute interface ou événement
aliopi
1
AppStartListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof ApplicationReadyEvent){
            System.out.print("ciao");

        }
    }
}
dnocode
la source
2
ApplicationReadyEvent est au printemps pas au printemps 3
John Mercier
0

Si vous souhaitez configurer un bean avant que votre application ne s'exécute complètement, vous pouvez utiliser @Autowired:

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

Vous pouvez utiliser @EventListenersur votre composant, qui sera appelé après le démarrage du serveur et l'initialisation de tous les beans.

@EventListener
public void onApplicationEvent(ContextClosedEvent event) {

}
krmanish007
la source
0

Pour un fichier StartupHousekeeper.javasitué dans le package com.app.startup,

Faites ceci dans StartupHousekeeper.java:

@Component
public class StartupHousekeeper {

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

Et faites-le en 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
la source