|
|
|
|
公众号矩阵

面试官:来说说Tomcat的启动过程是什么样子的

阿粉最近在疯狂的研究各种用的工具里面的源码实现,之前给大家都专门的去扣了一下 JDK 里面自带的exe程序,这次阿粉开始更加无聊,直接开始搞Tomcat。

作者:鸭血粉丝 来源: Java极客技术|2021-01-07 07:33

本文转载自微信公众号「Java极客技术」,作者鸭血粉丝 。转载本文请联系Java极客技术公众号。 

阿粉最近在疯狂的研究各种用的工具里面的源码实现,之前给大家都专门的去扣了一下 JDK 里面自带的exe程序,这次阿粉开始更加无聊,直接开始搞Tomcat。

1.Tomcat分析

阿粉知道作为一个 Java 资深开发人员,对 Tomcat 那是再熟悉不过了,bin目录、conf目录、webapps目录,对这些目录熟悉的简直不能再熟悉了。一言不合就是一个shutdown.sh,或者来个shutdown.bat,但是你知道你的启动startup.bat,和startup.sh他们的启动过程是什么过程么?接下来我们就开始进入分析吧。

2.Tomcat的整体结构图

这个整体结构图可不是大家想的目录结构图,目录结构图阿粉就不给大家展示了,自己去打开你的 Tomcat,里面就有你想看到目录结构图,那么整体结构图是什么样子的呢?

给大家解释一下这个图的意思,

  • Server:整个服务器。
  • Service:具体的服务。
  • Connector:提供Socket和request,response的连接。
  • Container:用于封装和管理Servlet,以及具体的处理请求。

这个图就把里面的包含关系说的是明明白白了,为什么这么说呢?因为一个Server中可以存在多个服务,也就是多个Service,而一个Service中可以存在多个Connector,但是只能存在一个Container,是不是就非常的清晰了呢?

3.Tomcat的启动过程

接下来我们就来看看源码里面的启动过程吧,Bootstrap类中的启动过程。

这个类的位置是在tomcat的catalina的包里面,大家看一下主方法也就是所谓的main方法,

  1. public static void main(String[] args) { 
  2.       //对象初始化 
  3.       if (daemon == null) { 
  4.           Bootstrap bootstrap = new Bootstrap(); 
  5.  
  6.           try { 
  7.               bootstrap.init(); 
  8.           } catch (Throwable var3) { 
  9.               handleThrowable(var3); 
  10.               var3.printStackTrace(); 
  11.               return
  12.           } 
  13.  
  14.           daemon = bootstrap; 
  15.       } else { 
  16.           Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); 
  17.       } 
  18.  
  19.       try { 
  20.           String command = "start"
  21.           if (args.length > 0) { 
  22.               command = args[args.length - 1]; 
  23.           } 
  24.  
  25.           if (command.equals("startd")) { 
  26.               args[args.length - 1] = "start"
  27.               //加载 
  28.               daemon.load(args); 
  29.               //启动 
  30.               daemon.start(); 
  31.           } else if (command.equals("stopd")) { 
  32.               args[args.length - 1] = "stop"
  33.               //停止 
  34.               daemon.stop(); 
  35.           } else if (command.equals("start")) { 
  36.               daemon.setAwait(true); 
  37.               //加载并且启动 
  38.               daemon.load(args); 
  39.               daemon.start(); 
  40.               if (null == daemon.getServer()) { 
  41.                   System.exit(1); 
  42.               } 
  43.           } else if (command.equals("stop")) { 
  44.               daemon.stopServer(args); 
  45.           } else if (command.equals("configtest")) { 
  46.               daemon.load(args); 
  47.               if (null == daemon.getServer()) { 
  48.                   System.exit(1); 
  49.               } 
  50.  
  51.               System.exit(0); 
  52.           } else { 
  53.               log.warn("Bootstrap: command \"" + command + "\" does not exist."); 
  54.           } 
  55.       } catch (Throwable var4) { 
  56.           Throwable t = var4; 
  57.           if (var4 instanceof InvocationTargetException && var4.getCause() != null) { 
  58.               t = var4.getCause(); 
  59.           } 
  60.  
  61.           handleThrowable(t); 
  62.           t.printStackTrace(); 
  63.           System.exit(1); 
  64.       } 
  65.  
  66.   } 

