前言:
上篇《探秘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方法说起
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 <!-- A "Connector" represents an endpoint by which requests are received
2 and responses are returned. Documentation at :
3 Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
4 Java AJP Connector: /docs/config/ajp.html
5 APR (HTTP/AJP) Connector: /docs/apr.html
6 Define a non-SSL HTTP/1.1 Connector on port 8080
7 -->
8 <Connector port="8080" protocol="HTTP/1.1"
9 connectionTimeout="20000"
10 redirectPort="8443" />
11 <!-- A "Connector" using the shared thread pool-->
12 <!--
13 <Connector executor="tomcatThreadPool"
14 port="8080" protocol="HTTP/1.1"
15 connectionTimeout="20000"
16 redirectPort="8443" />
17 -->
18 <!-- Define a SSL HTTP/1.1 Connector on port 8443
19 This connector uses the JSSE configuration, when using APR, the
20 connector should be using the OpenSSL style configuration
21 described in the APR documentation -->
22 <!--
23 <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
24 maxThreads="150" scheme="https" secure="true"
25 clientAuth="false" sslProtocol="TLS" />
26 -->
27
28 <!-- Define an AJP 1.3 Connector on port 8009 -->
29 <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
从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.error
10 (sm.getString
11 ("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 setProtocolHandlerClassName
14 ("org.apache.coyote.http11.Http11AprProtocol");
15 }
16 } else {
17 if ("HTTP/1.1".equals(protocol)) {
18 setProtocolHandlerClassName
19 ("org.apache.coyote.http11.Http11Protocol");
20 } else if ("AJP/1.3".equals(protocol)) {
21 setProtocolHandlerClassName
22 ("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 // ----------------------------------------------------------------- Fields
12
13 protected Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this);
14 protected JIoEndpoint endpoint = new JIoEndpoint();
这里初始化主要用于创建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() + " <null>:" + port;
25 else
26 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 server
15 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 hook
29 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 since
36 // shutdown hooks run in parallel and log messages may be lost
37 // 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 run
46 // fine without the shutdown hook.
47 }
48
49 if (await) {
50 await();
51 stop();
52 }
53
54 }
首先执行到((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 LifecycleListeners
10 lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
11
12 lifecycle.fireLifecycleEvent(START_EVENT, null);
13 started = true;
14
15 // Start our defined Services
16 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 LifecycleListeners
24 lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
25
26 }
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 LifecycleListeners
15 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 first
22 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<executors.size(); i++ ) {
32 executors.get(i).start();
33 }
34 }
35
36 // Start our defined Connectors second
37 synchronized (connectors) {
38 for (int i = 0; i < connectors.length; i++) {
39 try {
40 ((Lifecycle) connectors[i]).start();
41 } catch (Exception e) {
42 log.error(sm.getString(
43 "standardService.connector.startFailed",
44 connectors[i]), e);
45 }
46 }
47 }
48
49 // Notify our interested LifecycleListeners
50 lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
51
52 }
这里对于Http11Protocol的调用顺序是StandardService.start->Connetor.start->Http11Protocol.start->JIoEndpoint.start,启动成功后在console得到打印信息:
1 七月 16, 2016 7:30:50 下午 org.apache.coyote.http11.Http11Protocol start
2 信息: 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 init
2 信息: JK: ajp13 listening on /0.0.0.0:8009
3 七月 16, 2016 7:36:16 下午 org.apache.jk.server.JkMain start
4 信息: Jk running ID=0 time=33100/45405 config=null
至此,我们算是理清楚了,如何从一个server的load和start能够把所有的services启动,以及service中的Connetor和Container启动起来的。
其实读tomcat的代码还是很费劲的,主要的自己的功力还比较浅,其中用到的一些框架技术或者设计模式不能完全理解,所以阅读过程中会经常卡住,但是从这块启动来看,主要的脉络还是看明白了,读完之后体会还是蛮深刻:
当然了,源码中的奥妙肯定远不止于此,还需要慢慢研读^_^,最近有研究tomcat源码的可以一起交流,毕竟一个人能看到的还是蛮有限的。
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。