博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
探秘Tomcat——连接器和容器的优雅启动
阅读量:6583 次
发布时间:2019-06-24

本文共 16718 字,大约阅读时间需要 55 分钟。

前言:

  上篇《》粗线条的介绍了在tomcat在启动过程中如何初始化Bootstrap类,加载并执行server,从而启动整个tomcat服务,一直到我们看到控制台打印出如下信息

七月 16, 2016 4:42:18 下午 org.apache.catalina.core.AprLifecycleListener init信息: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: C:\Program Files\Java\jdk1.8.0_60\bin;C:\Windows\Sun\Java\bin;C:\Windows\system32;C:\Windows;C:\Program Files\Java\jdk1.8.0_60\jre\bin;C:/Program Files/Java/jdk1.8.0_60/bin/../jre/bin/server;C:/Program Files/Java/jdk1.8.0_60/bin/../jre/bin;C:/Program Files/Java/jdk1.8.0_60/bin/../jre/lib/amd64;C:\Program Files\Java\jdk1.8.0_60\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\ATI Technologies\ATI.ACE\Core-Static;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x86;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x64;C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\;C:\Program Files\Microsoft SQL Server\100\Tools\Binn\;C:\Program Files\Microsoft SQL Server\100\DTS\Binn\;C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\;C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\;C:\Program Files (x86)\Microsoft SQL Server\100\DTS\Binn\;C:\Program Files\nodejs;E:\software\apache-maven-3.1.0-bin\apache-maven-3.1.0\bin;E:\software\gradle-2.7\bin;C:\Program Files (x86)\Git\bin;C:\Program Files (x86)\Git\cmd;C:\Users\Administrator\Desktop\博客\20160410\android\android-sdk-windows\tools;E:\software\apache-ant-1.9.7-bin\apache-ant-1.9.7\bin;C:\Users\Administrator\AppData\Roaming\npm;E:\安装包\学习软件\eclipse-jee-mars-1-win32-x86_64\eclipse;;.七月 16, 2016 4:42:41 下午 org.apache.coyote.http11.Http11Protocol init信息: Initializing Coyote HTTP/1.1 on http-8080七月 16, 2016 4:45:01 下午 org.apache.catalina.startup.Catalina load信息: Initialization processed in 190850 ms七月 16, 2016 4:45:07 下午 org.apache.catalina.core.StandardService start信息: Starting service Catalina七月 16, 2016 4:45:07 下午 org.apache.catalina.core.StandardEngine start信息: Starting Servlet Engine: Apache Tomcat/@VERSION@七月 16, 2016 4:45:12 下午 org.apache.catalina.startup.HostConfig deployDescriptor信息: Deploying configuration descriptor host-manager.xml七月 16, 2016 4:45:17 下午 org.apache.catalina.startup.HostConfig deployDescriptor信息: Deploying configuration descriptor manager.xml七月 16, 2016 4:45:18 下午 org.apache.catalina.startup.HostConfig deployDirectory信息: Deploying web application directory docs七月 16, 2016 4:45:19 下午 org.apache.catalina.startup.HostConfig deployDirectory信息: Deploying web application directory examples七月 16, 2016 4:45:21 下午 org.apache.catalina.core.ApplicationContext log信息: ContextListener: contextInitialized()七月 16, 2016 4:45:21 下午 org.apache.catalina.core.ApplicationContext log信息: SessionListener: contextInitialized()七月 16, 2016 4:45:21 下午 org.apache.catalina.startup.HostConfig deployDirectory信息: Deploying web application directory ROOT七月 16, 2016 4:47:47 下午 org.apache.coyote.http11.Http11Protocol start信息: Starting Coyote HTTP/1.1 on http-8080七月 16, 2016 4:48:36 下午 org.apache.jk.common.ChannelSocket init信息: JK: ajp13 listening on /0.0.0.0:8009七月 16, 2016 4:48:44 下午 org.apache.jk.server.JkMain start信息: Jk running ID=0 time=7967/16219  config=null七月 16, 2016 4:49:07 下午 org.apache.catalina.startup.Catalina start信息: Server startup in 243017 ms

  表示tomcat服务启动成功。

 

 

  从上面的tomcat启动过程打印信息我们可以发现,在启动tomcat时,我们做了很多工作,包括一些类加载器的初始化,server的加载和启动等,本篇紧接着上篇来说说

