Tomcat源码初识四 Tomcat如何实现热加载与热部署

Tomcat源码初识一 Tomcat整理流程图
Tomcat源码初识二 用文字描述整体流程
Tomcat源码初识三 Tomcat如何实现热加载与热部署
Tomcat源码初识四 Tomcat如何打破双亲委派
Tomcat源码初识五 Tomcat如何处理HTTP请求

热部署流程如下:

  1. StandardEngine执行processChildren(child)方法时,一层层调用子类processChildren方法
  2. processChildren方法会执行container.backgroundProcess()方法
  3. StandardContext的backgroundProcess方法执行热加载
  4. 首先判断reloadable =true,然后判断缓存的文件时间与当前文件修改时间对比,如果修改调用reload方法

热加载流程如下:

  1. StandardEngine初始化时会修改backgroundProcessorDelay的值为10
  2. StandardEngine启动时,会创建一个线程,每次sleep 10秒钟(第一步的值)
  3. 调用StandardEngine容器的processChildren方法
  4. StandardEngine.processChildren()方法会调用子容器(StandardHost)的processChildren方法
  5. StandardHost.processChildren()方法会触发PERIODIC_EVENT事件
  6. 事件通知到HostConfig.lifecycleEvent方法,然后调用check方法
  7. 如果host的autoDeploy为ttrue,调用deployApps方法
  8. 加载类文件

热部署代码执行流程如下:

StandardEngine初始化时会修改backgroundProcessorDelay的值,代码如下

public StandardEngine() {
	...
     // 执行间隔,复制10,代表10秒钟检测一次
     backgroundProcessorDelay = 10;
 }

StandardEngine启动时,会调用threadStart()方法, 此方法代码如下

// 容器后台监控(热部署与热加载线程)
 threadDone = false;
 String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
 thread = new Thread(new ContainerBackgroundProcessor(), threadName);
 thread.setDaemon(true);
 thread.start();

线程启动后,每隔10秒钟执行一次检测,调用子容器的processChildren

// 容器后台监控(热部署与热加载线程)
public void run() {
    ...
    try {
        while (!threadDone) {
            try {
                Thread.sleep(backgroundProcessorDelay * 1000L);
            } catch (InterruptedException e) {
                // Ignore
            }
            if (!threadDone) {
                processChildren(ContainerBase.this);
            }
        }
    } ...
}

protected void processChildren(Container container) {
	...
         for (Container child : children) {
             if (child.getBackgroundProcessorDelay() <= 0) {
             	 //实现热加载
                 processChildren(child);
             }
         }
    ...
    fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);.
    
 }

 protected void processChildren(Container container) {
    try {
        ...
        //后台监控(实现热加载)
        container.backgroundProcess();
        Container[] children = container.findChildren();
        for (Container child : children) {
            if (child.getBackgroundProcessorDelay() <= 0) {
                processChildren(child);
            }
        }
    } 
    ...
}

子容器的processChildren方法中,会触发fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);这个方法会通知对应的监听,StandardEngine的子容器StandardHost对应的监听器HostConfig,代码如下

public void lifecycleEvent(LifecycleEvent event) {
	...
    if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
        check();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
    } else if (event.getType().equals(Lifecycle.START_EVENT)) {
        start();
    } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
        stop();
    }
}

protected void check() {
	//如果配置了热部署
    if (host.getAutoDeploy()) {
        ...
        deployApps();
    }
}

deployApps方法会调用deployDescriptors、deployWARs、deployDirectories。deployDirectories方法如下

protected void deployDirectories(File appBase, String[] files) {
    if (files == null)
        return;
    //获取StartStop线程池
    ExecutorService es = host.getStartStopExecutor();
    List<Future<?>> results = new ArrayList<>();
    for (String file : files) {
        ...
        if (dir.isDirectory()) {
            ...
            //多线程执行DeployDirectory
            results.add(es.submit(new DeployDirectory(this, cn, dir)));
        }
    }

    for (Future<?> result : results) {
        try {
            result.get();
        } catch (Exception e) {
        }
    }
}

DeployDirectory类代码如下

public void run() {
   config.deployDirectory(cn, dir);
}

//实际调用
protected void deployDirectory(ContextName cn, File dir) {
    ...
    try {
        if (deployThisXML && xml.exists()) {
            ...
        } else if (!deployThisXML && xml.exists()) {
            ...
        } else {
        	//通过反射创建StandardContext对象
            context = (Context) Class.forName(contextClass).getConstructor().newInstance();
        }
        //创建context对象的监听对象ContextConfig
        Class<?> clazz = Class.forName(host.getConfigClass());
        LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
        //设置监听
        context.addLifecycleListener(listener);
		...
		//项目添加到host中(部署)
		host.addChild(context);
    } catch (Throwable t) {
       ...
    } finally {
        ...
    }
	...
}

