Tomcat 总体结构
Tomcat 的结构很复杂,但是 Tomcat 也非常的模块化,找到了 Tomcat 最核心的模块,您就抓住了 Tomcat 的“七寸”。下面是 Tomcat 的总体结构图:
图 1.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 接口的方法列表如下:
图 2. Service 接口
从 Service 接口中定义的方法中可以看出,它主要是为了关联 Connector 和 Container,同时会初始化它下面的其它组件,注意接口中它并没有规定一定要控制它下面的组件的生命周期。所有组件的生命周期在一个 Lifecycle 的接口中控制,这里用到了一个重要的设计模式,关于这个接口将在后面介绍。
Tomcat 中 Service 接口的标准实现类是 StandardService 它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控制它下面的组件的生命周期了。StandardService 类结构图如下:
图 3. StandardService 的类结构图
从上图中可以看出除了 Service 接口的方法的实现以及控制组件生命周期的 Lifecycle 接口的实现,还有几个方法是用于在事件监听的方法的实现,不仅是这个 Service 组件,Tomcat 中其它组件也同样有这几个方法,这也是一个典型的设计模式,将在后面介绍。
下面看一下 StandardService 中主要的几个方法实现的代码,下面是 setContainer 和 addConnector 方法的源码:
清单 1. 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 |
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 没有双向关联,不然这个关联关系将会很难维护。
清单 2. 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 |
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 的类结构图如下:
图 4. Server 的类结构图
它的标准实现类 StandardServer 实现了上面这些方法,同时也实现了 Lifecycle、MbeanRegistration 两个接口的所有方法,下面主要看一下 StandardServer 重要的一个方法 addService 的实现:
清单 3. 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 |
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 接口的类结构图:
图 5. Lifecycle 类结构图
除了控制生命周期的 Start 和 Stop 方法外还有一个监听机制,在生命周期开始和结束的时候做一些额外的操作。这个机制在其它的框架中也被使用,如在 Spring 中。关于这个设计模式会在后面介绍。
Lifecycle 接口的方法的实现都在其它组件中,就像前面中说的,组件的生命周期由包含它的父组件控制,所以它的 Start 方法自然就是调用它下面的组件的 Start 方法,Stop 方法也是一样。如在 Server 中 Start 方法就会调用 Service 组件的 Start 方法,Server 的 Start 方法代码如下:
清单 4. StandardServer.Start
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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 方法代码如下:
清单 5. StandardServer.Stop
1 2 3 4 5 6 7 8 9 10 11 12 |
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); } |
---|---|
Connector 组件
Connector 组件是 Tomcat 中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程,处理这个请求的线程就是 Container 组件要做的事了。
由于这个过程比较复杂,大体的流程可以用下面的顺序图来解释:
图 6. Connector 处理一次请求顺序图
(查看清晰大图)
Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是可以选择替换的。Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。Tomcat5 将这个过程更加细化,它将 Connector 划分成 Connector、Processor、Protocol, 另外 Coyote 也定义自己的 Request 和 Response 对象。
下面主要看一下 Tomcat 中如何处理多线程的连接请求,先看一下 Connector 的主要类图:
图 7. Connector 的主要类图
(查看清晰大图)
看一下 HttpConnector 的 Start 方法:
清单 6. HttpConnector.Start
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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 方法中,这个方法是代码如下 :
清单 7. HttpProcessor.assign
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 方法代码如下:
清单 8. HttpProcessor.Run
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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 方法的代码片段如下:
清单 9. 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 |
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 通常就是对应下面这个配置:
清单 10. Server.xml
1 2 3 4 5 |
<``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 引擎。
那么这些容器是如何协同工作的呢?先看一下它们之间的关系图:
图 8. 四个容器的关系图
(查看清晰大图)
当 Connector 接受到一个连接请求时,将请求交给 Container,Container 是如何处理这个请求的?这四个组件是怎么分工的,怎么把请求传给特定的子容器的呢?又是如何将最终的请求交给 Servlet 处理。下面是这个过程的时序图:
图 9. Engine 和 Host 处理请求的时序图
(查看清晰大图)
这里看到了 Valve 是不是很熟悉,没错 Valve 的设计在其他框架中也有用的,同样 Pipeline 的原理也基本是相似的,它是一个管道,Engine 和 Host 都会执行这个 Pipeline,您可以在这个管道上增加任意的 Valve,Tomcat 会挨个执行这些 Valve,而且四个组件都会有自己的一套 Valve 集合。您怎么才能定义自己的 Valve 呢?在 server.xml 文件中可以添加,如给 Engine 和 Host 增加一个 Valve 如下:
清单 11. Server.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<``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 容器时如何处理请求的。下面是处理请求的时序图:
图 10. Context 和 wrapper 的处理请求时序图
(查看清晰大图)
从 Tomcat5 开始,子容器的路由放在了 request 中,request 中保存了当前请求正在处理的 Host、Context 和 wrapper。
Engine 容器
Engine 容器比较简单,它只定义了一些基本的关联关系,接口类图如下:
图 11. Engine 接口的类结构
它的标准实现类是 StandardEngine,这个类注意一点就是 Engine 没有父容器了,如果调用 setParent 方法时将会报错。添加子容器也只能是 Host 类型的,代码如下:
清单 12. StandardEngine. addChild
1 2 3 4 5 6 7 8 9 10 11 |
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 相关的类关联图:
图 12. 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 方法开始的,这个方法的代码片段如下:
清单 13. 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 |
public synchronized void start() throws LifecycleException { ``……… ``if( !initialized ) { ``try { ``init(); ``} catch( Exception ex ) { ``throw new LifecycleException("Error initializaing ", ex); ``} ``} `<br /> ………`<br />` lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);<br /> setAvailable(false);`<br />` setConfigured(false);<br /> boolean ok = true;`<br />` File configBase = getConfigBase();<br /> if (configBase != null) {`<br />` if (getConfigFile() == null) {<br /> File file = new File(configBase, getDefaultConfigFile());`<br />` setConfigFile(file.getPath());<br /> try {`<br />` File appBaseFile = new File(getAppBase());<br /> if (!appBaseFile.isAbsolute()) {`<br />` appBaseFile = new File(engineBase(), getAppBase());<br /> }`<br />` String appBase = appBaseFile.getCanonicalPath();<br /> String basePath = `<br />` (new File(getBasePath())).getCanonicalPath();<br /> if (!basePath.startsWith(appBase)) {`<br />` Server server = ServerFactory.getServer();<br /> ((StandardServer) server).storeContext(this);`<br />` }<br /> } catch (Exception e) {`<br />` log.warn(“Error storing config file”, e);<br /> }`<br />` } else {<br /> try {`<br />` String canConfigFile = (new File(getConfigFile())).getCanonicalPath();<br /> if (!canConfigFile.startsWith (configBase.getCanonicalPath())) {`<br />` File file = new File(configBase, getDefaultConfigFile());<br /> if (copy(new File(canConfigFile), file)) {`<br />` setConfigFile(file.getPath());<br /> }`<br />` }<br /> } catch (Exception e) {`<br />` log.warn(“Error setting config file”, e);<br /> }`<br />` }<br /> }`<br /> <br />` ………<br /> Container children[] = findChildren();`<br />` for (int i = 0; i < children.length; i++) {<br /> if (children[i] instanceof Lifecycle)`<br />` ((Lifecycle) children[i]).start();<br /> }`<br />` `<br />` if (pipeline instanceof Lifecycle)<br /> ((Lifecycle) pipeline).start();`<br />` ………<br /> <br /> }` |
---|---|
它主要是设置各种资源属性和管理组件,还有非常重要的就是启动子容器和 Pipeline。
我们知道 Context 的配置文件中有个 reloadable 属性,如下面配置:
清单 14. Server.xml
1 2 3 4 5 |
<``Context ``path``=``"/library" ``docBase``=``"D:\projects\library\deploy\target\library.war" ``reloadable``=``"true" /> |
---|---|
当这个 reloadable 设为 true 时,war 被修改后 Tomcat 会自动的重新加载这个应用。如何做到这点的呢 ? 这个功能是在 StandardContext 的 backgroundProcess 方法中实现的,这个方法的代码如下:
清单 15. 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 |
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,代码片段如下:
清单 16. 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 |
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) |
<br /> ((Context)getParent()).getPrivileged() )) {`<br />` ((ContainerServlet) servlet).setWrapper(this);<br /> }`<br />` classLoadTime=(int) (System.currentTimeMillis() -t1);<br /> try {`<br />` instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);<br /> if( System.getSecurityManager() != null) {`<br />` Class[] classType = new Class[]{ServletConfig.class};<br /> Object[] args = new Object[]{((ServletConfig)facade)};`<br />` SecurityUtil.doAsPrivilege(“init”,servlet,classType,args);<br /> } else {`<br />` servlet.init(facade);<br /> }`<br />` if ((loadOnStartup >= 0) && (jspFile != null)) {<br /> ………`<br />` if( System.getSecurityManager() != null) {<br /> Class[] classType = new Class[]{ServletRequest.class,`<br />` ServletResponse.class};<br /> Object[] args = new Object[]{req, res};`<br />` SecurityUtil.doAsPrivilege(“service”,servlet,classType,args);<br /> } else {`<br />` servlet.service(req, res);<br /> }`<br />` }<br /> instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet);`<br />` ………<br /> <br /> `return servlet; } |
|
---|---|---|---|
它基本上描述了对 Servlet 的操作,当装载了 Servlet 后就会调用 Servlet 的 init 方法,同时会传一个 StandardWrapperFacade 对象给 Servlet,这个对象包装了 StandardWrapper,ServletConfig 与它们的关系图如下:
图 13. ServletConfig 与 StandardWrapperFacade、StandardWrapper 的关系
Servlet 可以获得的信息都在 StandardWrapperFacade 封装,这些信息又是在 StandardWrapper 对象中拿到的。所以 Servlet 可以通过 ServletConfig 拿到有限的容器的信息。
当 Servlet 被初始化完成后,就等着 StandardWrapperValve 去调用它的 service 方法了,调用 service 方法之前要调用 Servlet 所有的 filter。
Tomcat 中其它组件
Tomcat 还有其它重要的组件,如安全组件 security、logger 日志组件、session、mbeans、naming 等其它组件。这些组件共同为 Connector 和 Container 提供必要的服务。
Jetty
基本架构
Jetty目前的是一个比较被看好的 Servlet 引擎,它的架构比较简单,也是一个可扩展性和非常灵活的应用服务器。
它有一个基本数据模型,这个数据模型就是 Handler(处理器),所有可以被扩展的组件都可以作为一个 Handler,添加到 Server 中,Jetty 就是帮你管理这些 Handler。
下图是 Jetty 的基本架构图,整个 Jetty 的核心组件由 Server 和 Connector 两个组件构成,整个 Server 组件是基于 Handler 容器工作的,它类似与 Tomcat 的 Container 容器。
Jetty 中另外一个比不可少的组件是 Connector,它负责接受客户端的连接请求,并将请求分配给一个处理队列去执行。
图 1. Jetty 的基本架构
Jetty 中还有一些可有可无的组件,我们可以在它上做扩展。如 JMX,我们可以定义一些 Mbean 把它加到 Server 中,当 Server 启动的时候,这些 Bean 就会一起工作。
图 2. Jetty 的主要组件的类图
从上图可以看出整个 Jetty 的核心是围绕着 Server 类来构建,Server 类继承了 Handler,关联了 Connector 和 Container,Container 是管理 Mbean 的容器。
Jetty 的 Server 的扩展主要是实现一个个 Handler 并将 Handler 加到 Server 中,Server 中提供了调用这些 Handler 的访问规则。
整个 Jetty 的所有组件的生命周期管理是基于观察者模板设计,它和 Tomcat 的管理是类似的。
图 3. LifeCycle 的类关系图
每个组件都会持有一个观察者(在这里是 Listener 类,这个类通常对应到观察者模式中常用的 Observer 角色,关于观察者模式可以参考 《Tomcat系统架构与设计模式,第2部分:设计模式分析》一文中关于观察者模式的讲解)集合,当 start、fail 或 stop 等事件触发时,这些 Listener 将会被调用,这是最简单的一种设计方式,相比 Tomcat 的 LifeCycle 要简单的多。
体系结构
前面所述 Jetty 主要是基于 Handler 来设计的,Handler 的体系结构影响着整个 Jetty 的方方面面。下面总结了一下 Handler 的种类及作用:
图 3. Handler 的体系结构
Jetty 主要提供了两种 Handler 类型,一种是 HandlerWrapper,它可以将一个 Handler 委托给另外一个类去执行,如我们要将一个 Handler 加到 Jetty 中,那么就必须将这个 Handler 委托给 Server 去调用。配合 ScopeHandler 类我们可以拦截 Handler 的执行,在调用 Handler 之前或之后,可以做一些另外的事情,类似于 Tomcat 中的 Valve(阀门);另外一个 Handler 类型是 HandlerCollection,这个 Handler 类可以将多个 Handler 组装在一起,构成一个 Handler 链,方便我们做扩展。
启动过程
Jetty 的入口是 Server 类,Server 类启动完成了,就代表 Jetty 能为你提供服务了。
它到底能提供哪些服务,就要看 Server 类启动时都调用了哪些组件的 start 方法。
从 Jetty 的配置文件我们可以发现,配置 Jetty 的过程就是将那些类配置到 Server 的过程。下面是 Jetty 的启动时序图:
因为 Jetty 中所有的组件都会继承 LifeCycle,所以 Server 的 start 方法调用就会调用所有已经注册到 Server 的组件,Server 启动其它组件的顺序是:
- 启动设置到 Server 的 Handler(通常这个 Handler 会有很多子 Handler,这些 Handler 将组成一个 Handler 链,Server 会依次启动这个链上的所有 Handler);
- 接着会启动注册在 Server 上 JMX 的 Mbean,让 Mbean 也一起工作起来;
- 最后会启动 Connector,打开端口,接受客户端请求。
接受请求
Jetty 作为一个独立的 Servlet 引擎可以独立提供 Web 服务,但是它也可以与其他 Web 应用服务器集成,所以它可以提供基于两种协议工作,一个是 HTTP,一个是 AJP 协议。
如果将 Jetty 集成到 Jboss 或者 Apache,那么就可以让 Jetty 基于 AJP 模式工作。
下面分别介绍 Jetty 如何基于这两种协议工作,以及如何建立连接和接受请求的。
基于 HTTP 协议工作
如果前端没有其它 web 服务器,那么 Jetty 应该是基于 HTTP 协议工作。也就是当 Jetty 接收到一个请求时,必须要按照 HTTP 协议解析请求来封装返回的数据。那么 Jetty 是如何接受一个请求又如何处理这个请求呢?
我们设置 Jetty 的 Connector 实现类为 org.eclipse.jetty.server.bi.SocketConnector ,让 Jetty 以 BIO 的方式工作。
- Jetty 在启动时将会创建 BIO 的工作环境,它会创建 HttpConnection 类用来解析和封装 HTTP1.1 的协议。
- ConnectorEndPoint 类负责以 BIO 的处理方式处理连接请求;
- ServerSocket 负责建立 socket 连接接受和传送数据;
- Executor 负责处理连接的线程池,处理每一个请求队列中的任务;
- acceptorThread 是监听连接请求,一有 socket 连接,它将进入下面的处理流程;
当 socket 被真正执行时,HttpConnection 将被调用,这里定义了如何将请求传递到 servlet 容器里,又如何将请求最终路由到目的 servlet,关于这个细节可以参考《servlet 工作原理解析》一文。
下图是 Jetty 启动创建建立连接的时序图:
图 5. 建立连接的时序图
Jetty 创建接受连接环境需要三个步骤:
- 创建一个队列线程池,用于处理每个建立连接产生的任务,这个线程池可以由用户来指定,这个和 Tomcat 是类似的。
- 创建 ServerSocket,用于准备接受客户端的 socket 请求,以及客户端用来包装这个 socket 的一些辅助类。
- 创建一个或多个监听线程,用来监听访问端口是否有连接进来。
相比 Tomcat 创建建立连接的环境,Jetty 的逻辑更加简单,牵涉到的类更少,执行的代码量也更少了。
当建立连接的环境已经准备好了,就可以接受 HTTP 请求了,当 Acceptor 接受到 socket 连接后将转入下图所示流程执行:
图 6. 处理连接时序图
Accetptor 线程将会为这个请求创建 ConnectorEndPoint。HttpConnection 用来表示这个连接是一个 HTTP 协议的连接,它会创建 HttpParse 类解析 HTTP 协议,并且会创建符合 HTTP 协议的 Request 和 Response 对象。接下去就是将这个线程交给队列线程池去执行了。
基于 AJP 工作
通常一个 web 服务站点的后端服务器不是将 Java 的应用服务器直接暴露给服务访问者,而是在应用服务器(如Jboss)的前面再加一个 web 服务器(如 Apache 或者 nginx),我想这个原因大家应该很容易理解,如做日志分析、负载均衡、权限控制、防止恶意请求以及静态资源预加载等等。
下图是通常的 web 服务端的架构图:
图 7. Web 服务端架构
这种架构下 servlet 引擎就不需要解析和封装返回的 HTTP 协议,因为 HTTP 协议的解析工作已经在 Apache 或 Nginx 服务器上完成了,Jboss 只要基于更加简单的 AJP 协议工作就行了,这样能加快请求的响应速度。
对比 HTTP 协议的时序图可以发现,它们的逻辑几乎是相同的,不同的是替换了一个类 Ajp13Parserer 而不是 HttpParser,它定义了如何处理 AJP 协议以及需要哪些类来配合。
实际上在 AJP 处理请求相比较 HTTP 时唯一的不同就是在读取到 socket 数据包时,如何来转换这个数据包,是按照 HTTP 协议的包格式来解析就是 HttpParser,按照 AJP 协议来解析就是 Ajp13Parserer。封装返回的数据也是如此。
让 Jetty 工作在 AJP 协议下,需要配置 connector 的实现类为 Ajp13SocketConnector,这个类继承了 SocketConnector 类,覆盖了父类的 newConnection 方法,为的是创建 Ajp13Connection 对象而不是 HttpConnection。如下图表示的是 Jetty 创建连接环境时序图:
与 HTTP 方式唯一不同的地方的就是将 SocketConnector 类替换成了 Ajp13SocketConnector。改成 Ajp13SocketConnector 的目的就是可以创建 Ajp13Connection 类,表示当前这个连接使用的是 AJP 协议,所以需要用 Ajp13Parser 类解析 AJP 协议,处理连接的逻辑都是一样的。如下时序图所示:
基于 NIO 方式工作
前面所描述的 Jetty 建立客户端连接到处理客户端的连接都是基于 BIO 的方式,它也支持另外一种 NIO 的处理方式,其中 Jetty 的默认 connector 就是 NIO 方式。
关于 NIO 的工作原理可以参考 developerworks 上关于 NIO 的文章,通常 NIO 的工作原型如下:
Selector selector = Selector.open; ServerSocketChannel ssc = ServerSocketChannel.open; ssc.configureBlocking( false ); SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT ); ServerSocketChannel ss = (ServerSocketChannel)key.channel; SocketChannel sc = ss.accept; sc.configureBlocking( false ); SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ ); Set selectedKeys = selector.selectedKeys;
创建一个 Selector 相当于一个观察者,打开一个 Server 端通道,把这个 server 通道注册到观察者上并且指定监听的事件。然后遍历这个观察者观察到事件,取出感兴趣的事件再处理。这里有个最核心的地方就是,我们不需要为每个被观察者创建一个线程来监控它随时发生的事件。而是把这些被观察者都注册一个地方统一管理,然后由它把触发的事件统一发送给感兴趣的程序模块。这里的核心是能够统一的管理每个被观察者的事件,所以我们就可以把服务端上每个建立的连接传送和接受数据作为一个事件统一管理,这样就不必要每个连接需要一个线程来维护了。
这里需要注意的地方时,很多人认为监听 SelectionKey.OP_ACCEPT 事件就已经是非阻塞方式了,其实 Jetty 仍然是用一个线程来监听客户端的连接请求,当接受到请求后,把这个请求再注册到 Selector 上,然后才是非阻塞方式执行。这个地方还有一个容易引起误解的地方是:认为 Jetty 以 NIO 方式工作只会有一个线程来处理所有的请求,甚至会认为不同用户会在服务端共享一个线程从而会导致基于 ThreadLocal 的程序会出现问题,其实从 Jetty 的源码中能够发现,真正共享一个线程的处理只是在监听不同连接的数据传送事件上,比如有多个连接已经建立,传统方式是当没有数据传输时,线程是阻塞的也就是一直在等待下一个数据的到来,而 NIO 的处理方式是只有一个线程在等待所有连接的数据的到来,而当某个连接数据到来时 Jetty 会把它分配给这个连接对应的处理线程去处理,所以不同连接的处理线程仍然是独立的。
Jetty 的 NIO 处理方式和 Tomcat 的几乎一样,唯一不同的地方是在如何把监听到事件分配给对应的连接的处理方式。从测试效果来看 Jetty 的 NIO 处理方式更加高效。下面是 Jetty 的 NIO 处理时序图:
处理请求
下面看一下 Jetty 是如何处理一个 HTTP 请求的。
实际上 Jetty 的工作方式非常简单,当 Jetty 接受到一个请求时,Jetty 就把这个请求交给在 Server 中注册的代理 Handler 去执行,如何执行你注册的 Handler,同样由你去规定,Jetty 要做的就是调用你注册的第一个 Handler 的 handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 方法,接下去要怎么做,完全由你决定。
要能接受一个 web 请求访问,首先要创建一个 ContextHandler,如下代码所示:
Server server = new Server(8080); ContextHandler context = new ContextHandler; context.setContextPath(“/“); context.setResourceBase(“.”); context.setClassLoader(Thread.currentThread.getContextClassLoader); server.setHandler(context); context.setHandler(new HelloHandler); server.start; server.join;
当我们在浏览器里敲入 http://localhost:8080 时的请求将会代理到 Server 类的 handle 方法,Server 的 handle 的方法将请求代理给 ContextHandler 的 handle 方法,ContextHandler 又调用 HelloHandler 的 handle 方法。这个调用方式是不是和 Servlet 的工作方式类似,在启动之前初始化,然后创建对象后调用 Servlet 的 service 方法。在 Servlet 的 API 中我通常也只实现它的一个包装好的类,在 Jetty 中也是如此,虽然 ContextHandler 也只是一个 Handler,但是这个 Handler 通常是由 Jetty 帮你实现了,我们一般只要实现一些我们具体要做的业务逻辑有关的 Handler 就好了,而一些流程性的或某些规范的 Handler,我们直接用就好了,如下面的关于 Jetty 支持 Servlet 的规范的 Handler 就有多种实现,下面是一个简单的 HTTP 请求的流程。
访问一个 Servlet 的代码:
Server server = new Server; Connector connector = new SelectChannelConnector; connector.setPort(8080); server.setConnectors(new Connector[]{ connector }); ServletContextHandler root = new ServletContextHandler(null,”/“,ServletContextHandler.SESSIONS); server.setHandler(root); root.addServlet(new ServletHolder(new org.eclipse.jetty.embedded.HelloServlet(“Hello”)),”/“); server.start; server.join;
创建一个 ServletContextHandler 并给这个 Handler 添加一个 Servlet,这里的 ServletHolder 是 Servlet 的一个装饰类,它十分类似于 Tomcat 中的 StandardWrapper。下面是请求这个 Servlet 的时序图:
图 8. Jetty 处理请求的时序图
上图可以看出 Jetty 处理请求的过程就是 Handler 链上 handle 方法的执行过程,在这里需要解释的一点是 ScopeHandler 的处理规则,ServletContextHandler、SessionHandler 和 ServletHandler 都继承了 ScopeHandler,那么这三个类组成一个 Handler 链,它们的执行规则是:ServletContextHandler.handleServletContextHandler.doScope SessionHandler. doScopeServletHandler. doScopeServletContextHandler. doHandleSessionHandler. doHandleServletHandler. doHandle,它这种机制使得我们可以在 doScope 做一些额外工作。
与 Jboss 集成
前面介绍了 Jetty 可以基于 AJP 协议工作,在正常的企业级应用中,Jetty 作为一个 Servlet 引擎都是基于 AJP 协议工作的,所以它前面必然有一个服务器,通常情况下与 Jboss 集成的可能性非常大,这里介绍一下如何与 Jboss 集成。
Jboss 是基于 JMX 的架构,那么只要符合 JMX 规范的系统或框架都可以作为一个组件加到 Jboss 中,扩展 Jboss 的功能。Jetty 作为主要的 Servlet 引擎当然支持与 Jboss 集成。具体集成方式如下:
Jetty 作为一个独立的 Servlet 引擎集成到 Jboss 需要继承 Jboss 的 AbstractWebContainer 类,这个类实现的是模板模式,其中有一个抽象方法需要子类去实现,它是 getDeployer,可以指定创建 web 服务的 Deployer。Jetty 工程中有个 jetty-jboss 模块,编译这个模块就会产生一个 SAR 包,或者可以直接从官网下载一个 SAR 包。解压后如下图:
图 9. jboss-jetty 目录
在 jboss-jetty-6.1.9 目录下有一个 webdefault.xml 配置文件,这个文件是 Jetty 的默认 web.xml 配置,在 META-INF 目录发下发现 jboss-service.xml 文件,这个文件配置了 MBean,如下:
同样这个 org.jboss.jetty.JettyService 类也是继成 org.jboss.web.AbstractWebContainer 类,覆盖了父类的 startService 方法,这个方法直接调用 jetty.start 启动 Jetty。
与 Tomcat 的比较
Tomcat 和 Jetty 都是作为一个 Servlet 引擎应用的比较广泛,可以将它们比作为中国与美国的关系,虽然 Jetty 正常成长为一个优秀的 Servlet 引擎,但是目前的 Tomcat 的地位仍然难以撼动。相比较来看,它们都有各自的优点与缺点。 Tomcat 经过长时间的发展,它已经广泛的被市场接受和认可,相对 Jetty 来说 Tomcat 还是比较稳定和成熟,尤其在企业级应用方面,Tomcat 仍然是第一选择。但是随着 Jetty 的发展,Jetty 的市场份额也在不断提高,至于原因就要归功与 Jetty 的很多优点了,而这些优点也是因为 Jetty 在技术上的优势体现出来的。
架构比较
从架构上来说,显然 Jetty 比 Tomcat 更加简单,如果你对 Tomcat 的架构还不是很了解的话,建议你先看一下 《Tomcat系统架构与设计模式》这篇文章。
Jetty 的架构从前面的分析可知,它的所有组件都是基于 Handler 来实现,当然它也支持 JMX。但是主要的功能扩展都可以用 Handler 来实现。可以说 Jetty 是面向 Handler 的架构,就像 Spring 是面向 Bean 的架构,iBATIS 是面向 statement 一样,而 Tomcat 是以多级容器构建起来的,它们的架构设计必然都有一个“元神”,所有以这个“元神“构建的其它组件都是肉身。
从设计模板角度来看 Handler 的设计实际上就是一个责任链模式,接口类 HandlerCollection 可以帮助开发者构建一个链,而另一个接口类 ScopeHandler 可以帮助你控制这个链的访问顺序。另外一个用到的设计模板就是观察者模式,用这个设计模式控制了整个 Jetty 的生命周期,只要继承了 LifeCycle 接口,你的对象就可以交给 Jetty 来统一管理了。所以扩展 Jetty 非常简单,也很容易让人理解,整体架构上的简单也带来了无比的好处,Jetty 可以很容易被扩展和裁剪。
相比之下,Tomcat 要臃肿很多,Tomcat 的整体设计上很复杂,前面说了 Tomcat 的核心是它的容器的设计,从 Server 到 Service 再到 engine 等 container 容器。作为一个应用服务器这样设计无口厚非,容器的分层设计也是为了更好的扩展,这是这种扩展的方式是将应用服务器的内部结构暴露给外部使用者,使得如果想扩展 Tomcat,开发人员必须要首先了解 Tomcat 的整体设计结构,然后才能知道如何按照它的规范来做扩展。这样无形就增加了对 Tomcat 的学习成本。不仅仅是容器,实际上 Tomcat 也有基于责任链的设计方式,像串联 Pipeline 的 Vavle 设计也是与 Jetty 的 Handler 类似的方式。要自己实现一个 Vavle 与写一个 Handler 的难度不相上下。表面上看,Tomcat 的功能要比 Jetty 强大,因为 Tomcat 已经帮你做了很多工作了,而 Jetty 只告诉,你能怎么做,如何做,有你去实现。 打个比方,就像小孩子学数学,Tomcat 告诉你 1+1=2,1+2=3,2+2=4 这个结果,然后你可以根据这个方式得出 1+1+2=4,你要计算其它数必须根据它给你的公式才能计算,而 Jetty 是告诉你加减乘除的算法规则,然后你就可以根据这个规则自己做运算了。所以你一旦掌握了 Jetty,Jetty 将变得异常强大。
性能比较
单纯比较 Tomcat 与 Jetty 的性能意义不是很大,只能说在某种使用场景下,它表现的各有差异。因为它们面向的使用场景不尽相同。从架构上来看 Tomcat 在处理少数非常繁忙的连接上更有优势,也就是说连接的生命周期如果短的话,Tomcat 的总体性能更高。
而 Jetty 刚好相反,Jetty 可以同时处理大量连接而且可以长时间保持这些连接。例如像一些 web 聊天应用非常适合用 Jetty 做服务器,像淘宝的 web 旺旺就是用 Jetty 作为 Servlet 引擎。
另外由于 Jetty 的架构非常简单,作为服务器它可以按需加载组件,这样不需要的组件可以去掉,这样无形可以减少服务器本身的内存开销,处理一次请求也是可以减少产生的临时对象,这样性能也会提高。另外 Jetty 默认使用的是 NIO 技术在处理 I/O 请求上更占优势,Tomcat 默认使用的是 BIO,在处理静态资源时,Tomcat 的性能不如 Jetty。
特性比较
作为一个标准的 Servlet 引擎,它们都支持标准的 Servlet 规范,还有 Java EE 的规范也都支持,由于 Tomcat 的使用的更加广泛,它对这些支持的更加全面一些,有很多特性 Tomcat 都直接集成进来了。但是 Jetty 的应变更加快速,这一方面是因为 Jetty 的开发社区更加活跃,另一方面也是因为 Jetty 的修改更加简单,它只要把相应的组件替换就好了,而 Tomcat 的整体结构上要复杂很多,修改功能比较缓慢。所以 Tomcat 对最新的 Servlet 规范的支持总是要比人们预期的要晚。
总结
本文介绍了目前 Java 服务端中一个比较流行应用服务器 Jetty,介绍了它的基本架构和工作原理以及如何和 Jboss 工作,最后与 Tomcat 做了比较。在看这篇文章的时候最好是结合我前面写的两篇文章《 Tomcat 系统架构与设计模式》和《 Servlet 工作原理解析》以及这些系统的源代码,耐心的都看一下会让你对 Java 服务端有个总体的了解。