七月 16, 2016 4:47:47 下午 org.apache.coyote.http11.Http11Protocol start信息: Starting Coyote HTTP/1.1 on http-8080七月 16, 2016 4:48:36 下午 org.apache.jk.common.ChannelSocket init信息: JK: ajp13 listening on /0.0.0.0:8009七月 16, 2016 4:48:44 下午 org.apache.jk.server.JkMain start信息: Jk running ID=0 time=7967/16219  config=null

  这几行console信息背后的故事……

 

正文:

  我们还是从Bootstrap类的main方法说起

1 public static void main(String args[]) { 2  3         if (daemon == null) { 4             daemon = new Bootstrap(); 5             try { 6                 daemon.init(); 7             } catch (Throwable t) { 8                 t.printStackTrace(); 9                 return;10             }11         }12 13         try {14             String command = "start";15             if (args.length > 0) {16                 command = args[args.length - 1];17             }18 19             if (command.equals("startd")) {20                 args[args.length - 1] = "start";21                 daemon.load(args);22                 daemon.start();23             } else if (command.equals("stopd")) {24                 args[args.length - 1] = "stop";25                 daemon.stop();26             } else if (command.equals("start")) {27                 daemon.setAwait(true);28                 daemon.load(args);29                 daemon.start();30             } else if (command.equals("stop")) {31                 daemon.stopServer(args);32             } else {33                 log.warn("Bootstrap: command \"" + command + "\" does not exist.");34             }35         } catch (Throwable t) {36             t.printStackTrace();37         }38 39     }

 

在line28~29可以看出依次执行deamon的load和start方法,而实际上这两个方法的具体实现是通过反射机制跳转到类Catalina中找到相应的load和start方法的。

 

load方法执行的是谁的load?load了那些服务组件?load的目的又是什么?

  Catalina.load方法中一个很重要的方法就是createStartDigester,完成的工作是根据conf/server.xml文件中的数据,将相应的元素转化 为对象,将元素中的属性转化为生成对象的属性,并且理清楚各个元素之间的关联关系。比如server.xml文件中最外层的元素是server,server中包含了子节点service,而在这个service里面又有很多元素节点如Connector、Engie、Host等等,这是他们之间的关系。简单说就是先定义一个规则,好让后面在实际解析这个xml文件的时候有章可循。

  当在执行到load中的digester.parse(inputSource)方法时,会依次遍历每个元素,当遍历到Connector元素的时候,会依次调用Digester.startElement->Rule.begin->ConnectorCreateRule.begin.

 

1 public void begin(Attributes attributes) throws Exception { 2         Service svc = (Service)digester.peek(); 3         Executor ex = null; 4         if ( attributes.getValue("executor")!=null ) { 5             ex = svc.getExecutor(attributes.getValue("executor")); 6         } 7         Connector con = new Connector(attributes.getValue("protocol")); 8         if ( ex != null )  _setExecutor(con,ex); 9         10         digester.push(con);11     }

 

  line7获取到server.xml中Connector的protocol属性之后,以此传值并创建一个Connetor对象。

  备注:server.xml中有声明了两个Connetor元素,分别是:

 

1 
8
11
12
18
22
27 28
29
server.xml-Connetor

 

 

  从Connetor类的构造函数可以看出,我们首先会执行Connetor类的setProtocol方法,这时候传入的attributs.getValue("protocol")就会派上用场。