main方法里面的存在也很简单,先进行init的操作,然后再执行start,也就是说,启动过程中,首先要进行初始化,然后接下来再进行启动,最后阶段在来个stop,这样才算完整嘛。

  • load方法:其实说白了load方法就是根据server.xml文件创建Server并且调用Server的init方法进行初始化。
  • start方法:start方法很直白,启动服务器。
  • stop方法:stop方法同样,停止服务器。

在这里的start方法和stop方法调用的分别就是调用了Server内部的start和stop方法,而这三个方法都是按照图中的层级结构来的,先从Server的load,start,stop,然后Server的start再调用Service的start,而Service的start调用的就是Connector和Container的start方法了,这从外到内的启动,就可以把Tomcat完整的启动起来了。

我们在接下来就继续从外到内的启动开始分析一波。

3.1 Catalina启动过程

上面的启动入口我们已经成功找到了,那么我们就继续来,对象初始化完成后,执行了init的方法

  1. Bootstrap bootstrap = new Bootstrap(); 
  2.  
  3.            try { 
  4.                bootstrap.init(); 
  5.            } catch (Throwable var3) { 

就是上面的这个,如果参数为空了,那么就开始调用start了,那么start方法是什么呢?

  1. public void start() throws Exception { 
  2.        if (this.catalinaDaemon == null) { 
  3.            this.init(); 
  4.        } 
  5.  
  6.        Method method = this.catalinaDaemon.getClass().getMethod("start", (Class[])null); 
  7.        method.invoke(this.catalinaDaemon, (Object[])null); 
  8.    } 

上面的start方法就是直接使用invoke的方法映射到了catalinaDaemon,也就是到了Catalina的start的方法上,

而这个Catalina的启动无非也就是调用了同样的方法,setAwait方法,load方法,start方法,

  • setAwait方法:用于设置Server启动完成时,是否进入等待,如果是true,那就等待,如果不是false,那就不进入等待。
  • load方法:创建并且初始化Server,
  • start方法:同样是启动服务器

同样的setAwait方法比较少,阿粉就不给大家看了,无非就是个判断,而load方法一定得看,

  1. if (!this.loaded) { 
  2.             this.loaded = true
  3.             long t1 = System.nanoTime(); 
  4.                     try { 
  5.                         inputSource.setByteStream((InputStream)inputStream); 
  6.                         digester.push(this); 
  7.                         digester.parse(inputSource); 
  8.                         break label242; 
  9.                     } catch (SAXParseException var28) { 
  10.                         log.warn("Catalina.start using " + this.getConfigFile() + ": " + var28.getMessage()); 
  11.                         return
  12.                     } catch (Exception var29) { 
  13.                         log.warn("Catalina.start using " + this.getConfigFile() + ": ", var29); 
  14.                     } 
  15.                 } finally { 
  16.                     if (inputStream != null) { 
  17.                         try { 
  18.                             ((InputStream)inputStream).close(); 
  19.                         } catch (IOException var23) { 
  20.                             ; 
  21.                         } 
  22.                     } 
  23.  
  24.                 } 
  25.  
  26.                 return
  27.             } 
  28.  try { 
  29.     //此处同样调用的Server的init方法, 
  30.                 this.getServer().init(); 
  31.             } catch (LifecycleException var24) { 
  32.                 if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) { 
  33.                     throw new Error(var24); 
  34.                 } 
  35.  
  36.                 log.error("Catalina.start", var24); 
  37.             } 
  38.  
  39.             long t2 = System.nanoTime(); 
  40.             if (log.isInfoEnabled()) { 
  41.                 log.info("Initialization processed in " + (t2 - t1) / 1000000L + " ms"); 
  42.             } 
  43.  
  44.         } 

而从这里就开始进入下一步了,Server的启动过程,因为从Catalina里面已经找到了getServer的初始化方法,接下来就是要进行Server的初始化,然后加载,然后启动的过程了。

3.2 Server的启动过程

Server是Tomcat里面的接口,而不是类,那么我们就只能去找实现它的子类来于是就找到了StandardServer extends LifecycleMBeanBase implements Server。

