Ejecutar método al inicio en primavera

176

¿Existe alguna característica de Spring 3 para ejecutar algunos métodos cuando la aplicación se inicia por primera vez? Sé que puedo hacer el truco de configurar un método con @Scheduledanotaciones y se ejecuta justo después del inicio, pero luego se ejecutará periódicamente.

Javi
fuente
1
¿Cuál es el truco con @Scheduled? eso es exactamente lo que quiero!
chrismarx

Respuestas:

185

Si por "inicio de la aplicación" te refieres a "inicio del contexto de la aplicación", entonces sí, hay muchas maneras de hacerlo , la más fácil (para los beans singletons, de todos modos) es anotar tu método @PostConstruct. Eche un vistazo al enlace para ver las otras opciones, pero en resumen son:

  • Métodos anotados con @PostConstruct
  • afterPropertiesSet()según lo definido por la InitializingBeaninterfaz de devolución de llamada
  • Un método init () configurado personalizado

Técnicamente, estos son ganchos en el ciclo de vida del bean , en lugar del ciclo de vida del contexto, pero en el 99% de los casos, los dos son equivalentes.

Si necesita conectarse específicamente al inicio / apagado del contexto, puede implementar la Lifecycleinterfaz en su lugar, pero probablemente eso sea innecesario.

skaffman
fuente
77
Todavía tengo que ver una implementación de Lifecycle o SmartLifecycle después de bastante investigación. Sé que esto tiene un año, pero skaffman, si tienes algo que puedas publicar, sería muy apreciado.
44
Los métodos anteriores se invocan antes de que se haya creado todo el contexto de la aplicación (p. Ej., / Antes / se ha configurado la demarcación de transacciones).
Hans Westerbeek
Recibo una advertencia extraña al intentar usar @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
encierro el
2
Hay casos importantes donde el bean y el ciclo de vida del contexto son muy diferentes. Como señaló @HansWesterbeek, se puede configurar un bean antes de que el contexto del que depende esté completamente listo. En mi situación, un bean dependía de JMS: estaba completamente construido, por lo @PostConstructque se llamó su método, pero la infraestructura de JMS de la que dependía indirectamente aún no estaba completamente conectada (y siendo Spring todo simplemente falló en silencio). Al cambiar a @EventListener(ApplicationReadyEvent.class)todo lo que funcionó ( ApplicationReadyEventSpring Boot es específico para Vanilla Spring, consulte la respuesta de Stefan).
George Hawkins
@Skaffman: ¿qué pasa si mi bean no es referido por ningún bean y quiero inicializar el bean sin ser utilizado en ningún lado?
Sagar Kharab
104

Esto se hace fácilmente con un ApplicationListener. Llegué a esto escuchando Spring's 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 
  }
}

Los escuchas de aplicaciones se ejecutan sincrónicamente en Spring. Si desea asegurarse de que su código se ejecute solo una vez, solo mantenga algún estado en su componente.

ACTUALIZAR

Comenzando con Spring 4.2+ también puede usar la @EventListeneranotación para observar el ContextRefreshedEvent(gracias a @bphilipnyc por señalar esto):

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
fuente
1
Esto también funcionó para mí, perfecto para la inicialización única que no sea bean.
Rory Hunter
9
Nota: para aquellos que están tentados a usar ContextStartedEvent, es más difícil agregar el oyente antes de que se active el evento.
OrangeDog
2
¿Cómo llamar a un repositorio de JPA @Autowired al evento? El repositorio es nulo.
e-info128
No funciona para mi Estoy usando spring mvc 3. Este método onApplicationEvent (___) no recibe una llamada cuando se inicia la aplicación. Alguna ayuda. Aquí está mi código @Component public class AppStartListener implementa ApplicationListener <ContextRefreshedEvent> {public void onApplicationEvent (evento ContextRefreshedEvent final) {System.out.println ("\ n \ n \ nInside en el evento de aplicación"); }}
Vishwas Tyagi
@VishwasTyagi ¿Cómo comienzas tu contenedor? ¿Está seguro de que su AppStartListener es parte de su escaneo de componentes?
Stefan Haberl
38

