Executar método na inicialização no Spring


176

Existe algum recurso do Spring 3 para executar alguns métodos quando o aplicativo é iniciado pela primeira vez? Eu sei que posso @Scheduledexecutar o truque de definir um método com anotação e ele é executado logo após a inicialização, mas será executado periodicamente.


1
qual é o truque do @Scheduled? é exatamente isso que eu quero!
Chrismarx #

Respostas:


185

Se por "inicialização do aplicativo" você quer dizer "inicialização do contexto do aplicativo", sim, existem muitas maneiras de fazer isso , sendo a mais fácil (para os feijões singletons, de qualquer maneira) anotar seu método @PostConstruct. Dê uma olhada no link para ver as outras opções, mas em resumo elas são:

  • Métodos anotados com @PostConstruct
  • afterPropertiesSet()conforme definido pela InitializingBeaninterface de retorno de chamada
  • Um método init () configurado personalizado

Tecnicamente, esses são ganchos no ciclo de vida do bean , em vez do ciclo de vida do contexto, mas em 99% dos casos, os dois são equivalentes.

Se você precisar se conectar especificamente ao contexto de inicialização / desligamento, poderá implementar a Lifecycleinterface , mas isso provavelmente é desnecessário.


7
Ainda estou para ver uma implementação do Lifecycle ou SmartLifecycle após bastante pesquisa. Eu sei que este é um ano de idade, mas skaffman, se você tiver alguma coisa que possa postar, seria muito apreciada.

4
Os métodos acima são chamados antes que todo o contexto do aplicativo seja criado (por exemplo, / antes / a demarcação da transação foi configurada).
Hans Westerbeek

Recebo um aviso estranho ao tentar usar o @PostConstruct no 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
Existem casos importantes em que o ciclo de vida do bean e do contexto é muito diferente. Como o @HansWesterbeek observou, um bean pode ser configurado antes que o contexto em que depende esteja totalmente pronto. Na minha situação, um bean dependia do JMS - ele foi totalmente construído, então seu @PostConstructmétodo foi chamado, mas a infra-estrutura do JMS de que ele dependia indiretamente ainda não estava totalmente conectada (e sendo Spring, tudo falhou silenciosamente). Ao mudar para @EventListener(ApplicationReadyEvent.class)tudo que funcionou (o ApplicationReadyEventSpring Boot é específico para o vanilla Spring, consulte a resposta de Stefan).
George Hawkins

@Skaffman: E se o meu feijão não é conhecido por qualquer feijão e quero inicializar o feijão sem ser usado em qualquer lugar
Sagar Kharab

104

Isso é feito facilmente com um ApplicationListener. Eu fiz isso funcionar ouvindo 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 
  }
}

Ouvintes de aplicativos são executados de forma síncrona no Spring. Se você deseja garantir que seu código seja executado apenas uma vez, mantenha algum estado em seu componente.

ATUALIZAR

A partir do Spring 4.2+, você também pode usar a @EventListeneranotação para observar o ContextRefreshedEvent(obrigado a @bphilipnyc por apontar isso):

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
Isso funcionou para mim também - perfeito para inicialização única que não é do bean.
Rory Hunter

9
Nota: para aqueles que tentaram usar ContextStartedEvent, é mais difícil adicionar o ouvinte antes que o evento seja disparado.
precisa saber é

2
Como chamar um repositório JPA @Autowired no evento? o repositório é nulo.
precisa saber é o seguinte

Não está trabalhando para mim. Eu estou usando o spring mvc 3. Esse método no AppAplicativo (___) não é chamado quando o aplicativo é iniciado. Qualquer ajuda. Aqui está o meu código @Component public class AppStartListener implementa ApplicationListener <ContextRefreshedEvent> {public void onApplicationEvent (evento final ContextRefreshedEvent) {System.out.println ("\ n \ n \ nInside on event event"); }}
Vishwas Tyagi

@VishwasTyagi Como você inicia seu contêiner? Tem certeza de que seu AppStartListener faz parte da sua verificação de componentes?
Stefan Haberl