阿粉一看有继承,还有实现,那就先看看LifecycleMBeanBase这个被继承的类,于是再次去看了它,

  1. public abstract class LifecycleMBeanBase extends LifecycleBase implements JmxEnabled { 

嗯?还有继承?继续往下扒拉,

  1. public abstract class LifecycleBase implements Lifecycle { 

终于算是找到了,

阿粉一看这init方法和start方法又调用了initInternal()和startInternal(),找来找去又回去了,而阿粉也从这里知道了,模板方法,是有自己的子类具体实现

于是回到了StandardServer自己的init和start方法,

  1. protected void startInternal() throws LifecycleException { 
  2.        this.fireLifecycleEvent("configure_start", (Object)null); 
  3.        this.setState(LifecycleState.STARTING); 
  4.        this.globalNamingResources.start(); 
  5.        Object var1 = this.servicesLock; 
  6.        synchronized(this.servicesLock) { 
  7.            for(int i = 0; i < this.services.length; ++i) { 
  8.                this.services[i].start(); 
  9.            } 
  10.  
  11.        } 
  12.    } 

总得来说就是,StandardServer继承自LifecycleMBeanBase,而LifecycleMBeanBase继承自LifecycleBase,而LifecycleBase类中的模板方法,又让自己的子类去进行具体的实现,但是我们要知道他的Tomcat生命周期中存在这些内容才行。

图中都说了,Server里面有Service,那么一定就有,我们得去找找看,于是阿粉再次去找并且去看它到底是个什么意思,

  1. public void addService(Service service) { 
  2.         service.setServer(this); 
  3.         Object var2 = this.servicesLock; 
  4.         synchronized(this.servicesLock) { 
  5.             Service[] results = new Service[this.services.length + 1]; 
  6.             System.arraycopy(this.services, 0, results, 0, this.services.length); 
  7.             results[this.services.length] = service; 
  8.             this.services = results; 
  9.             if (this.getState().isAvailable()) { 
  10.                 try { 
  11.                     service.start(); 
  12.                 } catch (LifecycleException var6) { 
  13.                     ; 
  14.                 } 
  15.             } 
  16.  
  17.             this.support.firePropertyChange("service", (Object)null, service); 
  18.         } 
  19.     } 

位置是在Server的接口中出现了增加和删除Service的方法,Server的init方法和start方法循环去调用每个Service的init方法和start方法。

接下来我们看看Service的具体实现,找到StandardService:

  1. protected void initInternal() throws LifecycleException { 
  2.        super.initInternal(); 
  3.        if (this.engine != null) { 
  4.            this.engine.init(); 
  5.        } 
  6.  
  7.        Executor[] arr$ = this.findExecutors(); 
  8.        int len$ = arr$.length; 
  9.  
  10.        int len$; 
  11.        for(len$ = 0; len$ < len$; ++len$) { 
  12.            Executor executor = arr$[len$]; 
  13.            if (executor instanceof JmxEnabled) { 
  14.                ((JmxEnabled)executor).setDomain(this.getDomain()); 
  15.            } 
  16.  
  17.            executor.init(); 
  18.        } 
  19.  
  20.        this.mapperListener.init(); 
  21.        Object var11 = this.connectorsLock; 
  22.        synchronized(this.connectorsLock) { 
  23.            Connector[] arr$ = this.connectors; 
  24.            len$ = arr$.length; 
  25.  
  26.            for(int i$ = 0; i$ < len$; ++i$) { 
  27.                Connector connector = arr$[i$]; 
  28.  
  29.                try { 
  30.                    connector.init(); 
  31.                } catch (Exception var9) { 
  32.                    String message = sm.getString("standardService.connector.initFailed", new Object[]{connector}); 
  33.                    log.error(message, var9); 
  34.                    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) { 
  35.                        throw new LifecycleException(message); 
  36.                    } 
  37.                } 
  38.            } 
  39.  
  40.        } 
  41.    } 

而在方法中主要调用Executor,mapperListener,executor的init方法。

connector之前已经有了,而这个mapperListener就是Mapper的监听器,用来坚挺container容器的变化。


文献参考

《深入剖析Tomcat》

《Tomcat架构解析》

《Servlet/JSP深入详解》

【编辑推荐】

  1. 这年头还有问Tomcat调优和JVM参数优化,你看看这篇文章
  2. 强哥带你精通tomcat
  3. 分享一个超实用的中间件ApacheTomcat漏洞升级方案
  4. Tomcat管理与维护实战培训(企业级中间件09):大型集群与管理优化
  5. 聊一聊如何SpringBoot外置Tomcat
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

数据湖与数据仓库的分析实践攻略

数据湖与数据仓库的分析实践攻略

助力现代化数据管理:数据湖与数据仓库的分析实践攻略
共3章 | 创世达人

1人订阅学习

云原生架构实践

云原生架构实践

新技术引领移动互联网进入急速赛道
共3章 | KaliArch

30人订阅学习

数据中心和VPDN网络建设案例

数据中心和VPDN网络建设案例

漫画+案例
共20章 | 捷哥CCIE

209人订阅学习

视频课程+更多

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微