Call context.close() rather than shutdown hook in DevTools restart
Previously, when DevTools was restarting the application it would
use reflection to run all of the JVM's shutdown hooks. This was done
to close any SpringApplications' application contexts. Unfortunately,
it had the unwanted side-effect of running other shutdown hooks as
well.
The other shutdown hooks were often written with the, entirely
reasonable, expectation that they would only be called when the JVM
was shutting down. Calling them at another time could leave the
hook's library in an unexpected state. One such example is Log4J2
which was worked around in aaae4aa3
(see gh-4279). Another is the
problem with Eureka (see gh-4097). There's no work around for this
problem, even with reflective hackery, hence the change being made
here.
This commit updates the Restarter so that shutdown hooks are no longer
called during a restart. This removes the chance of a restart having
the unwanted side-effect of leaving a third-party library in a broken
state. RestartApplicationListener now prepares the Restarter with the
root application context, and the Restarter then closes it as part of
the restart. The changes have been tested with an application that
uses a single context and an application with a context hierarchy.
Closes gh-4097
pull/3499/merge
parent
6e3faecce6
commit
2522a5f9ef
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.devtools.log4j2;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.util.Cancellable;
|
||||
import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
|
||||
import org.apache.logging.log4j.spi.LoggerContextFactory;
|
||||
|
||||
import org.springframework.boot.devtools.restart.RestartListener;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link RestartListener} that prepares Log4J2 for an application restart.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class Log4J2RestartListener implements RestartListener {
|
||||
|
||||
@Override
|
||||
public void beforeRestart() {
|
||||
if (ClassUtils.isPresent("org.apache.logging.log4j.core.impl.Log4jContextFactory",
|
||||
getClass().getClassLoader())) {
|
||||
prepareLog4J2ForRestart();
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareLog4J2ForRestart() {
|
||||
LoggerContextFactory factory = LogManager.getFactory();
|
||||
Field field = ReflectionUtils.findField(factory.getClass(),
|
||||
"shutdownCallbackRegistry");
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
ShutdownCallbackRegistry shutdownCallbackRegistry = (ShutdownCallbackRegistry) ReflectionUtils
|
||||
.getField(field, factory);
|
||||
Field hooksField = ReflectionUtils.findField(shutdownCallbackRegistry.getClass(),
|
||||
"hooks");
|
||||
ReflectionUtils.makeAccessible(hooksField);
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Cancellable> hooks = (Collection<Cancellable>) ReflectionUtils
|
||||
.getField(hooksField, shutdownCallbackRegistry);
|
||||
hooks.clear();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue