探秘Tomcat——连接器和容器的优雅启动
2016-07-16 20:33
639 查看
前言:
上篇《探秘Tomcat——启动篇》粗线条的介绍了在tomcat在启动过程中如何初始化Bootstrap类,加载并执行server,从而启动整个tomcat服务,一直到我们看到控制台打印出如下信息
表示tomcat服务启动成功。
从上面的tomcat启动过程打印信息我们可以发现,在启动tomcat时,我们做了很多工作,包括一些类加载器的初始化,server的加载和启动等,本篇紧接着上篇来说说
这几行console信息背后的故事……
正文:
我们还是从Bootstrap类的main方法说起
在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.
line7获取到server.xml中Connector的protocol属性之后,以此传值并创建一个Connetor对象。
备注:server.xml中有声明了两个Connetor元素,分别是:
Catalina.start
首先执行到((Lifecycle) getServer()).start()的时候会进入StandarServer执行start方法。
该方法唤醒所有LifecycleListeners,具体实现在LifeCycleSupport.fireLifecycleEvent中,包括NamingContextListener、AprLifecycleListener、JasperListener、JreMemoryLeakPreventionListener、ServerLifecycleListener和GlobalResourcesLifecycleListener。
通过循环遍历,启动所有的serivces。这里我们看看StandardService的start方法实现:
line21~28用于递归启动Containers,大致的调用层次为:大致为Server.start->Service.start->StandarEngine.start->StandardHost.start->StandardPipeline.start
line36~47用于启动Connetors,即如下图所示的两个connetors:
![](http://images2015.cnblogs.com/blog/619240/201607/619240-20160716192830373-1798255580.png)
这里对于Http11Protocol的调用顺序是StandardService.start->Connetor.start->Http11Protocol.start->JIoEndpoint.start,启动成功后在console得到打印信息:
对于JkCoyoteHandler调用顺序是StandardService.start->Connetor.start->JkCoyoteHandler.start->JkMain.start,启动成功后在console得到打印信息:
至此,我们算是理清楚了,如何从一个server的load和start能够把所有的services启动,以及service中的Connetor和Container启动起来的。
其实读tomcat的代码还是很费劲的,主要的自己的功力还比较浅,其中用到的一些框架技术或者设计模式不能完全理解,所以阅读过程中会经常卡住,但是从这块启动来看,主要的脉络还是看明白了,读完之后体会还是蛮深刻:
为什么tomcat能够做到启动一个server就能够把存在其上面的serveices都启动,我想这应该是得益于LifeCycle机制,正如上篇所说,所有的组件都实现了LifeCycle的接口,说白了这就是java的面向接口编程的思想的应用,每个组件都实现了LifeCycle接口,而这个接口中具有了start方法,从而可以通过递归调用实现牵一发而动全身的效果;
我们对于Connetor和Container的初始化和启动的所有信息都是来源于配置文件,我们把这些可以灵活配置的信息放到了server.xml文件中,这样下次如果我们想换个端口就可以直接改在文件中,而不需要动代码,这也是降低了代码的耦合性;
当然了,源码中的奥妙肯定远不止于此,还需要慢慢研读^_^,最近有研究tomcat源码的可以一起交流,毕竟一个人能看到的还是蛮有限的。
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。
上篇《探秘Tomcat——启动篇》粗线条的介绍了在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方法说起
public static void main(String args[]) { if (daemon == null) { daemon = new Bootstrap(); try { daemon.init(); } catch (Throwable t) { t.printStackTrace(); return; } } try { String command = "start"; if (args.length > 0) { command = args[args.length - 1]; } if (command.equals("startd")) { args[args.length - 1] = "start"; daemon.load(args); daemon.start(); } else if (command.equals("stopd")) { args[args.length - 1] = "stop"; daemon.stop(); } else if (command.equals("start")) { daemon.setAwait(true); daemon.load(args); daemon.start(); } else if (command.equals("stop")) { daemon.stopServer(args); } else { log.warn("Bootstrap: command \"" + command + "\" does not exist."); } } catch (Throwable t) { t.printStackTrace(); } }
在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.
public void begin(Attributes attributes) throws Exception { Service svc = (Service)digester.peek(); Executor ex = null; if ( attributes.getValue("executor")!=null ) { ex = svc.getExecutor(attributes.getValue("executor")); } Connector con = new Connector(attributes.getValue("protocol")); if ( ex != null ) _setExecutor(con,ex); digester.push(con); }
line7获取到server.xml中Connector的protocol属性之后,以此传值并创建一个Connetor对象。
备注:server.xml中有声明了两个Connetor元素,分别是:
public void start() { if (getServer() == null) { load(); } if (getServer() == null) { log.fatal("Cannot start server. Server instance is not configured."); return; } long t1 = System.nanoTime(); // Start the new server if (getServer() instanceof Lifecycle) { try { ((Lifecycle) getServer()).start(); } catch (LifecycleException e) { log.error("Catalina.start: ", e); } } long t2 = System.nanoTime(); if(log.isInfoEnabled()) log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); try { // Register shutdown hook if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULI's shutdown hook since // shutdown hooks run in parallel and log messages may be lost // if JULI's hook completes before the CatalinaShutdownHook() LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook( false); } } } catch (Throwable t) { // This will fail on JDK 1.2. Ignoring, as Tomcat can run // fine without the shutdown hook. } if (await) { await(); stop(); } }
Catalina.start
首先执行到((Lifecycle) getServer()).start()的时候会进入StandarServer执行start方法。
public void start() throws LifecycleException { // Validate and update our current component state if (started) { log.debug(sm.getString("standardServer.start.started")); return; } // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; // Start our defined Services synchronized (services) { for (int i = 0; i < services.length; i++) { if (services[i] instanceof Lifecycle) ((Lifecycle) services[i]).start(); } } // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); }
该方法唤醒所有LifecycleListeners,具体实现在LifeCycleSupport.fireLifecycleEvent中,包括NamingContextListener、AprLifecycleListener、JasperListener、JreMemoryLeakPreventionListener、ServerLifecycleListener和GlobalResourcesLifecycleListener。
通过循环遍历,启动所有的serivces。这里我们看看StandardService的start方法实现:
public void start() throws LifecycleException { // Validate and update our current component state if (started) { if (log.isInfoEnabled()) { log.info(sm.getString("standardService.start.started")); } return; } if( ! initialized ) init(); // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); if(log.isInfoEnabled()) log.info(sm.getString("standardService.start.name", this.name)); lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; // Start our defined Container first if (container != null) { synchronized (container) { if (container instanceof Lifecycle) { ((Lifecycle) container).start(); } } } synchronized (executors) { for ( int i=0; i<executors.size(); i++ ) { executors.get(i).start(); } } // Start our defined Connectors second synchronized (connectors) { for (int i = 0; i < connectors.length; i++) { try { ((Lifecycle) connectors[i]).start(); } catch (Exception e) { log.error(sm.getString( "standardService.connector.startFailed", connectors[i]), e); } } } // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); }
line21~28用于递归启动Containers,大致的调用层次为:大致为Server.start->Service.start->StandarEngine.start->StandardHost.start->StandardPipeline.start
line36~47用于启动Connetors,即如下图所示的两个connetors:
![](http://images2015.cnblogs.com/blog/619240/201607/619240-20160716192830373-1798255580.png)
这里对于Http11Protocol的调用顺序是StandardService.start->Connetor.start->Http11Protocol.start->JIoEndpoint.start,启动成功后在console得到打印信息:
七月 16, 2016 7:30:50 下午 org.apache.coyote.http11.Http11Protocol start 信息: Starting Coyote HTTP/1.1 on http-8080
对于JkCoyoteHandler调用顺序是StandardService.start->Connetor.start->JkCoyoteHandler.start->JkMain.start,启动成功后在console得到打印信息:
七月 16, 2016 7:36:00 下午 org.apache.jk.common.ChannelSocket init 信息: JK: ajp13 listening on /0.0.0.0:8009 七月 16, 2016 7:36:16 下午 org.apache.jk.server.JkMain start 信息: 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的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。
相关文章推荐
- tomcat:run和tomcat7:run的区别,以及Apache Tomcat Maven Plugin 相关
- Linux安装Tomcat9
- tomcat篇之tomcat结合apache中的模块proxy反代理之apache安装
- Tomcat无故宕机原因分析
- Centos6服务器部署JavaWeb项目(tomcat6+jdk6)
- tomcat系统架构简介
- tomcat7:run配置
- Nginx + Tomcat Windows下的负载均衡配置
- tomcat 熟知与运用
- An incompatible version 1.1.1 of the APR based Apache Tomcat Native library is installed, while Tomcat requires version 1.1.17
- tomcat配置SSL步骤
- 160714、解决虚拟机上的tomcat无法被主机访问的问题
- jsp+sclipse+tomcat
- Tomcat addWebapp()方法和addContext()方法
- javaEE:day1-验证码生成技术和启动tomcat的四种方式
- 网站tomcat配置
- 安装与配置Tomcat8
- 把tomcat服务添加进系统服务中
- Tomcat源码分析--总体架构
- Tomcat9源码编译及导入Eclipse