1 public Connector(String protocol) 2         throws Exception { 3         setProtocol(protocol); 4         // Instantiate protocol handler 5         try { 6             Class clazz = Class.forName(protocolHandlerClassName); 7             this.protocolHandler = (ProtocolHandler) clazz.newInstance(); 8         } catch (Exception e) { 9             log.error10                 (sm.getString11                  ("coyoteConnector.protocolHandlerInstantiationFailed", e));12         }13     }

 

  setProtocol方法如下

1 public void setProtocol(String protocol) { 2  3         if (AprLifecycleListener.isAprAvailable()) { 4             if ("HTTP/1.1".equals(protocol)) { 5                 setProtocolHandlerClassName 6                     ("org.apache.coyote.http11.Http11AprProtocol"); 7             } else if ("AJP/1.3".equals(protocol)) { 8                 setProtocolHandlerClassName 9                     ("org.apache.coyote.ajp.AjpAprProtocol");10             } else if (protocol != null) {11                 setProtocolHandlerClassName(protocol);12             } else {13                 setProtocolHandlerClassName14                     ("org.apache.coyote.http11.Http11AprProtocol");15             }16         } else {17             if ("HTTP/1.1".equals(protocol)) {18                 setProtocolHandlerClassName19                     ("org.apache.coyote.http11.Http11Protocol");20             } else if ("AJP/1.3".equals(protocol)) {21                 setProtocolHandlerClassName22                     ("org.apache.jk.server.JkCoyoteHandler");23             } else if (protocol != null) {24                 setProtocolHandlerClassName(protocol);25             }26         }27 28     }

 

  这里首先遍历到的server.xml中的Connector元素是protocol="HTTP/1.1",这时候将org.apache.coyote.http11.Http11Protocol赋值给Connetor的protocolHandlerClassName变量,之后在Connetor构造函数中完成以当前的protocolHandlerClassName值构造一个org.apache.coyote.http11.Http11Protocol对象,并赋值于Connetor的protocolHandler变量。在Http11Protocol类中我们可以发现其中的构造函数和声明的fields如下:

1 // ------------------------------------------------------------ Constructor 2  3     public Http11Protocol() { 4         setSoLinger(Constants.DEFAULT_CONNECTION_LINGER); 5         setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT); 6         //setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT); 7         setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY); 8     } 9 10 11     // ----------------------------------------------------------------- Fields12 13     protected Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this);14     protected JIoEndpoint endpoint = new JIoEndpoint();
View Code

 

  这里初始化主要用于创建serviceSocket对象

  这里的protocolHandler.init()会根据当前的protocolHandler的对象调用相应类的init方法,比如对于Http11Protocol,则会调用Http11Protocol中的init方法,而Http11Protocol.init又会调用endpiont.init方法,endpiont.init的具体实现在JIoEndpoint的init方法中,如下:

1 public void init() 2         throws Exception { 3  4         if (initialized) 5             return; 6          7         // Initialize thread count defaults for acceptor 8         if (acceptorThreadCount == 0) { 9             acceptorThreadCount = 1;10         }11         if (serverSocketFactory == null) {12             serverSocketFactory = ServerSocketFactory.getDefault();13         }14         if (serverSocket == null) {15             try {16                 if (address == null) {17                     serverSocket = serverSocketFactory.createSocket(port, backlog);18                 } else {19                     serverSocket = serverSocketFactory.createSocket(port, backlog, address);20                 }21             } catch (BindException orig) {22                 String msg;23                 if (address == null)24                     msg = orig.getMessage() + " 
:" + port;25 else26 msg = orig.getMessage() + " " +27 address.toString() + ":" + port;28 BindException be = new BindException(msg);29 be.initCause(orig);30 throw be;31 }32 }33 //if( serverTimeout >= 0 )34 // serverSocket.setSoTimeout( serverTimeout );35 36 initialized = true;37 38 }

 

  line17创建了serverSocket对象(这里的调用关系比较深,要结合代码和debug来看)。

  当Http11Protocol.init方法执行完后,console会打印如下信息:

七月 16, 2016 7:03:06 下午 org.apache.coyote.http11.Http11Protocol init信息: Initializing Coyote HTTP/1.1 on http-8080

  之后同理解析到"AJP/1.3"并生成JkCoyoteHandler对象并完成初始化的过程。

  至此,就执行完成了load的所有工作。

 

start方法又是谁的start?谁为start提供了如此便捷的实现?start又启动了那些服务组件?

  下面就开始执行我们的start方法,也就是Catalina.start。

1 public void start() { 2  3         if (getServer() == null) { 4             load(); 5         } 6  7         if (getServer() == null) { 8             log.fatal("Cannot start server. Server instance is not configured."); 9             return;10         }11 12         long t1 = System.nanoTime();13         14         // Start the new server15         if (getServer() instanceof Lifecycle) {16             try {17                 ((Lifecycle) getServer()).start();18             } catch (LifecycleException e) {19                 log.error("Catalina.start: ", e);20             }21         }22 23         long t2 = System.nanoTime();24         if(log.isInfoEnabled())25             log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");26 27         try {28             // Register shutdown hook29             if (useShutdownHook) {30                 if (shutdownHook == null) {31                     shutdownHook = new CatalinaShutdownHook();32                 }33                 Runtime.getRuntime().addShutdownHook(shutdownHook);34                 35                 // If JULI is being used, disable JULI's shutdown hook since36                 // shutdown hooks run in parallel and log messages may be lost37                 // if JULI's hook completes before the CatalinaShutdownHook()38                 LogManager logManager = LogManager.getLogManager();39                 if (logManager instanceof ClassLoaderLogManager) {40                     ((ClassLoaderLogManager) logManager).setUseShutdownHook(41                             false);42                 }43             }44         } catch (Throwable t) {45             // This will fail on JDK 1.2. Ignoring, as Tomcat can run46             // fine without the shutdown hook.47         }48 49         if (await) {50             await();51             stop();52         }53 54     }
Catalina.start

 

  首先执行到((Lifecycle) getServer()).start()的时候会进入StandarServer执行start方法。

1 public void start() throws LifecycleException { 2  3         // Validate and update our current component state 4         if (started) { 5             log.debug(sm.getString("standardServer.start.started")); 6             return; 7         } 8  9         // Notify our interested LifecycleListeners10         lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);11 12         lifecycle.fireLifecycleEvent(START_EVENT, null);13         started = true;14 15         // Start our defined Services16         synchronized (services) {17             for (int i = 0; i < services.length; i++) {18                 if (services[i] instanceof Lifecycle)19                     ((Lifecycle) services[i]).start();20             }21         }22 23         // Notify our interested LifecycleListeners24         lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);25 26     }

 

  • 该方法唤醒所有LifecycleListeners,具体实现在LifeCycleSupport.fireLifecycleEvent中,包括NamingContextListener、AprLifecycleListener、JasperListener、JreMemoryLeakPreventionListener、ServerLifecycleListener和GlobalResourcesLifecycleListener。
  • 通过循环遍历,启动所有的serivces。这里我们看看StandardService的start方法实现:
1 public void start() throws LifecycleException { 2  3         // Validate and update our current component state 4         if (started) { 5             if (log.isInfoEnabled()) { 6                 log.info(sm.getString("standardService.start.started")); 7             } 8             return; 9         }10         11         if( ! initialized )12             init(); 13 14         // Notify our interested LifecycleListeners15         lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);16         if(log.isInfoEnabled())17             log.info(sm.getString("standardService.start.name", this.name));18         lifecycle.fireLifecycleEvent(START_EVENT, null);19         started = true;20 21         // Start our defined Container first22         if (container != null) {23             synchronized (container) {24                 if (container instanceof Lifecycle) {25                     ((Lifecycle) container).start();26                 }27             }28         }29 30         synchronized (executors) {31             for ( int i=0; i

 

  • line21~28用于递归启动Containers,大致的调用层次为:大致为Server.start->Service.start->StandarEngine.start->StandardHost.start->StandardPipeline.start
  • line36~47用于启动Connetors,即如下图所示的两个connetors:

 

  这里对于Http11Protocol的调用顺序是StandardService.start->Connetor.start->Http11Protocol.start->JIoEndpoint.start,启动成功后在console得到打印信息:

1 七月 16, 2016 7:30:50 下午 org.apache.coyote.http11.Http11Protocol start2 信息: Starting Coyote HTTP/1.1 on http-8080

 

  对于JkCoyoteHandler调用顺序是StandardService.start->Connetor.start->JkCoyoteHandler.start->JkMain.start,启动成功后在console得到打印信息:

1 七月 16, 2016 7:36:00 下午 org.apache.jk.common.ChannelSocket init2 信息: JK: ajp13 listening on /0.0.0.0:80093 七月 16, 2016 7:36:16 下午 org.apache.jk.server.JkMain start4 信息: Jk running ID=0 time=33100/45405  config=null

 

  至此,我们算是理清楚了,如何从一个server的load和start能够把所有的services启动,以及service中的Connetor和Container启动起来的。

  其实读tomcat的代码还是很费劲的,主要的自己的功力还比较浅,其中用到的一些框架技术或者设计模式不能完全理解,所以阅读过程中会经常卡住,但是从这块启动来看,主要的脉络还是看明白了,读完之后体会还是蛮深刻:

    •   为什么tomcat能够做到启动一个server就能够把存在其上面的serveices都启动,我想这应该是得益于LifeCycle机制,正如上篇所说,所有的组件都实现了LifeCycle的接口,说白了这就是java的面向接口编程的思想的应用,每个组件都实现了LifeCycle接口,而这个接口中具有了start方法,从而可以通过递归调用实现牵一发而动全身的效果;
    •   我们对于Connetor和Container的初始化和启动的所有信息都是来源于配置文件,我们把这些可以灵活配置的信息放到了server.xml文件中,这样下次如果我们想换个端口就可以直接改在文件中,而不需要动代码,这也是降低了代码的耦合性;

  当然了,源码中的奥妙肯定远不止于此,还需要慢慢研读^_^,最近有研究tomcat源码的可以一起交流,毕竟一个人能看到的还是蛮有限的。

  如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

 

友情赞助

如果你觉得博主的文章对你那么一点小帮助,恰巧你又有想打赏博主的小冲动,那么事不宜迟,赶紧扫一扫,小额地赞助下,攒个奶粉钱,也是让博主有动力继续努力,写出更好的文章^^。

    1. 支付宝                          2. 微信

                      

转载地址:http://pfino.baihongyu.com/

你可能感兴趣的文章
GitHub 开源的 MySQL 在线更改 Schema 工具【转】
查看>>
Identity Server 4 - Hybrid Flow - 保护API资源
查看>>
数据库性能测试:sysbench用法详解
查看>>
CSS阻塞渲染、怎么防止css阻塞
查看>>
larabbs安装教程
查看>>
PHP-CPP开发扩展(一)
查看>>
WRI$_ADV_OBJECTS表过大,导致PDB的SYSAUX表空间不足
查看>>
35.QT-多线程
查看>>
SourceInsight快捷键
查看>>
Hadoop基础-HDFS安全管家之Kerberos实战篇
查看>>
RxJava RxLifecycle 生命周期 内存泄漏 MD
查看>>
设计模式(1)------初始设计模式(回调函数)
查看>>
Linux系统中的tar命令
查看>>
SpringTask定时任务的使用
查看>>
Linux下查看/管理当前登录用户及用户操作历史记录
查看>>
dedecms模板中 if else怎么写
查看>>
VS2017使用Git进行源代码管理
查看>>
微信小程序 weui 使用方法
查看>>
SQL Server 2014 新特性——内存数据库(转载)
查看>>
MS CRM 2011 如何创建并使用Chart
查看>>