En Spring 4.2+ ahora puedes simplemente hacer:

@Component
class StartupHousekeeper {

    @EventListener(ContextRefreshedEvent.class)
    public void contextRefreshedEvent() {
        //do whatever
    }
}
vphilipnyc
fuente
¿Se garantiza que este oyente invoca solo una vez después del inicio?
gstackoverflow
No, mira mi respuesta arriba. Mantenga algún estado en su oyente para verificar si se está ejecutando por primera vez
Stefan Haberl
13

Si está utilizando spring-boot, esta es la mejor respuesta.

Siento que @PostConstructy otras diversas interjecciones del ciclo de vida son formas redondas. Estos pueden conducir directamente a problemas de tiempo de ejecución o causar defectos menos que obvios debido a eventos inesperados del ciclo de vida del bean / contexto. ¿Por qué no invocar directamente su bean utilizando Java simple? Todavía invocas el bean 'spring way' (por ejemplo, a través del proxy AoP de spring). Y lo mejor de todo, es simplemente Java, no puede ser más simple que eso. No hay necesidad de oyentes contextuales o programadores extraños.

@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();
    }
}
Zombis
fuente
55
Esta es una buena idea en general, pero al iniciar el contexto de su aplicación de primavera desde una prueba de integración, main nunca se ejecuta.
Jonas Geiregat
@JonasGeiregat: Además, hay otros escenarios en los que no hay ninguno main(), por ejemplo, cuando se usa un marco de aplicación (por ejemplo, JavaServer Faces).
sleske
9

Para los usuarios de Java 1.8 que reciben una advertencia cuando intentan hacer referencia a la anotación @PostConstruct, terminé aprovechando la anotación @Scheduled que puede hacer si ya tiene un trabajo @Scheduled con fixedRate o 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");
    }

}
encierro
fuente
7

Lo que hemos hecho fue extender org.springframework.web.context.ContextLoaderListenerpara imprimir algo cuando comienza el contexto.

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 la subclase luego en web.xml:

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

Con SpringBoot, podemos ejecutar un método en el inicio mediante @EventListeneranotaciones

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

Atención, esto solo se recomienda si su runOnceOnStartupmétodo depende de un contexto de primavera totalmente inicializado. Por ejemplo: desea llamar a un dao con demarcación de transacción

También puede usar un método programado con fixedDelay establecido muy alto

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

Esto tiene la ventaja de que toda la aplicación está conectada (Transacciones, Dao, ...)

visto en Programación de tareas para ejecutar una vez, usando el espacio de nombres de tareas Spring

Joram
fuente
No veo ninguna ventaja sobre el uso @PostConstruct?
Wim Deblauwe
@WimDeblauwe depende de lo que quieras hacer en dosomething () llamar a un dao Autowired con demarcación de Trasaction necesita que se inicie todo el contexto, no solo este bean
Joram
55
@WimDeblauwe El método '@PostConstruct' se activa cuando se inicializa el bean, todo el contexto puede no estar listo (por ejemplo, gestión de transacciones)
Joram
Esto es más elegante que la construcción posterior o cualquier interfaz o evento
aliopi
1
AppStartListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof ApplicationReadyEvent){
            System.out.print("ciao");

        }
    }
}
dnocode
fuente
2
ApplicationReadyEvent está en la bota de primavera no en la primavera 3
John Mercier
0

Si desea configurar un bean antes de que su aplicación se ejecute por completo, puede usar @Autowired:

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

Puede usar @EventListeneren su componente, que se invocará después de que se inicie el servidor y se inicialicen todos los beans.

@EventListener
public void onApplicationEvent(ContextClosedEvent event) {

}
krmanish007
fuente
0

Para un archivo StartupHousekeeper.javaubicado en el paquetecom.app.startup ,

Haz esto en StartupHousekeeper.java:

@Component
public class StartupHousekeeper {

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

Y haz esto 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
fuente