热加载
热加载指的是应用的类发生了改变之后,不需要重新启动Tomcat,就可以对这个发生变化的类进行替换
下面分析下Tomcat是如何实现热加载的
后台线程的创建
当StandardEngine调用startInternal执行启动逻辑的时候,最后会调用父类ContainerBase的threadStart方法
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
logger = null;
getLogger();
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
results.add(startStopExecutor.submit(new StartChild(child)));
}
MultiThrowable multiThrowable = null;
for (Future<Void> result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
setState(LifecycleState.STARTING);
// Start our thread
threadStart();
}
可以看到最后会调用额threadStart方法
protected void threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
log.info("threadName:" + threadName);
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
可以看到这里会启动一个守护线程,执行的逻辑封装在了ContainerBackgroundProcessor这个类中
后台线程的执行
接着看下ContainerBackgroundProcessor这个类的run方法
public void run() {
Throwable t = null;
String unexpectedDeathMessage = sm.getString(
"containerBase.backgroundProcess.unexpectedThreadDeath",
Thread.currentThread().getName());
try {
while (!threadDone) {
try {
// 休眠backgroundProcessorDelay指定的秒数
Thread.sleep(backgroundProcessorDelay * 1000L);
} catch (InterruptedException e) {
// Ignore
}
if (!threadDone) {
processChildren(ContainerBase.this);
}
}
} catch (RuntimeException|Error e) {
t = e;
throw e;
} finally {
if (!threadDone) {
log.error(unexpectedDeathMessage, t);
}
}
}
可以看到,这个任务主要的作用就是每隔backgroundProcessorDelay指定的秒数的时间之后,开始执行processChildren方法
接着看下processChildren
protected void processChildren(Container container) {
ClassLoader originalClassLoader = null;
try {
// 当前容器是context
if (container instanceof Context) {
Loader loader = ((Context) container).getLoader();
// Loader will be null for FailedContext instances
if (loader == null) {
return;
}
// Ensure background processing for Contexts and Wrappers
// is performed under the web app's class loader
// 这里会使用容器的类加载器来作为当前的线程上下文类加载器
originalClassLoader = ((Context) container).bind(false, null);
}
// 执行当前线程的后台处理
container.backgroundProcess();
// 遍历子容器,每个容器最终都会执行自己的backgroundProcess方法
Container[] children = container.findChildren();
for (Container child : children) {
// 如果子容器没有设置backgroundsProcessorDelay,那么会递归地处理每个子容器
if (child.getBackgroundProcessorDelay() <= 0) {
processChildren(child);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("Exception invoking periodic operation: ", t);
} finally {
// 当前容器是context,那么因为之前使用了容器的类加载器作为线程上下文类加载器,所以现在要替换回来
if (container instanceof Context) {
((Context) container).unbind(false, originalClassLoader);
}
}
}
StandardContext后台执行
下面看下StandardContext的backgroundProcess方法
public void backgroundProcess() {
if (!getState().isAvailable())
return;
Loader loader = getLoader();
if (loader != null) {
try {
// 热加载发生在这里
loader.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.loader", loader), e);
}
}
Manager manager = getManager();
if (manager != null) {
try {
manager.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.manager", manager),
e);
}
}
WebResourceRoot resources = getResources();
if (resources != null) {
try {
resources.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.resources",
resources), e);
}
}
InstanceManager instanceManager = getInstanceManager();
if (instanceManager instanceof DefaultInstanceManager) {
try {
((DefaultInstanceManager)instanceManager).backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.instanceManager",
resources), e);
}
}
super.backgroundProcess();
}
可以看到会分别调用属于context的组件比如loader,manager等组件的backgroundProcess方法,然后会调用父类的backgroundProcess方法,父类的backgroundProcess方法主要是将各个容器共有的组件的backgroundProcess提出来,比如valve等组件
热加载的主要逻辑就封装在了loader的backgroundProcess方法中,下面看下这个方法
public void backgroundProcess() {
// 判断是否启动了热替换,并且当前应用的类有进行过修改
if (reloadable && modified()) {
try {
Thread.currentThread().setContextClassLoader
(WebappLoader.class.getClassLoader());
if (context != null) {
context.reload();
}
} finally {
if (context != null && context.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
}
}
}
}
判断类需要重新加载
看下WebappLoader的modified方法
@Override
public boolean modified() {
return classLoader != null ? classLoader.modified() : false ;
}
接着看下WebappClassLoaderBase.modified方法
public boolean modified() {
if (log.isDebugEnabled())
log.debug("modified()");
// 遍历加载过的类
for (Entry<String,ResourceEntry> entry : resourceEntries.entrySet()) {
// 判断类是否有修改过
long cachedLastModified = entry.getValue().lastModified;
long lastModified = resources.getClassLoaderResource(
entry.getKey()).getLastModified();
// 上次加载时类的修改时间和当前类的修改时间不一致,代表上次加载后类又发生了变化
if (lastModified != cachedLastModified) {
if( log.isDebugEnabled() )
log.debug(sm.getString("webappClassLoader.resourceModified",
entry.getKey(),
new Date(cachedLastModified),
new Date(lastModified)));
return true;
}
}
// Check if JARs have been added or removed
// 判断/WEB-INF/lib下面是否新增了jar
WebResource[] jars = resources.listResources("/WEB-INF/lib");
// Filter out non-JAR resources
int jarCount = 0;
// 遍历/WEB-INF/lib 下面的每个文件
for (WebResource jar : jars) {
// 只处理jar文件
if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
jarCount++;
// 从缓存中获取上次jar上次的修改时间
Long recordedLastModified = jarModificationTimes.get(jar.getName());
// 没有缓存,代表是新增的jar
if (recordedLastModified == null) {
// Jar has been added
log.info(sm.getString("webappClassLoader.jarsAdded",
resources.getContext().getName()));
return true;
}
// 前后两次修改时间不一致,因此jar文件被修改过了
if (recordedLastModified.longValue() != jar.getLastModified()) {
// Jar has been changed
log.info(sm.getString("webappClassLoader.jarsModified",
resources.getContext().getName()));
return true;
}
}
}
if (jarCount < jarModificationTimes.size()){
log.info(sm.getString("webappClassLoader.jarsRemoved",
resources.getContext().getName()));
return true;
}
// No classes have been modified
return false;
}
从上面的方法可以看到,主要是判断当前已经加载的类的修改时间是否有变化,以及当前应用/WEB-INF/lib下面的jar是否有新增或者修改,从而判断是否需要进行类的重新加载
执行类的重新加载
下面看下StandardContext的reload方法
public synchronized void reload() {
// Validate our current component state
if (!getState().isAvailable())
throw new IllegalStateException
(sm.getString("standardContext.notStarted", getName()));
if(log.isInfoEnabled())
log.info(sm.getString("standardContext.reloadingStarted",
getName()));
// Stop accepting requests temporarily.
setPaused(true);
try {
stop();
} catch (LifecycleException e) {
log.error(
sm.getString("standardContext.stoppingContext", getName()), e);
}
try {
start();
} catch (LifecycleException e) {
log.error(
sm.getString("standardContext.startingContext", getName()), e);
}
setPaused(false);
if(log.isInfoEnabled())
log.info(sm.getString("standardContext.reloadingCompleted",
getName()));
}
从上面的方法可以看出,当进行应用类的重新加载时,
(1)先会设置paused标志为true,停止接收新的请求
(2)然后调用stop方法,停止context
(3)接着又调用start,重新启动context
(4)最后将paused标志设置为false,重新接收请求
在start方法中会创建一个新的WebappLoader,来加载当前应用下的类,从而实现类的热加载