项目添加到host容器中,代码流程如下:

host.addChild(context)->StandardHost.addChild()->ContainerBase.addChild()->ContainerBase.addChildInternal(child)->child.start()->LifecycleBase.startInternal()->StandardContext.startInternal()

protected synchronized void startInternal() throws LifecycleException {

    ...
    // 在work文件夹中添加
    postWorkDirectory();

    ...
	
    if (getLoader() == null) {
        WebappLoader webappLoader = new WebappLoader();
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader);
    }

    // An explicit cookie processor hasn't been specified; use the default
    if (cookieProcessor == null) {
        cookieProcessor = new Rfc6265CookieProcessor();
    }

    // Initialize character set mapper
    getCharsetMapper();

    // Validate required extensions
    boolean dependencyCheck = true;
    try {
        dependencyCheck = ExtensionValidator.validateApplication
            (getResources(), this);
    } catch (IOException ioe) {
        log.error(sm.getString("standardContext.extensionValidationError"), ioe);
        dependencyCheck = false;
    }

    if (!dependencyCheck) {
        // do not make application available if dependency check fails
        ok = false;
    }

    // Reading the "catalina.useNaming" environment variable
    String useNamingProperty = System.getProperty("catalina.useNaming");
    if ((useNamingProperty != null)
        && (useNamingProperty.equals("false"))) {
        useNaming = false;
    }

    if (ok && isUseNaming()) {
        if (getNamingContextListener() == null) {
            NamingContextListener ncl = new NamingContextListener();
            ncl.setName(getNamingContextName());
            ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
            addLifecycleListener(ncl);
            setNamingContextListener(ncl);
        }
    }

    // Standard container startup
    if (log.isDebugEnabled())
        log.debug("Processing standard container startup");


    // Binding thread
    ClassLoader oldCCL = bindThread();

    try {
        if (ok) {
            // 初始化
            Loader loader = getLoader();
            if (loader instanceof Lifecycle) {
                ((Lifecycle) loader).start();
            }

            ...
        if (ok) {
        	//加载listener
            if (!listenerStart()) {
                log.error(sm.getString("standardContext.listenerFail"));
                ok = false;
            }
        }
    	...
        if (ok) {
        	//加载filter
            if (!filterStart()) {
                log.error(sm.getString("standardContext.filterFail"));
                ok = false;
            }
        }

        // 加载servlets
        if (ok) {
            if (!loadOnStartup(findChildren())){
                log.error(sm.getString("standardContext.servletFail"));
                ok = false;
            }
        }
		//开启新线程热加载
        super.threadStart();
    } 
    ....
}

热加载代码执行流程如下:
StandardEngine执行processChildren(child)方法时,一层层调用子类processChildren方法,processChildren方法会执行container.backgroundProcess()方法,执行代码如下:

public void backgroundProcess() {
	...
    Loader loader = getLoader();
    if (loader != null) {
        try {
        	//实现热加载
            loader.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString(
                    "standardContext.backgroundProcess.loader", loader), e);
        }
    }
    ...
}

public void backgroundProcess() {
 //如果Context配置了reloadable =true,判断是否有文件修改
    if (reloadable && modified()) {
        try {
            Thread.currentThread().setContextClassLoader
                (WebappLoader.class.getClassLoader());
            if (context != null) {
            	 //重新加载context
                context.reload();
            }
        } finally {
            if (context != null && context.getLoader() != null) {
                Thread.currentThread().setContextClassLoader
                    (context.getLoader().getClassLoader());
            }
        }
    }
}
//判断文件是否修改过
public boolean 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;
       }
   }
   ...
   //循环所有jar
   for (WebResource jar : jars) {
       if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
           jarCount++;
           Long recordedLastModified = jarModificationTimes.get(jar.getName());
           if (recordedLastModified == null) {
               // Jar has been added
               log.info(sm.getString("webappClassLoader.jarsAdded",
                       resources.getContext().getName()));
               return true;
           }
           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;
   }
   ...
   return false;
}

Tomcat源码初识一 Tomcat整理流程图
Tomcat源码初识二 用文字描述整体流程
Tomcat源码初识三 Tomcat如何实现热加载与热部署
Tomcat源码初识四 Tomcat如何打破双亲委派
Tomcat源码初识五 Tomcat如何处理HTTP请求

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值