关于这边blog呢,实际开发中并不会用到,但是我觉得还是很有必要认真的写一下。毕竟我们每天在本地撸码的时候使用的就是tomcat来做web服务器。一个常识就是说我们本地在tomcat里面部署了一个web应用就可以去跑这个应用了,那么这里就有一个很底层的问题,这个web应用是如何在tomcat里面跑起来的呢?我们发了一个http请求,这个请求是如何在到达tomcat上然后又做了些什么才最终返回给我们想要的结果呢?
所以我现在认真的对tomcat的系统架构做一个分析,先交代下大致的情况。
- Tomcat的整体结构
Connector 组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计的本身,而且和不同的应用场景也十分相关,所以一个 Container 可以选择对应多个 Connector。多个 Connector 和一个 Container 就形成了一个 Service, Service也就是服务了,有了 Service 就可以对外提供服务了,但是Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非Server莫属了。所以整个 Tomcat 的生命周期由Server 控制。
前面的博客里面我也已经讲到说我们平时在操作tomcat的配置文件的时候,一般都是在操作server.xml这个配置文件。下面是这个文件的主要的大致标签,发现没?正好和上面所讲的每一层都一一对应的,这样子也方便以后我们记住了呢。
<Server> <Service> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false"> <Context path="/LinkinPark" docBase="E: est1" debug="0" privileged="true"></Context> </Host> </Engine> </Service> </Server>
- 1,Service
Tomcat 中 Service 接口的标准实现类是 StandardService 它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控制它下面的组件的生命周期了。下面就来看下在service添加connector和container2个组件关键的代码:
/** * Set the <code>Container</code> that handles requests for all * <code>Connectors</code> associated with this Service. * * @param container * The new Container * * 这段代码很简单,其实就是先判断当前的这个 Service 有没有已经关联了Container,如果已经关联了,那么去掉这个关联关系——oldContainer.setService(null)。 * 如果这个 oldContainer已经被启动了,结束它的生命周期。然后再替换新的关联、再初始化并开始这个 新的 Container的生命周期。最后将这个过程通知感兴趣的事件监听程序。 * 这里值得注意的地方就是,修改 Container 时要将新的Container 关联到每个 Connector,还好 Container 和 Connector没有双向关联,不然这个关联关系将会很难维护。 */ public void setContainer(Container container) { Container oldContainer = this.container; if ((oldContainer != null) && (oldContainer instanceof Engine)) ((Engine) oldContainer).setService(null); this.container = container; if ((this.container != null) && (this.container instanceof Engine)) ((Engine) this.container).setService(this); if (started && (this.container != null) && (this.container instanceof Lifecycle)) { try { ((Lifecycle) this.container).start(); } catch (LifecycleException e) { ; } } synchronized (connectors) { for (int i = 0; i < connectors.length; i++) connectors[i].setContainer(this.container); } if (started && (oldContainer != null) && (oldContainer instanceof Lifecycle)) { try { ((Lifecycle) oldContainer).stop(); } catch (LifecycleException e) { ; } } // Report this property change to interested listeners support.firePropertyChange("container", oldContainer, this.container); }
// --------------------------------------------------------- Public Methods /** * Add a new Connector to the set of defined Connectors, and * associate[əˈsəʊʃieɪt] it with this Service's * Container.[添加一个新的连接到连接数组里面去,并且要关联服务的容器] * * @param connector * The Connector to be added * 这个方法比较简单,首先是设置关联关系,然后是初始化工作,开始新的生命周期。这里值得一提的是,注意 Connector * 用的是数组而不是 List 集合,这个从性能角度考虑可 以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始 * 就分配一个固定大小[0]的数组,我去,它这里的实现机制是:重新创建一个当前大小的数组对象,然后将原来的数组对象 copy * 到新的数组中,这种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后拿来借鉴,其实这也是性能调优的一点。 */ public void addConnector(Connector connector) { synchronized (connectors) { connector.setContainer(this.container); connector.setService(this); // protected Connector connectors[] = new // Connector[0];这里是上面的那个连接数组的初始化,长度是0,我去 // 创建一个新的数组,这个数组的要比原来的数组多一个,因为要放新的connector进来呢 Connector results[] = new Connector[connectors.length + 1]; System.arraycopy(connectors, 0, results, 0, connectors.length); results[connectors.length] = connector; connectors = results; if (initialized) { try { connector.initialize(); } catch (LifecycleException e) { log.error(sm.getString("standardService.connector.initFailed", connector), e); } } if (started && (connector instanceof Lifecycle)) { try { ((Lifecycle) connector).start(); } catch (LifecycleException e) { log.error(sm.getString("standardService.connector.startFailed", connector), e); } } // Report this property change to interested listeners support.firePropertyChange("connector", null, connector); } }
- 2,Server
- 3,组件的生命线“Lifecycle”
现在我们已经知道Service 和 Server 管理它下面组件的生命周期,那它们是如何管理的呢?
除了控制生命周期的 Start 和 Stop 方法外还有一个监听机制,在生命周期开始和结束的时候做一些额外的操作。这个机制在其它的框架中也被使用,如在 Spring 中。
- 4,Connector
Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是可以选择替换的。 记住这么一句话就好了:Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求。所以这必然是多线程的,多线程的处理是 Connector 设计的核心。 Tomcat5 将这个过程更加细化,它将 Connector 划分成 Connector、 Processor、 Protocol, 另外Coyote 也定义自己的 Request 和 Response 对象。完成这个过程有点复杂,我这里大概的说下执行的相关代码:
启动1个连接(HttpConnector.Start)-->进入等待请求的状态,直到一个新的请求到来才会激活它继续执行HttpProcessor.assign)-->处理这次请求(HttpProcessor.Run)-->解析socket(HttpProcessor.process)-->当Connector 将 socket 连接封装成 request 和 response 对象后接下来的事情就交给 Container 来处理了。
- 5,Container
Container (Servlet 容器)是容器的父接口,所有子容器都必须实现这个接口,Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,分别是: Engine、 Host、 Context、 Wrapper,这四个组件不是平行的,而是父子关系, Engine 包含 Host,Host 包含Context, Context 包含 Wrapper。通常一个 Servlet class 对应一个Wrapper,如果有多个 Servlet 就可以定义多个 Wrapper,如果有多个 Wrapper 就要定义一个更高的 Container 了,如 Context,Context 通常就是对应下面这个配置,在前面的文章我也讲到了,说如何通过配置文件来部署一个web应用。
<Context path="/LinkinPark" docBase="E: est1" debug="0" privileged="true"></Context>Context 还可以定义在父容器 Host 中, Host 不是必须的,但是要运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件,这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表一个完整的 Servlet 引擎。
当Connector接受到一个连接请求时,将请求交给 Container,Container 是如何处理这个请求的?这四个组件是怎么分工的,怎么把请求传给特定的子容器的呢?又是如何将最终的请求交给 Servlet处理?
- 6,Engine
- 7,Host
Host 是 Engine 的字容器,一个 Host 在 Engine 中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。
除了所有容器都继承的ContainerBase外,StandardHost 还实现了Deployer接口,上图清楚的列出了这个接口的主要方法,这些方法都是安装、展开、启动和结束每个web application。Deployer 接口的实现是 StandardHostDeployer,这个类实现了的最要的几个方法, Host可以调用这些方法完成应用的部署等。
- 8,Context
我们知道 Context 的配置文件中有个 reloadable 属性,如下面配置当这个 reloadable 设为 true 时, war 被修改后 Tomcat 会自动的重新加载这个应用。如何做到这点的呢 ? 这个功能是在StandardContext 的 backgroundProcess 方法中实现的,这个方法的代码如下:
public void backgroundProcess() { if (!started) return; count = (count + 1) % managerChecksFrequency; if ((getManager() != null) && (count == 0)) { try { getManager().backgroundProcess(); } catch ( Exception x ) { log.warn("Unable to perform background process on manager",x); } } if (getLoader() != null) { if (reloadable && (getLoader().modified())) { try { Thread.currentThread().setContextClassLoader (StandardContext.class.getClassLoader()); reload(); } finally { if (getLoader() != null) { Thread.currentThread().setContextClassLoader } } } if (getLoader() instanceof WebappLoader) { ((WebappLoader) getLoader()).closeJARs(false); } } }它会调用 reload 方法,而 reload 方法会先调用 stop 方法然后再调用 Start 方法,完成 Context 的一次重新加载。可以看出执行
reload 方法的条件是 reloadable 为 true 和应用被修改,那么这个backgroundProcess 方法是怎么被调用的呢?
这个方法是在 ContainerBase 类中定义的内部类ContainerBackgroundProcessor 被周期调用的,这个类是运行在一个后台线程中,它会周期的执行 run 方法,它的 run 方法会周期调用所有容器的 backgroundProcess 方法,因为所有容器都会继承ContainerBase 类,所以所有容器都能够在 backgroundProcess 方法中定义周期执行的事件。
- 9,Wrapper
它基本上描述了对 Servlet 的操作,当装载了Servlet 后就会调用Servlet的init方法,同时会传一个StandardWrapperFacade对象给Servlet,这个对象包装了StandardWrapper, Servlet可以获得的信息都在StandardWrapperFacade封装,这些信息又是在 StandardWrapper 对象中拿到的。所以 Servlet 可以通过 ServletConfig 拿到有限的容器的信息。当 Servlet 被初始化完成后,就等着 StandardWrapperValve 去调用它的 service 方法了,调用 service 方法之前要调用 Servlet 所有的filter。
上面这段话说的有点绕,我通俗点讲就是说:wrapper是来管理servlet的,它的实现类在装载一个servlet的时候会调用servlet的一些方法,而且还把自己的一个对象包装成了StandardWrapperFacade这个参数参入给了servlet,所以那么servlet可以拿到配置中的所有的容器的信息了,就这么简单,下面是StandardWrapper.loadServlet核心源码:
try { instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet); if( Globals.IS_SECURITY_ENABLED) { boolean success = false; try { Object[] args = new Object[]{ facade }; SecurityUtil.doAsPrivilege("init",servlet,classType,args); success = true; } finally { if (!success) { // destroy() will not be called, thus clear the reference now SecurityUtil.remove(servlet); } } } else { servlet.init(facade); } // Invoke jspInit on JSP pages if ((loadOnStartup >= 0) && (jspFile != null)) { // Invoking jspInit DummyRequest req = new DummyRequest(); req.setServletPath(jspFile); req.setQueryString(Constants.PRECOMPILE + "=true"); DummyResponse res = new DummyResponse(); if( Globals.IS_SECURITY_ENABLED) { Object[] args = new Object[]{req, res}; SecurityUtil.doAsPrivilege("service",servlet,classTypeUsedInService,args); args = null; } else { servlet.service(req, res); } } instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet); } catch (UnavailableException f) { 。。。。。 }
- 10,Tomcat 中其它组件