Tomcat源码分析(六):热加载

热加载

热加载指的是应用的类发生了改变之后,不需要重新启动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,来加载当前应用下的类,从而实现类的热加载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值