粗浅看 Tomcat系统架构分析
原文出处: 吴士龙 http://www.importnew.com/21112.html
Tomcat的结构很复杂,但是Tomcat也非常的模块化,找到了Tomcat最核心的模块,就抓住了Tomcat的“七寸”。
整体结构
Tomcat 总体结构图
从上图中可以看出Tomcat的心脏是两个组件:Connector 和 Container,关于这两个组件将在后面详细介绍。Connector 组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计的本身,而且和不同的应用场景也十分相关,所以一个Container 可以选择对应多个Connector。多个Connector和一个Container 就形成了一个Service,Service 的概念大家都很熟悉了,有了Service 就可以对外提供服务了,但是Service还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非Server莫属了。所以整个Tomcat的生命周期由Server控制。
以Service 作为“婚姻”
我们将 Tomcat 中 Connector、Container 作为一个整体比作一对情 侣的话,Connector主要负责对外交流,可以比作为 Boy,Container 主要处理 Connector 接受的请求,主要是处理内部事务,可以比作为 Girl。那么这个 Service就是连接这对男女的结婚证了。是Service将它们连接在一起,共同组成一个家庭。当然要组成一个家庭还要很多其它的元素。
说白了,Service 只是在Connector 和 Container外面多包一层,把它们组装在一起,向外面提供服务,一个Service可以设置多个Connector,但是只能有一个 Container 容器。这个 Service 接口的 方法列表如下:
①Service接口
从 Service接口中定义的方法中可以看出,它主要是为了关联Connector和 Container,同时会初始化它下面的其它组件,注意接 口中它并没有规定一定要控制它下面的组件的生命周期。所有组件的 生命周期在一个 Lifecycle 的接口中控制,这里用到了一个重要的设 计模式,关于这个接口将在后面介绍。
Tomcat 中 Service接口的标准实现类是StandardService它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控 制它下面的组件的生命周期了。StandardService 类结构图如下:
②StandardService的类结构图
从上图中可以看出除了 Service接口的方法的实现以及控制组件生命周期的 Lifecycle 接口的实现,还有几个方法是用于在事件监听的 方法的实现,不仅是这个 Service 组件,Tomcat 中其它组件也同样 有这几个方法,这也是一个典型的设计模式,将在后面介绍。
下面看一下 StandardService 中主要的几个方法实现的代码,下面是setContainer和addConnector 方法的源码:
③StandardService. SetContainer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
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) { ; } } support.firePropertyChange( "container" , oldContainer, this .container); ————————————————————————————— } |
这段代码很简单,其实就是先判断当前的这个 Service 有没有已经关 联了 Container,如果已经关联了,那么去掉这个关联关系——oldContainer.setService(null)。如果这个oldContainer 已经被启动 了,结束它的生命周期。然后再替换新的关联、再初始化并开始这个新的 Container 的生命周期。最后将这个过程通知感兴趣的事件监听程序。这里值得注意的地方就是,修改Container 时要将新的 Container关联到每个Connector,还好Container 和 Connector 没有双向关联,不然这个关联关系将会很难维护。
④StandardService. addConnector
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public void addConnector(Connector connector) { synchronized (connectors) { connector.setContainer( this .container); connector.setService( this ); 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) { e.printStackTrace(System.err); } } if (started && (connector instanceof Lifecycle)) { try { ((Lifecycle) connector).start(); } catch (LifecycleException e) { ; } } support.firePropertyChange( "connector" , null , connector); } } |
上面是 addConnector 方法,这个方法也很简单,首先是设置关联关 系,然后是初始化工作,开始新的生命周期。这里值得一提的是,注 意 Connector 用的是数组而不是 List集合,这个从性能角度考虑可 以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始 就分配一个固定大小的数组,它这里的实现机制是:重新创建一个当 前大小的数组对象,然后将原来的数组对象 copy 到新的数组中,这 种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后 拿来借鉴。
最新的 Tomcat6 中 StandardService也基本没有变化,但是从Tomcat5 开始Service、Server 和容器类都继承了MBeanRegistration接口,Mbeans 的管理更加合理。
以 Server 为“居”
前面说一对情侣因为 Service 而成为一对夫妻,有了能够组成一个家 庭的基本条件,但是它们还要有个实体的家,这是它们在社会上生存 之本,有了家它们就可以安心的为人民服务了,一起为社会创造财富。
Server要完成的任务很简单,就是要能够提供一个接口让其它程序能够访问到这个Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service。还有其它的一些次要的任务,如您住在这个地方要向当 地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么 的。
Server的类结构图如下:
①Server的类结构图
它的标准实现类 StandardServer 实现了上面这些方法,同时也实现 了Lifecycle、MbeanRegistration 两个接口的所有方法,下面主要看 一下 StandardServer重要的一个方法 addService的实现:
②StandardServer.addService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
public void addService(Service service) { service.setServer( this ); synchronized (services) { Service results[] = new Service[services.length + 1 ]; System.arraycopy(services, 0 , results, 0 , services.length); results[services.length] = service; services = results; if (initialized) { try { service.initialize(); } catch (LifecycleException e) { e.printStackTrace(System.err); } } if (started && (service instanceof Lifecycle)) { try { ((Lifecycle) service).start(); } catch (LifecycleException e) { ; } } support.firePropertyChange( "service" , null , service); } } |
从上面第一句就知道了 Service和 Server是相互关联的,Server也是和 Service 管理 Connector 一样管理它,也是将 Service 放在 一个数组中,后面部分的代码也是管理这个新加进来的 Service 的生 命周期。Tomcat6 中也是没有什么变化的。
组件的生命线“Lifecycle”
前面一直在说 Service 和 Server 管理它下面组件的生命周期,那它 们是如何管理的呢?
Tomcat 中组件的生命周期是通过Lifecycle 接口来控制的,组件只 要继承这个接口并实现其中的方法就可以统一被拥有它的组件控制 了,这样一层一层的直到一个最高级的组件就可以控制 Tomcat 中 所有组件的生命周期,这个最高的组件就是 Server,而控制Server的是 Startup,也就是您启动和关闭Tomcat。
下面是 Lifecycle 接口的类结构图:
①Lifecycle类结构图
除了控制生命周期的 Start 和 Stop 方法外还有一个监听机制,在生命周期开始和结束的时候做一些额外的操作。这个机制在其它的框架中也被使用,如在Spring 中。关于这个设计模式会在后面介绍。
Lifecycle接口的方法的实现都在其它组件中,就像前面中说的,组件的生命周期由包含它的父组件控制,所以它的 Start 方法自然就是调用它下面的组件的 Start 方法,Stop 方法也是一样。如在 Server 中 Start 方法就会调用Service组件的 Start方法,Server 的 Start方法代码如下:
②StandardServer.Start
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public void start() throws LifecycleException { if (started) { log.debug(sm.getString( "standardServer.start.started" )); return ; } lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null ); lifecycle.fireLifecycleEvent(START_EVENT, null ); started = true ; synchronized (services) { for ( int i = 0 ; i < services.length; i++) { if (services[i] instanceof Lifecycle) ((Lifecycle) services[i]).start(); } } lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null ); } |
监听的代码会包围Service组件的启动过程,就是简单的循环启动所有Service组件的Start方法,但是所有Service必须要实现Lifecycle接口,这样做会更加灵活。
Server的 Stop 方法代码如下:
③StandardServer.Stop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public void stop() throws LifecycleException { if (!started) return ; lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null ); lifecycle.fireLifecycleEvent(STOP_EVENT, null ); started = false ; for ( int i = 0 ; i < services.length; i++) { if (services[i] instanceof Lifecycle) ((Lifecycle) services[i]).stop(); } lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null ); } |
它所要做的事情也和Start方法差不多。
Connector组件
Connector组件是Tomcat中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的tcp连接请求,创建个Request 和处理这个请求并把产生的Request 和 Response对象传给处理这个请求的线程,处理这个请求的线程就是Container 组件要做的事了。
由于这个过程比较复杂,大体的流程可以用下面的顺序图来解释:
①Connector处理一次请求顺序图
Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是可以选择替换的。Connector 最重要的功能就是接收连接请求然后分配线 程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。Tomcat5将这个过程更加细化,它将 Connector划分成 Connector、Processor、Protocol, 另外Coyote也定义自己的Request 和 Response对象。
下面主要看一下 Tomcat 中如何处理多线程的连接请求,先看一下Connector的主要类图:
② Connector的主要类图
看一下HttpConnector的Start 方法:
③HttpConnector.Start
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public void start() throws LifecycleException { if (started) throw new LifecycleException (sm.getString( "httpConnector.alreadyStarted" )); threadName = "HttpConnector[" + port + "]" ; lifecycle.fireLifecycleEvent(START_EVENT, null ); started = true ; threadStart(); while (curProcessors < minProcessors) { if ((maxProcessors > 0 ) && (curProcessors >= maxProcessors)) break ; HttpProcessor processor = newProcessor(); recycle(processor); } } |
threadStart()执行就会进入等待请求的状态,直到一个新的请求到来才会激活它继续执行,这个激活是在HttpProcessor 的 assign 方法中,这个方法是代码如下 :
④ HttpProcessor.assign
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
synchronized void assign(Socket socket) { while (available) { try { wait(); } catch (InterruptedException e) { ————————————————————————————— } } this .socket = socket; available = true ; notifyAll(); if ((debug >= 1 ) && (socket != null )) log( " An incoming request is being assigned" ); } |
创建 HttpProcessor 对象是会把 available 设为 false,所以当请求 到来时不会进入 while循环,将请求的socket 赋给当期处理的 socket,并将 available设为true,当 available设为true 是 HttpProcessor的 run方法将被激活,接下去将会处理这次请求。
Run方法代码如下:
⑤HttpProcessor.Run
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public void run() { while (!stopped) { Socket socket = await(); if (socket == null ) continue ; try { process(socket); } catch (Throwable t) { log( "process.invoke" , t); } connector.recycle( this ); } ————————————————————————————— synchronized (threadSync) { threadSync.notifyAll(); } } |
解析 socket 的过程在 process 方法中,process 方法的代码片段如 下:
⑥HttpProcessor.process
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
private void process(Socket socket) { boolean ok = true ; boolean finishResponse = true ; SocketInputStream input = null ; OutputStream output = null ; try { input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize()); } catch (Exception e) { log( "process.create" , e); ok = false ; } keepAlive = true ; while (!stopped && ok && keepAlive) { finishResponse = true ; try { request.setStream(input); request.setResponse(response); output = socket.getOutputStream(); response.setStream(output); response.setRequest(request); ((HttpServletResponse) response.getResponse()) ————————————————————————————— .setHeader( "Server" , SERVER_INFO); } catch (Exception e) { log( "process.create" , e); ok = false ; } try { if (ok) { parseConnection(socket); parseRequest(input, output); if (!request.getRequest().getProtocol().startsWith( "HTTP/0" )) parseHeaders(input); if (http11) { ackRequest(output); if (connector.isChunkingAllowed()) response.setAllowChunking( true ); } } try { ((HttpServletResponse) response).setHeader ( "Date" , FastHttpDateFormat.getCurrentDate()); if (ok) { connector.getContainer().invoke(request, response); } } try { shutdownInput(input); socket.close(); } catch (IOException e) { ; } catch (Throwable e) { log( "process.invoke" , e); } socket = null ; } |
当 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
1
2
3
4
5
6
7
8
9
|
< Context path = "/library" docBase = "D:\projects\library\deploy\target\library.war" reloadable = "true" /> |
②容器的总体设计
Context 还可以定义在父容器Host中,Host 不是必须的,但是要运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件, 这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个 top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表 一个完整的 Servlet 引擎。
那么这些容器是如何协同工作的呢?先看一下它们之间的关系图:
① 四个容器的关系图
当 Connector接受到一个连接请求时,将请求交给Container, Container是如何处理这个请求的?这四个组件是怎么分工的,怎么 把请求传给特定的子容器的呢?又是如何将最终的请求交给 Servlet处理。下面是这个过程的时序图:
②Engine和Host 处理请求的时序图
这里看到了 Valve 是不是很熟悉,没错 Valve 的设计在其他框架中 也有用的,同样Pipeline的原理也基本是相似的,它是一个管道,Engine和 Host都会执行这个 Pipeline,您可以在这个管道上增加 任意的 Valve,Tomcat 会挨个执行这些Valve,而且四个组件都会 有自己的一套 Valve 集合。您怎么才能定义自己的Valve 呢?在server.xml 文件中可以添加,如给 Engine 和 Host 增加一个 Valve如下:
③Server.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<Engine defaultHost= "localhost" name= "Catalina" > <Valve className= "org.apache.catalina.valves.RequestDumperValve" /> ……… <Host appBase= "webapps" autoDeploy= "true" name= "localhost" unpackWARs= "true" xmlNamespaceAware= "false" xmlValidation= "false" > <Valve className= "org.apache.catalina.valves.FastCommonAccessLogValve" directory= "logs" prefix= "localhost_access_log." suffix= ".txt" pattern= "common" resolveHosts= "false" /> ………… </Host> </Engine> |
StandardEngineValve和 StandardHostValve是 Engine和 Host的默认的 Valve,它们是最后一个Valve 负责将请求传给它们的子 容器,以继续往下执行。
前面是 Engine和 Host容器的请求过程,下面看Context 和Wrapper 容器时如何处理请求的。下面是处理请求的时序图:
④Context 和wrapper 的处理请求时序图
从 Tomcat5 开始,子容器的路由放在了 request 中,request 中保 存了当前请求正在处理的 Host、Context 和 wrapper。
③Engine 容器
Engine容器比较简单,它只定义了一些基本的关联关系,接口类图如下:
①Engine 接口的类结构
它的标准实现类是StandardEngine,这个类注意一点就是 Engine没有父容器了,如果调用 setParent 方法时将会报错。添加子容器也 只能是 Host 类型的,代码如下:
②StandardEngine. addChild
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public void addChild(Container child) { if (!(child instanceof Host)) throw new IllegalArgumentException (sm.getString( "standardEngine.notHost" )); super .addChild(child); } public void setParent(Container container) { throw new IllegalArgumentException (sm.getString( "standardEngine.notParent" )); } |
它的初始化方法也就是初始化和它相关联的组件,以及一些事件的监听。
④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 的运行环境是在 Start 方法开始的,这个方法 的代码片段如下:
①StandardContext.start
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
public synchronized void start() throws LifecycleException { ……… if ( !initialized ) { try { init(); } catch ( Exception ex ) { throw new LifecycleException( "Error initializaing " , ex); } } ……… lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null ); setAvailable( false ); setConfigured( false ); boolean ok = true ; File configBase = getConfigBase(); if (configBase != null ) { if (getConfigFile() == null ) { File file = new File(configBase, getDefaultConfigFile()); setConfigFile(file.getPath()); try { File appBaseFile = new File(getAppBase()); if (!appBaseFile.isAbsolute()) { appBaseFile = new File(engineBase(), getAppBase()); } String appBase = appBaseFile.getCanonicalPath(); String basePath = ( new File(getBasePath())).getCanonicalPath(); if (!basePath.startsWith(appBase)) { Server server = ServerFactory.getServer(); ((StandardServer) server).storeContext( this ); } } catch (Exception e) { log.warn( "Error storing config file" , e); } } else { try { String canConfigFile = ( new File(getConfigFile())).getCanonicalPath(); if (!canConfigFile.startsWith (configBase.getCanonicalPath())) { File file = new File(configBase, getDefaultConfigFile()); if (copy( new File(canConfigFile), file)) { ————————————————————————————— setConfigFile(file.getPath()); } } } catch (Exception e) { log.warn( "Error setting config file" , e); } } } ……… Container children[] = findChildren(); for ( int i = 0 ; i < children.length; i++) { if (children[i] instanceof Lifecycle) ((Lifecycle) children[i]).start(); } if (pipeline instanceof Lifecycle) ((Lifecycle) pipeline).start(); ……… } |
它主要是设置各种资源属性和管理组件,还有非常重要的就是启动子容器和 Pipeline。
我们知道 Context 的配置文件中有个 reloadable 属性,如下面配置:
②Server.xml
1
2
3
4
5
6
7
8
9
10
|
< Context path = "/library" ————————————————————————————— docBase = "D:\projects\library\deploy\target\library.war" reloadable = "true" /> |
当这个 reloadable 设为 true 时,war被修改后 Tomcat 会自动的重新加载这个应用。如何做到这点的呢? 这个功能是在StandardContext的 backgroundProcess 方法中实现的,这个方法的代码如下:
③StandardContext. backgroundProcess
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
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 (getLoader().getClassLoader()); } } } if (getLoader() instanceof WebappLoader) { ((WebappLoader) getLoader()).closeJARs( false ); } } } |
它会调用 reload 方法,而 reload方法会先调用 stop方法然后再调用 Start 方法,完成Context 的一次重新加载。可以看出执行reload方法的条件是reloadable 为 true 和应用被修改,那么这个backgroundProcess 方法是怎么被调用的呢?
这个方法是在 ContainerBase 类中定义的内部类ContainerBackgroundProcessor被周期调用的,这个类是运行在一个后台线程中,它会周期的执行 run 方法,它的 run 方法会周期调 用所有容器的 backgroundProcess 方法,因为所有容器都会继承ContainerBase类,所以所有容器都能够在backgroundProcess 方 法中定义周期执行的事件。
⑥Wrapper容器
Wrapper 代表一个Servlet,它负责管理一个 Servlet,包括的 Servlet的装载、初始化、执行以及资源回收。Wrapper是最底层的 容器,它没有子容器了,所以调用它的addChild 将会报错。
Wrapper 的实现类是 StandardWrapper,StandardWrapper 还实现 了拥有一个 Servlet初始化信息的ServletConfig,由此看出 StandardWrapper 将直接和Servlet的各种信息打交道。
下面看一下非常重要的一个方法loadServlet,代码片段如下:
①StandardWrapper.loadServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
public synchronized Servlet loadServlet() throws ServletException { ……… Servlet servlet; try { ……… ClassLoader classLoader = loader.getClassLoader(); ……… Class classClass = null ; ……… servlet = (Servlet) classClass.newInstance(); if ((servlet instanceof ContainerServlet) && (isContainerProvidedServlet(actualClass) || ((Context)getParent()).getPrivileged() )) { ((ContainerServlet) servlet).setWrapper( this ); } classLoadTime=( int ) (System.currentTimeMillis() -t1); try { instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet); if ( System.getSecurityManager() != null ) { Class[] classType = new Class[]{ServletConfig. class }; Object[] args = new Object[]{((ServletConfig)facade)}; SecurityUtil.doAsPrivilege( "init" ,servlet,classType,args); } else { servlet.init(facade); } if ((loadOnStartup >= 0 ) && (jspFile != null )) { ……… if ( System.getSecurityManager() != null ) { Class[] classType = new Class[]{ServletRequest. class , ServletResponse. class }; Object[] args = new Object[]{req, res}; SecurityUtil.doAsPrivilege( "service" ,servlet,classType,args); } else { servlet.service(req, res); } } instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet); ……… return servlet; } |
它基本上描述了对Servlet 的操作,当装载了Servlet后就会调用Servlet的 init方法,同时会传一个StandardWrapperFacade对象给Servlet,这个对象包装了StandardWrapper,ServletConfig 与它们的关系图如下:
②ServletConf 与StandardWrapperFacade、StandardWrapper的关系
Servlet可以获得的信息都在StandardWrapperFacade封装,这些信息又是在StandardWrapper 对象中拿到的。所以 Servlet 可以通 过 ServletConfig 拿到有限的容器的信息。
当 Servlet 被初始化完成后,就等着 StandardWrapperValve 去调用 它的 service 方法了,调用 service 方法之前要调用 Servlet 所有的 filter。
Tomcat中其它组件
Tomcat 还有其它重要的组件,如安全组件security、logger 日 志组件、session、mbeans、naming 等其它组件。这些组件共同为Connector和 Container 提供必要的服务。
业务思想
关于Tomcat服务器的了解,算是很长时间的了解了,很好用。本博文中关于Tomcat系统架构的学习和总结,算是个人的理解,写一写总结总感觉很有必要,收获颇多。多加使用,方感颇深。大家有什么好的理解,欢迎交流!
粗浅看 Tomcat系统架构分析的更多相关文章
- Tomcat系统架构分析
Tomcat系统架构分析 关于这边blog呢,实际开发中并不会用到,但是我觉得还是很有必要认真的写一下.毕竟我们每天在本地撸码的时候使用的就是tomcat来做web服务器.一个常识就是说我们本地在to ...
- 【转】tomcat系统架构分析
https://blog.csdn.net/wsl211511/article/details/51622991
- Tomcat 系统架构与设计模式
Tomcat 系统架构与设计模式,第 1 部分: 工作原理 这个分为两个部分的系列文章将研究 Apache Tomcat 的系统架构以及其运用的很多经典设计模式.本文是第 1 部分,将主要从 Tomc ...
- Tomcat 系统架构与设计模式,第 2 部分: 设计模式分析(转载)
简介: 这个分为两个部分的系列文章研究了 Apache Tomcat 服务器的系统架构以及其运用的很多经典设计模式.第 1 部分 分析了 Tomcat 的工作原理,第 2 部分将分析 Tomcat 中 ...
- tomcat 系统架构与设计模式 第二部分 设计模式 转
Tomcat 系统架构与设计模式,第 2 部分: 设计模式分析 许 令波, Java 开发工程师, 淘宝网 许令波,现就职于淘宝网,是一名 Java 开发工程师.对大型互联网架构设计颇感兴趣,并对一些 ...
- tomcat 系统架构与设计模式 第一部分 系统架构工作原理 转
Tomcat 系统架构与设计模式,第 1 部分: 工作原理 许 令波, Java 开发工程师, 淘宝网 许令波,现就职于淘宝网,是一名 Java 开发工程师.对大型互联网架构设计颇感兴趣,并对一些开源 ...
- 【Tomcat】Tomcat 系统架构与设计模式,第 2 部分: 设计模式分析
这个分为两个部分的系列文章研究了 Apache Tomcat 服务器的系统架构以及其运用的很多经典设计模式.第 1 部分 分析了 Tomcat 的工作原理,第 2 部分将分析 Tomcat 中运用的许 ...
- Tomcat 系统架构与设计模式之二
Tomcat 系统架构与设计模式,第 2 部分: 设计模式分析 来自:http://www.ibm.com/developerworks/cn/java/j-lo-tomcat2/ 这个分为两个部分的 ...
- Tomcat 系统架构与设计模式之一
Tomcat 系统架构与设计模式,第 1 部分: 工作原理 来自:http://www.ibm.com/developerworks/cn/java/j-lo-tomcat1/index.html 这 ...
随机推荐
- java-day09
接口 就是一种公共规范标准,只要符合规范标准,就可以大家通用,多个类的公告规范,引用数据类型 格式 public interface 接口名称{} 接口都能定义抽象方法 public abstract ...
- Java中9大内置基本数据类型Class实例和数组的Class实例
1.Java中9大内置几本数据类型: 对于对象来说,可以直接使用对象.getClass()或者Class.forName(className);.类名.class都可以获取Class实例. 但是我们的 ...
- 如何使用Spark大规模并行构建索引
使用Spark构建索引非常简单,因为spark提供了更高级的抽象rdd分布式弹性数据集,相比以前的使用Hadoop的MapReduce来构建大规模索引,Spark具有更灵活的api操作,性能更高,语法 ...
- google浏览器插件开发
官方开发文档 随便找个文件夹新建插件所需文件 目录结构 pluginName manifest.json(必须) 一个manifest文件 *.htm ...
- Mapped Statements collection does not contain value for xxx.xxx 错误原因&解决方案
先贴出详细的报错信息 2019-11-05 10:10:00 [executor-1] ERROR [org.quartz.core.JobRunShell:225] - Job DEFAULT.ef ...
- NBIS指纹特征提取与匹配软件使用
1. docker 创建虚拟centos 环境 2. docker 安装wget 工具 3.wget下载源代码 wget http://nigos.nist.gov:8080/nist/nbis/nb ...
- windows安装apache系统中无apache2服务解决方案
一直都是用WIN开发PHP,今天有用户反映SHUGUANG CMS在APACHE+PHP中不能正常运行,只好自己机器配置个环境测试(http://xz.8682222.com)遇到点小问题,搜索相关资 ...
- ConcurrentHashMap线程安全的具体实现方式/底层具体实现
1. jdk1.7以及之前 ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成. 通俗的话讲:就是首先将数据分为一段一段的存储,然后给每一段数据配一 ...
- vue的无缝滚动插件vue-seamless-scroll的使用
https://chenxuan0000.github.io/component-document/index_prod.html#/component/seamless-others 在vue环境下 ...
- ftp文件上传下载命令
介绍:从本地以用户wasqry登录的机器1*.1**.21.67上通过ftp远程登录到ftp服务器上,登录用户名是lte****,以下为使用该连接做的实验. 查看远程ftp服务器上用户lte**** ...