38

No Spring 4.2+, agora você pode simplesmente fazer:

@Component
class StartupHousekeeper {

    @EventListener(ContextRefreshedEvent.class)
    public void contextRefreshedEvent() {
        //do whatever
    }
}

É garantido que este ouvinte chame apenas uma vez após a inicialização?
Gstackoverflow 17/08/2018

Não, veja minha resposta acima. Mantenha algum estado em seu ouvinte para verificar se ele está sendo executado pela primeira vez
Stefan Haberl

13

Se você estiver usando o boot de mola, esta é a melhor resposta.

Eu sinto que @PostConstructe outras interjeições de vários ciclos de vida são alternativas. Isso pode levar diretamente a problemas de tempo de execução ou causar defeitos menos que óbvios devido a eventos inesperados do ciclo de vida do bean / contexto. Por que não invocar diretamente seu bean usando Java simples? Você ainda invoca o bean da 'maneira da primavera' (por exemplo: através do proxy AoP da primavera). E o melhor de tudo, é Java simples, não pode ser mais simples que isso. Não há necessidade de ouvintes de contexto ou agendadores ímpares.

@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();
    }
}

5
Essa é uma boa ideia em geral, mas ao iniciar o contexto do aplicativo Spring a partir de um teste de integração, o main nunca é executado!
Jonas Geiregat

@JonasGeiregat: Além disso, existem outros cenários em que não há main(), por exemplo, ao usar uma estrutura de aplicativo (por exemplo, JavaServer Faces).
sleske

9

Para usuários do Java 1.8 que recebem um aviso ao tentar referenciar a anotação @PostConstruct, acabei pegando a anotação @Scheduled, que você pode fazer se já tiver um trabalho @Scheduled com 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");
    }

}


7

O que fizemos foi estender org.springframework.web.context.ContextLoaderListenerpara imprimir algo quando o contexto começar.

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

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

Configure a subclasse e, em web.xml:

<listener>
    <listener-class>
        com.mycomp.myapp.web.context.ContextLoaderListener
    </listener-class>
</listener>

7

Com o SpringBoot, podemos executar um método na inicialização via @EventListeneranotação

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

4

Atenção, isso só é aconselhável se o seu runOnceOnStartupmétodo depender de um contexto de primavera totalmente inicializado. Por exemplo: você deseja chamar um dao com demarcação de transação

Você também pode usar um método agendado com FixedDelay definido muito alto

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

Isso tem a vantagem de que todo o aplicativo está conectado (Transações, Dao, ...)

visto em Planejando tarefas para serem executadas uma vez, usando o namespace da tarefa Spring


Não vejo nenhuma vantagem em usar @PostConstruct?
Wim Deblauwe

@WimDeblauwe depende do que você quer fazer na dosomething () chamar um dao Autowired com Trasaction demarcação precisa de todo o contexto para ser iniciado, não apenas este feijão
Jorão

5
O método @WimDeblauwe '@PostConstruct' é acionado quando o bean é inicializado, todo o contexto pode não estar pronto (por gerenciamento de transações)
Joram

Isso é mais elegante do que a pós-construção ou qualquer interface ou evento
aliopi


1
AppStartListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof ApplicationReadyEvent){
            System.out.print("ciao");

        }
    }
}

2
O ApplicationReadyEvent está na bota de primavera e não na primavera 3
John Mercier

0

Se você deseja configurar um bean antes que seu aplicativo esteja sendo executado completamente, você pode usar @Autowired:

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

0

Você pode usar @EventListenerem seu componente, que será chamado após o servidor ser iniciado e todos os beans inicializados.

@EventListener
public void onApplicationEvent(ContextClosedEvent event) {

}

0

Para um arquivo StartupHousekeeper.javalocalizado no pacotecom.app.startup ,

Faça isso em StartupHousekeeper.java:

@Component
public class StartupHousekeeper {

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

E faça isso em myDispatcher-servlet.java:

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

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

</beans>
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.