- Tomcat的整体结构
前面的博客里面我也已经讲到说我们平时在操作tomcat的配置文件的时候,一般都是在操作server.xml这个配置文件。下面是这个文件的主要的大致标签,发现没?正好和上面所讲的每一层都一一对应的,这样子也方便以后我们记住了呢。
在对整体的组件做了大致的了解后,现在我们具体的来看每一个组件。在这里我一边研究下源码,一边来具体的介绍下每一个组件。首先这些组件肯定是面向接口编的,所以对应的每一个组件都在/tomcat源码/java/org/apache/catalina这个路径下,下面可以找到常用的tomcat里面的每一个具体的接口,然后实现类基本都是在原有的接口名字前面加上standard这个前缀,这个前缀意思就是标准的,具体的类在catalina包下的core路径下,每一个类都可以找见的。
- 1,Service
Tomcat 中 Service 接口的标准实现类是 StandardService 它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控制它下面的组件的生命周期了。下面就来看下在service添加connector和container2个组件关键的代码:
/** * Set theContainer
that handles requests for all *Connectors
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 管理它下面组件的生命周期,那它们是如何管理的呢?
Tomcat 中组件的生命周期是通过 Lifecycle 接口来控制的,组件只要继承这个接口并实现其中的方法就可以统一被拥有它的组件控制了,这样一层一层的直到一个最高级的组件就可以控制 Tomcat 中所有组件的生命周期,这个最高的组件就是 Server,而控制 Server的是 Startup,也就是您启动和关闭 Tomcat。 除了控制生命周期的 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 还可以定义在父容器 Host 中, Host 不是必须的,但是要运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件,这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表一个完整的 Servlet 引擎。
当Connector接受到一个连接请求时,将请求交给 Container,Container 是如何处理这个请求的?这四个组件是怎么分工的,怎么把请求传给特定的子容器的呢?又是如何将最终的请求交给 Servlet处理?
StandardEngineValve 和 StandardHostValve 是 Engine 和 Host的默认的 Valve,它们是最后一个 Valve 负责将请求传给它们的子容器,以继续往下执行。从 Tomcat5 开始,子容器的路由放在了 request 中, request 中保存了当前请求正在处理的 Host、 Context 和 wrapper。
- 6,Engine
- 7,Host
Host 是 Engine 的字容器,一个 Host 在 Engine 中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。
除了所有容器都继承的ContainerBase外,StandardHost 还实现了Deployer接口,上图清楚的列出了这个接口的主要方法,这些方法都是安装、展开、启动和结束每个web application。Deployer 接口的实现是 StandardHostDeployer,这个类实现了的最要的几个方法, Host可以调用这些方法完成应用的部署等。
- 8,Context
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 中其它组件