粗浅看 Tomcat系统架构分析

20 篇文章 2 订阅
16 篇文章 0 订阅

Tomcat的结构很复杂,但是Tomcat也非常的模块化,找到了Tomcat最核心的模块,就抓住了Tomcat七寸

整体结构

Tomcat 总体结构图


从上图中可以看出Tomcat的心脏是两个组件:Connector 和 Container,关于这两个组件将在后面详细介绍。Connector 组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计的本身,而且和不同的应用场景也十分相关,所以一个Container 可以选择对应多个Connector多个Connector和一个Container 就形成了一个ServiceService 的概念大家都很熟悉了,有了Servic就可以对外提供服务了,但是Service还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非Server莫属了。所以整个Tomcat的生命周期由Server控制。

以Service  作为“婚姻”

我们将 Tomca中 ConnectorContainer 作为一个整体比作一对情 侣的话,Connector主要负责对外交流,可以比作为 BoyContaine主要处理 Connector 接受的请求,主要是处理内部事务,可以比作为 Girl那么这个 Service就是连接这对男女的结婚证了。是Service将它们连接在一起,共同组成一个家庭。当然要组成一个家庭还要很多其它的元素。

说白了,Service 只是在Connector 和 Container外面多包一层,把它们组装在一起,向外面提供服务,一个Service可以设置多个Connector但是只能有一个 Containe容器。这个 Service 口的 方法列表如下:

①Service接口


从 Service接口中定义的方法中可以看出,它主要是为了关联Connector和 Container,同时会初始化它下面的其它组件,注意接 口中它并没有规定一定要控制它下面的组件的生命周期。所有组件的 生命周期在一个 Lifecycle 接口中控制,这里用到了一个重要的设 计模式,关于这个接口将在后面介绍。

Tomcat 中 Service接口的标准实现类是StandardService它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控 制它下面的组件的生命周期了。StandardService 类结构图如下:

②StandardService的类结构图


从上图中可以看出除了 Service接口的方法的实现以及控制组件生命周期的 Lifecycle 口的实现,还有几个方法是用于在事件监听的 方法的实现,不仅是这个 Service 组件Tomcat 其它组件也同样 有这几个方法,这也是一个典型的设计模式,将在后面介绍。

下面看一下 StandardService 中主要的几个方法实现的代码,下面是setContaineraddConnector 方法的源码:

StandardService. SetContainer

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public void setContainer(Container container) {  
  2.   
  3. Container oldContainer = this.container;  
  4.   
  5. if ((oldContainer != null) && (oldContainer instanceof Engine))  
  6.   
  7. ((Engine) oldContainer).setService(null);  
  8.   
  9. this.container = container;  
  10.   
  11. if ((this.container != null) && (this.container instanceof Engine))  
  12.   
  13. ((Engine)  this.container).setService(this);  
  14.   
  15. if (started && (this.container != null) && (this.container instanceof Lifecycle))  
  16.   
  17. {  
  18.   
  19. try {  
  20.   
  21. ((Lifecycle) this.container).start();  
  22.   
  23. catch (LifecycleException e) {  
  24.   
  25. ;  
  26.   
  27. }  
  28.   
  29. }  
  30.   
  31. synchronized (connectors) {  
  32.   
  33. for (int i = 0; i < connectors.length; i++)  
  34.   
  35. connectors[i].setContainer(this.container);  
  36.   
  37. }  
  38.   
  39. if (started && (oldContainer != null) && (oldContainer instanceof Lifecycle)) {  
  40.   
  41. try {  
  42.   
  43. ((Lifecycle)  oldContainer).stop();  
  44.   
  45. catch (LifecycleException e) {  
  46.   
  47. ;  
  48.   
  49. }  
  50.   
  51. }  
  52.   
  53. support.firePropertyChange("container", oldContainer, this.container);  
  54. —————————————————————————————  
  55. }  
  56. </span>  

这段代码很简单,其实就是先判断当前的这个 Service 没有已经关 联了 Container,如果已经关联了,那么去掉这个关联关系——oldContainer.setService(null)。如果这个oldContainer 已经被启动 了,结束它的生命周期。然后再替换新的关联、再初始化并开始这个新的 Container 的生命周期。最后将这个过程通知感兴趣的事件监听程序。这里值得注意的地方就是,修改Container 时要将新的 Container关联到每个Connector,还好Container 和 Connector 没有双向关联,不然这个关联关系将会很难维护。

StandardService. addConnector

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public void addConnector(Connector connector) {  
  2.   
  3. synchronized (connectors) {  
  4.   
  5. connector.setContainer(this.container);  
  6.   
  7. connector.setService(this);  
  8.   
  9. Connector results[] = new Connector[connectors.length + 1];  
  10.   
  11. System.arraycopy(connectors, 0, results, 0, connectors.length);  
  12.   
  13. results[connectors.length] = connector;  
  14.   
  15. connectors = results;  
  16.   
  17. if (initialized) {  
  18.   
  19. try {  
  20.   
  21. connector.initialize();  
  22.   
  23. catch (LifecycleException e) {  
  24.   
  25. e.printStackTrace(System.err);  
  26.   
  27. }  
  28.   
  29. }  
  30.    
  31.   
  32. if (started && (connector instanceof Lifecycle)) {  
  33.   
  34. try {  
  35.   
  36. ((Lifecycle) connector).start();  
  37.   
  38. catch (LifecycleException e) {  
  39.   
  40. ;  
  41.   
  42. }  
  43.   
  44. }  
  45.   
  46. support.firePropertyChange("connector"null, connector);  
  47.   
  48. }  
  49.   
  50. }  
  51. </span>  

上面是 addConnector 法,这个方法也很简单,首先是设置关联关 系,然后是初始化工作,开始新的生命周期。这里值得一提的是,注 意 Connector 用的是数组而不是 List集合,这个从性能角度考虑可 以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始 就分配一个固定大小的数组,它这里的实现机制是:重新创建一个当 前大小的数组对象,然后将原来的数组对象 copy 到新的数组中,这 种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后 拿来借鉴。

最新的 Tomcat6 中 StandardService也基本没有变化,但是从Tomcat5 开始ServiceServer 和容器类都继承了MBeanRegistration接口,Mbeans 的管理更加合理。

以 Server  为“居”

前面说一对情侣因为 Service 而成为一对夫妻,有了能够组成一个家 庭的基本条件,但是它们还要有个实体的家,这是它们在社会上生存 之本,有了家它们就可以安心的为人民服务了,一起为社会创造财富。

Server要完成的任务很简单,就是要能够提供一个接口让其它程序能够访问到这个Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service。还有其它的一些次要的任务,如您住在这个地方要向当 地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么 的。

Server的类结构图如下:

①Server的类结构图


它的标准实现类 StandardServer 实现了上面这些方法,同时也实现 了LifecycleMbeanRegistration 两个接口的所有方法,下面主要看 一下 StandardServer重要的一个方法 addService的实现:

②StandardServer.addService

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public void addService(Service service) {  
  2.   
  3. service.setServer(this);  
  4.   
  5. synchronized (services) {  
  6.   
  7. Service results[] = new Service[services.length + 1];  
  8.   
  9. System.arraycopy(services, 0, results, 0, services.length);  
  10.   
  11. results[services.length] = service;  
  12.   
  13. services = results;  
  14.   
  15. if (initialized) {  
  16.   
  17. try {  
  18.   
  19. service.initialize();  
  20.   
  21. catch (LifecycleException e) {  
  22.   
  23. e.printStackTrace(System.err);  
  24.   
  25. }  
  26.   
  27. }  
  28.   
  29. if (started && (service instanceof Lifecycle)) {  
  30.   
  31. try {  
  32.   
  33. ((Lifecycle) service).start();  
  34.   
  35. catch (LifecycleException e) {  
  36.   
  37. ;  
  38.   
  39. }  
  40.   
  41. }  
  42.   
  43. support.firePropertyChange("service"null, service);  
  44.   
  45. }  
  46.   
  47. }  
  48. </span>  

从上面第一句就知道了 Service和 Server是相互关联的,Server也是和 Service 管理 Connector 一样管理它,也是将 Service 放在 一个数组中,后面部分的代码也是管理这个新加进来的 Service 的生 命周期。Tomcat6 中也是没有什么变化的。

组件的生命线“Lifecycle”

前面一直在说 Service 和 Serve管理它下面组件的生命周期,那它 们是如何管理的呢?

Tomcat 中组件的生命周期是通过Lifecycle 接口来控制的,组件只 要继承这个接口并实现其中的方法就可以统一被拥有它的组件控制 了,这样一层一层的直到一个最高级的组件就可以控制 Tomcat 中 所有组件的生命周期,这个最高的组件就是 Server,而控制Server的是 Startup,也就是您启动和关闭Tomcat

下面是 Lifecycle 接口的类结构图:

①Lifecycle类结构图

除了控制生命周期的 Start 和 Stop 方法外还有一个监听机制,在生命周期开始和结束的时候做一些额外的操作。这个机制在其它的框架中也被使用,如在spring 中。关于这个设计模式会在后面介绍。

Lifecycle接口的方法的实现都在其它组件中,就像前面中说的,组件的生命周期由包含它的父组件控制,所以它的 Star方法自然就是调用它下面的组件的 Star方法Sto方法也是一样。如在 Serve中 Start 方法就会调用Service组件的 Start方法,Server Start方法代码如下:

②StandardServer.Start

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public void start() throws LifecycleException {  
  2.   
  3. if (started) {  
  4.   
  5. log.debug(sm.getString("standardServer.start.started"));  
  6.   
  7. return;  
  8.   
  9. }  
  10.   
  11. lifecycle.fireLifecycleEvent(BEFORE_START_EVENT,  null);  
  12.   
  13. lifecycle.fireLifecycleEvent(START_EVENT,  null);  
  14.   
  15. started = true;  
  16.   
  17. synchronized (services) {  
  18.   
  19. for (int i = 0; i < services.length; i++) {  
  20.   
  21. if (services[i] instanceof Lifecycle)  
  22.   
  23. ((Lifecycle) services[i]).start();  
  24.   
  25. }  
  26.   
  27. }  
  28.   
  29. lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);  
  30.   
  31. }</span>  
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;"><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">监听的代</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">码会包围 </span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">S</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">e</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">r</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">vi</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">ce </span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">组</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">件的启动</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">过程,就是简单的循环启动所</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">有 </span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">Service</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">组件的</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">Start </span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">方法,但是所有</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">Service</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">必须要实现</span></span>  

Lifecycle接口,这样做会更加灵活。

Server的 Stop 方法代码如下:

③StandardServer.Stop

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public void stop() throws LifecycleException {  
  2.   
  3. if (!started)  
  4.   
  5. return;  
  6.   
  7. lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);  
  8.   
  9. lifecycle.fireLifecycleEvent(STOP_EVENT,  null);  
  10.   
  11. started = false;  
  12.   
  13. for (int i = 0; i < services.length; i++) {  
  14.   
  15. if (services[i] instanceof Lifecycle)  
  16.   
  17. ((Lifecycle) services[i]).stop();  
  18.   
  19. }  
  20.   
  21. lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);  
  22.   
  23. }  
  24. </span>  

它所要做的事情也和Start方法差不多。

Connector组件

Connector组件是Tomcat中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的tcp连接请求,创建个Request 和处理这个请求并把产生的Request 和 Response对象传给处理这个请求的线程,处理这个请求的线程就是Container 组件要做的事了。

由于这个过程比较复杂,大体的流程可以用下面的顺序图来解释:

①Connector处理一次请求顺序图



Tomcat5 中默认的 Connecto是 Coyote这个 Connector 是可以选择替换的。Connecto重要的功能就是接收连接请求然后分配线 程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。Tomcat5将这个过程更加细化,它将 Connector划分成 ConnectorProcessorProtocol, 另外Coyote也定义自己的Request 和 Response对象。

下面主要看一下 Tomcat 中如何处理多线程的连接请求,先看一下Connector的主要类图:

② Connector的主要类图


看一下HttpConnectorStart 方法:

③HttpConnector.Start

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public void start() throws LifecycleException {  
  2.   
  3. if (started)  
  4.   
  5. throw new LifecycleException  
  6.   
  7. (sm.getString("httpConnector.alreadyStarted"));  
  8.   
  9. threadName = "HttpConnector[" + port + "]";  
  10.   
  11. lifecycle.fireLifecycleEvent(START_EVENT,  null);  
  12.   
  13. started = true;  
  14.   
  15. threadStart();  
  16.   
  17. while (curProcessors < minProcessors) {  
  18.   
  19. if ((maxProcessors > 0) && (curProcessors >= maxProcessors))  
  20.   
  21. break;  
  22.   
  23. HttpProcessor processor = newProcessor();  
  24.   
  25. recycle(processor);  
  26.   
  27. }  
  28.   
  29. }  
  30. </span>  

threadStart()执行就会进入等待请求的状态,直到一个新的请求到来才会激活它继续执行,这个激活是在HttpProcessor 的 assign 方法中,这个方法是代码如下 :

④ HttpProcessor.assign

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">synchronized void assign(Socket socket) {  
  2.   
  3. while (available) {  
  4.   
  5. try {  
  6.   
  7. wait();  
  8.   
  9. catch (InterruptedException e) {  
  10.    
  11. —————————————————————————————  
  12. }  
  13.   
  14. }  
  15.   
  16. this.socket = socket;  
  17.   
  18. available = true;  
  19.   
  20. notifyAll();  
  21.   
  22. if ((debug >= 1) && (socket != null))  
  23.   
  24. log(" An incoming request is being assigned");  
  25.   
  26. }  
  27. </span>  

创建 HttpProcessor 对象是会把 available 设为 false,所以当请求 到来时不会进入 while循环,将请求的socket 赋给当期处理的 socket,并将 available设为true,当 available设为true 是 HttpProcessor的 run方法将被激活,接下去将会处理这次请求。

Run方法代码如下:

⑤HttpProcessor.Run

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public void run() {  
  2.   
  3. while (!stopped) {  
  4.   
  5. Socket socket = await();  
  6.   
  7. if (socket == null)  
  8.   
  9. continue;  
  10.   
  11. try {  
  12.   
  13. process(socket);  
  14.   
  15. catch (Throwable t) {  
  16.   
  17. log("process.invoke", t);  
  18.   
  19. }  
  20.   
  21. connector.recycle(this);  
  22.   
  23. }  
  24.   
  25. —————————————————————————————  
  26. synchronized (threadSync) {  
  27.   
  28. threadSync.notifyAll();  
  29.   
  30. }  
  31.   
  32. }  
  33. </span>  

解析 socke的过程在 process 法中process 方法的代码片段如 下:

⑥HttpProcessor.process

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">private void process(Socket socket) {  
  2.   
  3. boolean ok = true;  
  4.   
  5. boolean finishResponse = true;  
  6.   
  7. SocketInputStream input = null;  
  8.   
  9. OutputStream output = null;  
  10.   
  11. try {  
  12.   
  13. input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize());  
  14. catch (Exception e) {  
  15.   
  16. log("process.create", e);  
  17.   
  18. ok = false;  
  19.   
  20. }  
  21.   
  22. keepAlive = true;  
  23.   
  24. while (!stopped && ok && keepAlive) {  
  25.   
  26. finishResponse = true;  
  27.   
  28. try {  
  29.   
  30. request.setStream(input);  
  31.   
  32. request.setResponse(response);  
  33.   
  34. output = socket.getOutputStream();  
  35.   
  36. response.setStream(output);  
  37.   
  38. response.setRequest(request);  
  39.   
  40. ((HttpServletResponse)  response.getResponse())  
  41.   
  42.    
  43. —————————————————————————————  
  44. .setHeader("Server", SERVER_INFO);  
  45.   
  46. catch (Exception e) {  
  47.   
  48. log("process.create", e);  
  49.   
  50. ok = false;  
  51.   
  52. }  
  53.   
  54. try {  
  55.   
  56. if (ok) {  
  57.   
  58. parseConnection(socket);  
  59.   
  60. parseRequest(input, output);  
  61.   
  62. if (!request.getRequest().getProtocol().startsWith("HTTP/0"))  
  63.   
  64. parseHeaders(input);  
  65.   
  66. if (http11) {  
  67.   
  68. ackRequest(output);  
  69.   
  70. if  (connector.isChunkingAllowed())  
  71.   
  72. response.setAllowChunking(true);  
  73.   
  74. }  
  75.   
  76. }  
  77.   
  78. try {  
  79.   
  80. ((HttpServletResponse)  response).setHeader  
  81.   
  82. ("Date",  FastHttpDateFormat.getCurrentDate());  
  83.   
  84. if (ok) {  
  85.   
  86. connector.getContainer().invoke(request, response);  
  87.   
  88. }  
  89.   
  90. }  
  91.   
  92. try {  
  93.   
  94. shutdownInput(input);  
  95.   
  96. socket.close();  
  97.   
  98. catch (IOException e) {  
  99.   
  100. ;  
  101.   
  102. catch (Throwable e) {  
  103.    
  104.   
  105. log("process.invoke", e);  
  106.   
  107. }  
  108.   
  109. socket = null;  
  110.   
  111. }  
  112. </span>  

当 Connector将 socket 连接封装成 request 和 response 对象后 接下来的事情就交给Container 来处理了。

Servlet容器“Container”

Container是容器的父接口,所有子容器都必须实现这个接口,Container容器的设计用的是典型的责任链的设计模式,它有四个子 容器组件构成,分别是:Engine、Host、Context、Wrapper,这四个组件不是平行的,而是父子关系,Engine包含 Host,Host 包含 Context,Context 包含 Wrapper。通常一个 Servlet class 对应一个 Wrapper,如果有多个 Servlet 就可以定义多个 Wrapper,如果有多 个 Wrapper 就要定义一个更高的Container 了,如 Context, Context 通常就是对应下面这个配置:

①Server.xml

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;"><Context  
  2.   
  3. path="/library"  
  4.    
  5.   
  6. docBase="D:\projects\library\deploy\target\library.war"  
  7.   
  8. reloadable="true"  
  9.   
  10. />  
  11. </span>  
②容器的总体设计

Context 还可以定义在父容器Host中,Host 不是必须的,但是要运行 wa序,就必须要 Host为 wa必有 web.xm文件, 这个文件的解析就需要 Hos了,如果要有多个 Hos就要定义一个 to容器 Engine 了。而 Engine 没有父容器了,一个 Engin代表 一个完整的 Servlet 引擎。

那么这些容器是如何协同工作的呢?先看一下它们之间的关系图:

 四个容器的关系图


当 Connector接受到一个连接请求时,将请求交给Container, Container如何处理这个请求的?这四个组件是怎么分工的,怎么 把请求传给特定的子容器的呢?又是如何将最终的请求交给 Servlet处理。下面是这个过程的时序图:

②Engine和Host  处理请求的时序图


这里看到了 Valve 是不是很熟悉,没错 Valve 的设计在其他框架中 也有用的,同样Pipeline的原理也基本是相似的,它是一个管道,Engine和 Host都会执行这个 Pipeline,您可以在这个管道上增加 任意的 ValveTomcat 会挨个执行这些Valve,而且四个组件都会 有自己的一套 Valve 集合。您怎么才能定义自己的Valve 呢?在server.xml 文件中可以添加,如给 Engine 和 Host 增加一个 Valve如下:

③Server.xml

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;"><Engine defaultHost="localhost" name="Catalina">  
  2.   
  3. <Valve   className="org.apache.catalina.valves.RequestDumperValve"/>  
  4.   
  5. ………  
  6.   
  7. <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true"  
  8.   
  9. xmlNamespaceAware="false"  xmlValidation="false">  
  10.   
  11. <Valve   className="org.apache.catalina.valves.FastCommonAccessLogValve"  
  12.   
  13. directory="logs" prefix="localhost_access_log." suffix=".txt"  
  14.   
  15. pattern="common" resolveHosts="false"/>  
  16.   
  17. …………  
  18.   
  19. </Host>  
  20.   
  21. </Engine>  
  22. </span>  

StandardEngineValve和 StandardHostValve是 Engine和 Host的默认的 Valve,它们是最后一个Valve 负责将请求传给它们的子 容器,以继续往下执行。

前面是 Engine和 Host容器的请求过程,下面看Context Wrapper 容器时如何处理请求的。下面是处理请求的时序图:

④Context 和wrapper  的处理请求时序图


从 Tomcat5 开始,子容器的路由放在了 request 中,request 中保 存了当前请求正在处理的 HostContext 和 wrapper

③Engine 容器

Engine容器比较简单,它只定义了一些基本的关联关系,接口类图如下:

①Engine 接口的类结构


它的标准实现类是StandardEngine,这个类注意一点就是 Engine没有父容器了,如果调用 setParen法时将会报错。添加子容器也 只能是 Host 类型的,代码如下:

②StandardEngine. addChild

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public void addChild(Container child) {  
  2.   
  3. if (!(child instanceof Host))  
  4.   
  5. throw new IllegalArgumentException  
  6.   
  7. (sm.getString("standardEngine.notHost"));  
  8.   
  9. super.addChild(child);  
  10.   
  11. }  
  12.   
  13. public void setParent(Container container) {  
  14.   
  15. throw new IllegalArgumentException  
  16.   
  17. (sm.getString("standardEngine.notParent"));  
  18.   
  19. }  
  20. </span>  

它的初始化方法也就是初始化和它相关联的组件,以及一些事件的监听。

④Host容器

Host是 Engine 的字容器,一个Host在 Engine中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是Context,它除了关联子容器外,还有就是保存一个主机应该有的信 息。

①Host 相关的类图

从上图中可以看出除了所有容器都继承的ContainerBase外, StandardHost还实现了Deployer 接口,上图清楚的列出了这个接口的主要方法,这些方法都是安装、展开、启动和结束每个web application

Deployer 接口的实现是 StandardHostDeployer这个类实现了的最要的几个方法,Host可以调用这些方法完成应用的部署等。

⑤Context容器

Context 表 Servlet 的 Context具备了 Servlet 行的基本环 境,理论上只要有Context 就能运行Servlet 了。简单的 Tomcat可以没有 Engine 和 Host

Context 最重要的功能就是管理它里面的Servlet实例,Servlet 实 例在 Context 中是以Wrapper 出现的,还有一点就是 Context 如 何才能找到正确的Servlet 来执行它呢?Tomcat5以前是通过一 个 Mapper 类来管理的,Tomcat5 以后这个功能被移到了request 中,在前面的时序图中就可以发现获取子容器都是通过request 分配的。

Context 备 Servlet 的运行环境是在 Star方法开始的,这个方法 的代码片段如下:

①StandardContext.start

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public synchronized void start() throws LifecycleException {  
  2.   
  3. ………  
  4.   
  5. if( !initialized ) {  
  6.   
  7. try {  
  8.   
  9. init();  
  10.   
  11. catch( Exception ex ) {  
  12.   
  13. throw new LifecycleException("Error initializaing ", ex);  
  14.   
  15. }  
  16.   
  17. }  
  18.   
  19. ………  
  20.   
  21. lifecycle.fireLifecycleEvent(BEFORE_START_EVENT,   null);  
  22.   
  23. setAvailable(false);  
  24.   
  25. setConfigured(false);  
  26.   
  27. boolean ok = true;  
  28.   
  29. File configBase = getConfigBase();  
  30.   
  31. if (configBase != null) {  
  32.   
  33. if (getConfigFile() == null) {  
  34.   
  35. File file = new File(configBase, getDefaultConfigFile());  
  36.   
  37. setConfigFile(file.getPath());  
  38.   
  39. try {  
  40.   
  41. File appBaseFile = new File(getAppBase());  
  42.   
  43. if (!appBaseFile.isAbsolute()) {  
  44.   
  45. appBaseFile = new File(engineBase(), getAppBase());  
  46.   
  47. }  
  48.   
  49. String appBase = appBaseFile.getCanonicalPath();  
  50.   
  51. String basePath =  
  52.   
  53. (new  File(getBasePath())).getCanonicalPath();  
  54.   
  55. if (!basePath.startsWith(appBase)) {  
  56.   
  57. Server server = ServerFactory.getServer();  
  58.   
  59. ((StandardServer)  server).storeContext(this);  
  60.   
  61. }  
  62.   
  63. catch (Exception e) {  
  64.   
  65. log.warn("Error storing config file", e);  
  66.   
  67. }  
  68.   
  69. else {  
  70.   
  71. try {  
  72.   
  73. String canConfigFile =  (new File(getConfigFile())).getCanonicalPath();  
  74. if (!canConfigFile.startsWith (configBase.getCanonicalPath())) {  
  75.   
  76. File file = new File(configBase, getDefaultConfigFile());  
  77.   
  78. if (copy(new File(canConfigFile), file)) {  
  79.    
  80. —————————————————————————————  
  81. setConfigFile(file.getPath());  
  82.   
  83. }  
  84.   
  85. }  
  86.   
  87. catch (Exception e) {  
  88.   
  89. log.warn("Error setting config file", e);  
  90.   
  91. }  
  92.   
  93. }  
  94.   
  95. }  
  96. ………  
  97.   
  98. Container children[] = findChildren();  
  99.   
  100. for (int i = 0; i < children.length; i++) {  
  101.   
  102. if (children[i] instanceof Lifecycle)  
  103.   
  104. ((Lifecycle)  children[i]).start();  
  105.   
  106. }  
  107.   
  108. if (pipeline instanceof Lifecycle)  
  109.   
  110. ((Lifecycle) pipeline).start();  
  111.   
  112. ………  
  113.   
  114. }  
  115. </span>  

它主要是设置各种资源属性和管理组件,还有非常重要的就是启动子容器和 Pipeline

我们知道 Contex的配置文件中有个 reloadable 属性,如下面配置:

②Server.xml

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;"><Context  
  2.   
  3. path="/library"  
  4.    
  5. —————————————————————————————  
  6. docBase="D:\projects\library\deploy\target\library.war"  
  7.   
  8. reloadable="true"  
  9.   
  10. />  
  11. </span>  

当这个 reloadable 设为 true 时,war被修改后 Tomcat 会自动的重新加载这个应用。如何做到这点的呢这个功能是在StandardContext的 backgroundProcess 方法中实现的,这个方法的代码如下:

③StandardContext. backgroundProcess

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public void backgroundProcess() {  
  2.   
  3. if (!started) return;  
  4.   
  5. count = (count + 1) % managerChecksFrequency;  
  6.   
  7. if ((getManager() != null) && (count == 0)) {  
  8.   
  9. try {  
  10.   
  11. getManager().backgroundProcess();  
  12.   
  13. catch ( Exception x ) {  
  14.   
  15. log.warn("Unable to perform background process on manager",x);  
  16.   
  17. }  
  18.   
  19. }  
  20.   
  21. if (getLoader() != null) {  
  22.   
  23. if (reloadable && (getLoader().modified())) {  
  24.   
  25. try {  
  26.   
  27. Thread.currentThread().setContextClassLoader  
  28.   
  29. (StandardContext.class.getClassLoader());  
  30.   
  31. reload();  
  32.   
  33. finally {  
  34.   
  35. if (getLoader() != null) {  
  36.   
  37. Thread.currentThread().setContextClassLoader  
  38.    
  39.   
  40. (getLoader().getClassLoader());  
  41.   
  42. }  
  43.   
  44. }  
  45.   
  46. }  
  47.   
  48. if (getLoader() instanceof WebappLoader) {  
  49.   
  50. ((WebappLoader)  getLoader()).closeJARs(false);  
  51.   
  52. }  
  53.   
  54. }  
  55.   
  56. }  
  57. </span>  

它会调用 reload 方法,而 reload方法会先调用 stop方法然后再调用 Start 方法,完成Context 的一次重新加载。可以看出执行reload方法的条件是reloadable 为 true 和应用被修改,那么这个backgroundProcess 方法是怎么被调用的呢?

这个方法是在 ContainerBase 类中定义的内部类ContainerBackgroundProcessor被周期调用的,这个类是运行在一个后台线程中,它会周期的执行 ru法,它的 ru方法会周期调 用所有容器的 backgroundProcess 方法,因为所有容器都会继承ContainerBase类,所以所有容器都能够在backgroundProcess 方 法中定义周期执行的事件。

⑥Wrapper容器

Wrapper 代表一个Servlet,它负责管理一个 Servlet,包括的 Servlet的装载、初始化、执行以及资源回收。Wrapper是最底层的 容器,它没有子容器了,所以调用它的addChild 将会报错。

Wrappe的实现类是 StandardWrapperStandardWrapper 实现 了拥有一个 Servlet初始化信息的ServletConfig,由此看出 StandardWrapper 将直接和Servlet的各种信息打交道。

下面看一下非常重要的一个方法loadServlet,代码片段如下:

①StandardWrapper.loadServlet
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public synchronized Servlet loadServlet() throws ServletException {  
  2.   
  3. ………  
  4.   
  5. Servlet servlet;  
  6.   
  7. try {  
  8.   
  9. ………  
  10.   
  11. ClassLoader classLoader = loader.getClassLoader();  
  12.   
  13. ………  
  14.   
  15. Class classClass = null;  
  16.   
  17. ………  
  18.   
  19. servlet = (Servlet) classClass.newInstance();  
  20.   
  21. if ((servlet instanceof ContainerServlet) &&  
  22.   
  23. (isContainerProvidedServlet(actualClass)  ||  
  24.   
  25. ((Context)getParent()).getPrivileged() )) {  
  26.   
  27. ((ContainerServlet)  servlet).setWrapper(this);  
  28.   
  29. }  
  30.    
  31.   
  32. classLoadTime=(int) (System.currentTimeMillis() -t1);  
  33.   
  34. try {  
  35.   
  36.   
  37. instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);  
  38.   
  39. if( System.getSecurityManager() != null) {  
  40.   
  41. Class[] classType = new Class[]{ServletConfig.class};  
  42.   
  43. Object[] args = new Object[]{((ServletConfig)facade)};  
  44.   
  45. SecurityUtil.doAsPrivilege("init",servlet,classType,args);  
  46.   
  47. else {  
  48.   
  49. servlet.init(facade);  
  50.   
  51. }  
  52.   
  53. if ((loadOnStartup >= 0) && (jspFile != null)) {  
  54.   
  55. ………  
  56.   
  57. if( System.getSecurityManager() != null) {  
  58.   
  59. Class[] classType = new Class[]{ServletRequest.class,  
  60.   
  61. ServletResponse.class};  
  62.   
  63. Object[] args = new Object[]{req, res};  
  64.   
  65. SecurityUtil.doAsPrivilege("service",servlet,classType,args);  
  66.   
  67. else {  
  68.   
  69. servlet.service(req, res);  
  70.   
  71. }  
  72.   
  73. }  
  74.   
  75.   
  76. instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet);  
  77.   
  78. ………  
  79.   
  80. return servlet;  
  81.   
  82. }  
  83. </span>  

它基本上描述了对Servlet 的操作,当装载了Servlet后就会调用Servlet的 init方法,同时会传一个StandardWrapperFacade对象给Servlet,这个对象包装了StandardWrapper,ServletConfig 与它们的关系图如下:

②ServletConf 与StandardWrapperFacade、StandardWrapper的关系


Servlet可以获得的信息都在StandardWrapperFacade封装,这些信息又是StandardWrappe对象中拿到的。所以 Servlet 可以通 过 ServletConfig 拿到有限的容器的信息。

当 Servlet 被初始化完成后,就等着 StandardWrapperValve 去调用 它的 service 方法了,调用 service 法之前要调用 Servlet 有的 filter

Tomcat中其它组件

Tomcat 还有其它重要的组件,如安全组件security、logger 日 志组件、session、mbeans、naming 等其它组件。这些组件共同为Connector和 Container 提供必要的服务。

业务思想

关于Tomcat服务器的了解,算是很长时间的了解了,很好用。本博文中关于Tomcat系统架构的学习和总结,算是个人的理解,写一写总结总感觉很有必要,收获颇多。多加使用,方感颇深。大家有什么好的理解,欢迎交流!


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值