java - obtener - WebApplicationContext no se cierra en Servlet Context reload
obtener servletcontext (4)
Cuando apago Tomcat, observo un apagado correcto y una limpieza del Spring WebApplicationContext. Sin embargo, cuando vuelvo a desplegar mi WAR basado en Spring (copiando la nueva WAR en webapps
), no se produce el apagado normal. Este es un problema para mí debido a las siguientes filtraciones de recursos:
org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [] appears to have started a thread named [hz.hazelcast-swipe-instance.scheduled] but has failed to stop it. This is very likely to create a memory leak.
org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [] appears to have started a thread named [hz.hazelcast-swipe-instance.operation.thread-0] but has failed to stop it. This is very likely to create a memory leak.
... y muchos más. Estoy usando configuración sin XML, este es mi WebApplicationInitializer:
public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
@Override protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
@Override protected Class<?>[] getServletConfigClasses() { return null; }
@Override protected String[] getServletMappings() { return new String[] { "/" }; }
@Override public void onStartup(ServletContext ctx) throws ServletException {
ctx.setInitParameter("spring.profiles.active", "production");
super.onStartup(ctx);
}
}
No existe una configuración específica para controlar el comportamiento en la recarga del contexto del servlet, y supongo que esto debería haber funcionado de la caja.
¿Hay alguna manera de hacer que WebApplicationContext se cierre correctamente antes de continuar con el procedimiento de recarga del contexto del servlet?
Estoy en Spring 4.0.5, Tomcat 7.0.54, Hazelcast 3.2.1, Hibernate 4.3.4.Final.
Actualizar
He agregado un oyente de aplicación Spring para ContextClosedEvent e imprimí el rastro de pila de su invocación:
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:333) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:335) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:880) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:841) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.destroy(FrameworkServlet.java:819) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
at org.apache.catalina.core.StandardWrapper.unload(StandardWrapper.java:1486) [catalina.jar:7.0.54]
at org.apache.catalina.core.StandardWrapper.stopInternal(StandardWrapper.java:1847) [catalina.jar:7.0.54]
at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:232) [catalina.jar:7.0.54]
at org.apache.catalina.core.StandardContext.stopInternal(StandardContext.java:5647) [catalina.jar:7.0.54]
at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:232) [catalina.jar:7.0.54]
at org.apache.catalina.core.ContainerBase$StopChild.call(ContainerBase.java:1575) [catalina.jar:7.0.54]
at org.apache.catalina.core.ContainerBase$StopChild.call(ContainerBase.java:1564) [catalina.jar:7.0.54]
Esto indica que el cierre del resorte ocurre en su método de Servlet#destroy
. Este es el fragmento relevante de AbstractApplicationContext#close()
:
if (logger.isInfoEnabled()) {
logger.info("Closing " + this);
}
LiveBeansView.unregisterApplicationContext(this);
try {
// Publish shutdown event.
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// Stop all Lifecycle beans, to avoid delays during individual destruction.
try {
getLifecycleProcessor().onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
// Destroy all cached singletons in the context''s BeanFactory.
destroyBeans();
// Close the state of this context itself.
closeBeanFactory();
// Let subclasses do some final clean-up if they wish...
onClose();
synchronized (this.activeMonitor) {
this.active = false;
}
Veo la entrada de registro desde el inicio de este fragmento y obtengo mi ContextClosedEvent. También veo una entrada DefaultLifecycleProcessor - Stopping beans in phase 2147483647
, que probablemente proviene de la línea getLifecycleProcessor.onClose()
. Parece que algún error ocurre después de eso. Alguna excepción puede ser tragada.
Actualización 2
Según lo solicitado, así es como configuro Hazelcast:
@Bean(destroyMethod="shutdown") public HazelcastInstance hazelcast() {
final Config c = hzConfig();
final JoinConfig join = c.getNetworkConfig().getJoin();
join.getMulticastConfig().setEnabled(false);
join.getTcpIpConfig().setEnabled(true);
return getOrCreateHazelcastInstance(c);
}
hzConfig()
es un método en el que se configuran el nombre de la instancia, el nombre del grupo y la contraseña, los nombres de los mapas y los índices de los mapas, por lo que no creo que sea de interés aquí.
Y esta es mi configuración Hibernate SessionFactory:
@Bean
public LocalSessionFactoryBean sessionFactory() {
final LocalSessionFactoryBean b = new LocalSessionFactoryBean();
b.setDataSource(dataSource);
b.setHibernateProperties(props(
"hibernate.connection.release_mode", "on_close",
"hibernate.id.new_generator_mappings", "true",
"hibernate.hbm2ddl.auto", "update",
"hibernate.order_inserts", "true",
"hibernate.order_updates", "true",
"hibernate.max_fetch_depth", "0",
"hibernate.jdbc.fetch_size", "200",
"hibernate.jdbc.batch_size", "50",
"hibernate.jdbc.batch_versioned_data", "true",
"hibernate.jdbc.use_streams_for_binary", "true",
"hibernate.use_sql_comments", "true"
));
return b;
}
¿Ha intentado subir unloadDelay
(predeterminado a 2000 ms) para los contextos de Tomcat? Ver http://tomcat.apache.org/tomcat-7.0-doc/config/context.html
ACTUALIZACIÓN : Veo que también está teniendo problemas con el logback, podría valer la pena probar y registrar este oyente también:
class LogbackShutdownListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent event) {
LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
System.out.println("Shutting down Logback context ''" + loggerContext.getName() + "'' for " + contextRootFor(event));
loggerContext.stop();
}
@Override
public void contextInitialized(ServletContextEvent event) {
System.out.println("Logback context shutdown listener registered for " + contextRootFor(event));
}
private String contextRootFor(ServletContextEvent event) {
return event.getServletContext().getContextPath();
}
}
Asegúrese de declarar este oyente antes del oyente del cargador de contexto de primavera para que se invoque después del escucha de contexto al apagarse.
ACTUALIZACIÓN 2 : También podría valer la pena intentar registrar otro bean para manejar el cierre manual de Hazelcast (asegúrese de eliminar también destroyMethod del haba de hazelcast):
@Component
class HazelcastDestructor {
@Autowired
private HazelcastInstance instance;
@PreDestroy
public void shutdown() {
try {
instance.shutdown();
} catch (Exception e) {
System.out.println("Hazelcast failed to shutdown(): " + e);
throw e;
}
}
}
ACTUALIZACIÓN 3 : solo por curiosidad, ¿has probado la implementación paralela: http://www.javacodegeeks.com/2011/06/zero-downtime-deployment-and-rollback.html . Puede comportarse de manera diferente que volver a cargar el mismo contexto. Por lo menos, deberías poder anular la implementación de la versión anterior y ver si eso hace la diferencia.
En algún momento, mencionó que había un NoClassDefFoundError
para Logback. Esto se solucionó eliminando esta dependencia, pero luego el problema se trasladó a otra clase, una de las clases de Spring.
Esto puede significar que una de las bibliotecas que tiene tiene algún problema con los cargadores de clase o tal vez Tomcat necesita instrucciones para no bloquear ciertos recursos. Consulte aquí más sobre los recursos de Tomcat que están bloqueados y la configuración de <Context>
para probar: en conf/context.xml
su Tomcat, coloque un antiResourceLocking="true"
en el elemento.
Desde el punto de vista del desarrollo de aplicaciones web, ServletContainer
solo puede notificar el proceso antes y después de la aplicación. Está utilizando ServletContextListener
.
Config ServletContextListener
en web.xml
<listener>
<listener-class>com.var.YourListener</listener-class>
</listener>
YourListener.java
public class YourListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
//initialization process
}
public void contextDestroyed(ServletContextEvent sce) {
//destory process
}
}
Actualización -XML Less
Programáticamente
@Override
public void onStartup(ServletContext ctx) throws ServletException {
ctx.addListener(new YourContextListener());
ctx.setInitParameter("spring.profiles.active", "production");
super.onStartup(ctx);
}
Anotación
@WebListener / @WebServletContextListener
public class YourContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
Actualización- ShoutDown Hook In Spring
Nunca lo uso antes del desarrollo de mi aplicación, podemos registrar el shoutdownhook event
en AbstractApplicationContext
of Spring. No estoy seguro de que vaya a estar bien o no.
AbstractApplicationContext context = ...
context.registerShutdownHook();
Existe un problema similar en los hilos colgantes mientras se reinicia el contenedor aquí .
De todas las respuestas, una respuesta particular de interés fue de Howard, que muestra la forma en que se eliminan estos hilos.
Existe una buena discusión y razonamiento sobre cómo esto puede terminar los hilos aquí .
Ahora implemente ServletContextListener y cuide estos hilos en el método contextDestroyed () como:
public class YourListener implements ServletContextListener{
....
@Override
public void contextDestroyed(ServletContextEvent event) {
//Call the immolate method here
}
}
Registre este oyente en WebApplicationInitilizer como:
ctx.addListener(new YourListener());
Entonces, cuando se reinicia el servidor - se llama al método contextDestroyed y esto se encarga de todos estos hilos.