下面让我们来看看Tomcat容器的整体结构:

本文的目的是覆盖这张图中所涉及的主要请求处理组件。而上图中的一些高级主题如集群和安全则不是在本文讨论的范围之内。

本图中,Service, Host, Context以及Wrapper实例之后的符号“+”表示这些对象能存在一个或多个。例如一个Service可能只有一个Engine,但是一个Engine可以包含一个或多个Host;另外,图中旋转的圆圈代表请求处理器的线程池。

1、组件分类

Tomcat架构采用类似俄罗斯嵌套娃娃(译注:一层套一层)的设计方式。换句话说,就是一个容器包含另一个容器,而这个被包含的容器实体反过来再包含别的实体。

而在Tomcat中,“容器”是对任何包含有其他组件的组件的通称,如Server、Service、Engine、Host以及Context都称为容器。 其中Server和Service组件比较特殊些,被设计为顶级元素,代表Tomcat运行实例。而Tomcat其他所有的组件都属于这些顶级元素。其中Engine、Host以及Contex都被官方称为容器并依赖处理传入请求和生成适当数据响应的相关组件。

而被嵌套的组件可以作为子元素来嵌入顶级元素或其他容器之中来配置这些组件的工作方式。这其中,嵌套组件包括代表可重用的工作单元的Valve组件、代表Valve链的Pipeline组件以及帮助特定容器建立容器管理安全的Realm组件。

此外,嵌套组件中还包括用来强制servlet中的类使用指定的规范来加载类的加载器;为每个web应用提供session管理的管理器;代表web应用中静态资源以及提供机制来访问这些资源的资源管理组件;以及在容器的生命周期内允许在重要的点插入自定义处理程序的监听器组件,例如当某个组件启动或停止时就可以使用监听器。

值得注意的是,并不是所有的前台组件都可以被嵌套在每个容器中。

这里还有最后一个主要组件,那就是连接器(Connector);它代表一个连接点,通过这个连接点外部客户端可以(比如网页浏览器)可以连接至Tomcat容器。

在我们去学习这些组件之前,让我们快速看下这些组件的大体结构:

请注意,上图只展示了每个容器的关键属性。

启动Tomcat时,它运行的Java虚拟机(JVM)实例中包含一个服务器顶级元素,该元素代表了一个完整Tomcat服务器。一个Tomcat服务器通常只包含一个Service对象,这个对象是一种包含一个或多个连接器(Connector,如HTTP、HTTPS连接器)的结构化元素,正是这些Connector通过一个Catalina的Servlet引擎来处理传入的请求。

引擎(Engine)表示Tomcat中处理请求的核心代码,并且它还支持在其下定义多个虚拟主机(Host)。虚拟主机允许Tomcat引擎在将配置在一台机器上的多个域名(如www.my-site.com、www.your-site.com)分割开来互不干扰。

反过来,每个虚拟主机又可以支持多个web应用部署在它下边,这就是我们所熟知的上下文对象(Context)。上下文(Context)是使用由Servlet规范中指定的Web应用程序格式表示,不论是压缩过的war包形式的文件还是未压缩的目录形式。此外,上下文一般是在web.xml文件中配置,并且该配置是根据servlet规范定义的。

从上下问角度看,在上下文中又可以部署多个servlet,并且每个servlet都会被一个包装组件所包含。

至此,我们以上所说的Server、Service、Connector、Engine、Host、Context元素都会通过server.xml配置文件在tomcat实例中被使用。

2、架构的好处

这种架构有一些很实用的功能。它不仅便于组件的生命周期管理(每个组件管理生命周期并通知其子节点),而且便于在Tcomat启动时根据从配置文件中读取的配置文件来动态组装出Tomcat服务器实例。尤其是server.xml在启动时就会被解析,其内容正是用来实例化和配置被定义的元素,并随后组装到正在运行的Tomcat实例中。

server.xml文件只会被读取一次,对server.xml的修改只有在Tomcat重启后才会起作用。

同时这种架构简化配置,允许子容器继承父容器的配置。例如Realm定义了一个可验证权限的数据存储,并且可以授权用户通过web应用来访问受保护资源。为了便于配置,针对引擎定义的Realm适用于它所有的host和context配置。同时,某一个特定的子元素如context也可以通过继承父类的realm来实现自定义realm。

3、顶级组件

Server和Service容器组件存在的目的是尽可能的方便结构化。Server表示正在运行的Tomcat实例,可包含一个或多个Service子容器;其中每个Service代表一组请求处理组件。

Server

Server代表完整的Tomcat实例在Java虚拟集中是单例,主要是用来管理容器下各个Serivce组件的生命周期。

下图描述了Server组件的关键方面。如图所示,Server实例是通过server.xml配置文件来配置的;其根元素<Server>所代表的正是Tomcat实例,默认实现为org.apache.catalina.core.StandardServer。但是,你也可以通过<Server>标签的class属性来自定义服务器实现。

服务器可重要的一方面就是它打开了8005端口(默认端口)来监听关闭服务命令(默认情况下命令为SHUTDOWN)。当收到shutdown命令后,服务器会优雅的关闭自己。同时出于安全考虑,发起关闭请求的连接必须来自同一台机器上的同一个运行中的Tomcat实例。

此外,Server还提供了一个Java命名服务和JNDI服务,可以通过这两个服务可以使用名称来注册专用对象(如数据源配置)。在运行期,单个组件(如Servlet)可以使用对象名称来通过服务器的JNDI绑定服务来查找需要的对象相关信息。

虽然JNDI实现并不是Servlet容器的功能,但是它属于JavaEE规范一部分,并且可以为Servlet从应用服务器或者servlet容器中获取所需要的信息提供服务。

虽然在一个JVM中通常只有一个服务器实例,但是完全可以在同一台物理机器中运行多个服务器实例,每个实例对应一个JVM实例;这种做法将运行在一个JVM中的应用中的错误与其他JVM中应用的错误隔离开来互不影响,这也简化了维护使得JVM的重启与其他独立开来。这是一个共享主机环境的机制(另一种是虚拟主机机制,很快我们将会看到),这种机制下需要将运行在同一物理主机下的多个web应用隔离开来。

Service

Server代表Tomcat实例本身,Service则代表Tomcat中一组请求处理的组件。

Server可以包含一个或多个Service,但每个Service则将一组Connector组件和Engine关联了起来。

客户端请求首先到达连接器(connector),连接器在再将这些请求轮流传入引擎中处理,而Engine也是Tomcat中请求处理的关键组件。上图中展示了HTTP连接器、HTTPS连接以及AJP组件。

一般很少会修改这个元素,而且默认的Service实例通常就足够使用了。

值得注意的是上图中可能有多个Service实例。如图所示,一个Service集中了一些连接器,每个连接器监控一个指定的IP及端口并通过指定的协议做出响应。所以,一个关于多个服务的使用示例就是当你希望通过IP地址或者端口号来区分不同服务(包括这些服务中所包含的engine、host、web应用)时。

例如,当需要配置防火墙来为用户开放某一个服务而该主机上托管的其他服务仍然只是对内部用户可见,这将确保外部用户无法访问内部应用程序,因为对应访问会被防火墙拦截。

因此,Service仅仅是一个分组结构,它并不包含任何其他的附加功能。

4、连接器(Connector)

Connector是客户端连接到Tomcat容器的服务点它为引擎提供协议服务来将引擎与客户端各种协议隔离开来,如HTTP、HTTPS、AJP协议。

Tomcat有两种可配的工作模式--独立模式或在同一web服务器中共享模式。

在独立模式下,Tomcat会配置HTTP和HTTPS连接器,这可以使Tomcat看起来更像完整的web服务器以处理静态请求内容同时还委托Catalina引擎来处理动态内容。

发布时,Tomcat为这种运作模式提供了3种可能实现,即HTTP、HTTP1.1以及HTTPS。

Tomcat中最常见的连接器为标准连接器,也就是通过java标准I/O实现的Coyote连接器。

你也许希望使用一些技术实现,这其中就包括使用Java1.4中引入的非阻塞特性NIO,另一方面可能是通过APR充分利用本地代码来优化特定的操作系统。

值得注意的是,Connector和Engine不仅运行在同一个JVM中,而且还运行在同一个Tomcat服务实例中。

在共享模式中,Tomcat扮演着对web服务器如Apache httpd和微软的IIS支撑的角色。这里web服务器充当客户端通过Apache模块或者通过dll格式ISAPI模块来和Tomcat通信。当该模块判定一个请求需要传入Tomcat处理时,它将使用AJP协议来与Tomcat通信,该协议为二进制协议,在web服务器和Tomcat通信时比基于文本的Http协议更高效。

在Tomcat端,通过AJP连接器来接收wen服务器的请求,并将请求解释为Catalina引擎可处理的格式。

这种模式下Tomcat作为来自web服务器的单独进程运行在自身独立的JVM中。

不论在哪种模式中,Connector的基本属性都是它所需要监听的IP地址及端口号,以及所支持的协议。还有一个关键属性就是并发处理传入请求的最大线程数。一旦所有的处理线程都忙,那么传入的请求都将被忽略,直到有线程空闲为止。

默认情况下,连接器会监听指定物理机器上的所有IP(address属性默认值为0.0.0.0);但也可以配置为只监听某一个IP,这将限制它只接收指定ip的连接请求。

任意一个连接器接收到的请求都会被传入单一的服务引擎中,而这个引擎,就是众所周知的catalina,它负责处理请求并生成响应结果。

引擎将生成的结果返回给连接器,连接器再通过指定的协议将结果回传至客户端。

5、容器组件

这一小节中我们将讨论请求处理组件:引擎(engine)、虚拟主机、上下文(context)组件。

5.1、引擎(engine)

引擎表示可运行的Catalina的servlet引擎实例并且包含了servlet容器的核心功能。在一个服务中只能有一个引擎。同时,作为一个真正的容器,Engine元素之下可以包含一个或多个虚拟主机。

作为请求处理的主要组件,它接收代表传入请求的对象以及输出相应结果。它主要功能是将传入请求委托给适当的虚拟主机处理。如果根据名称没有找到可处理的虚拟主机,那么将根据默认的Host来判断该由哪个虚拟主机处理。

5.2、虚拟主机

虚拟主机在Tomcat中使用Host组件表示。

在虚拟主机中有两个概念非常重要--主机的域名和根目录。

·域名:每个虚拟主机是由它注册的域名来标识的(例:www.host1.com)。域名是您预期的在客户端浏览器地址栏输入的值,对虚拟主机来说就是请求头部。一台虚拟主机的名称在包含它的引擎内必须是唯一的。

·根目录:根目录所在的文件夹包含将被部署到此主机的上下文。根目录可以是一个绝对路径,也可以是对CATALINA_BASE 来说的一个相对路径。

CATALINA_HOME 是一个环境变量,它引用了tomcat 二进制文件的位置。通过CATALINA_BASE 环境变量仅仅使用一个tomcat安装信息的二进制文件,就可以根据不同的配置运行多个tomcat实例(这主要由conf文件夹的内容决定)。

此外,使用一个CATALINA_BASE引用的位置(和CATALINA_HOME不同)保持标准的二进制分配独立于您的安装。这是有好处的,使tomcat升级到一个新版本变得容易,而不必担心影响已经发布的web应用程序和相关的配置文件 。

基本概念

当涉及到主机名映射到互联网协议地址时,最简单的场景,一个给定的完全合格的主机名(FQHN),例如www.swengsol.com 与映射到特定主机的IP地址相关联。

这种方法的缺点是,主机连接到互联网是相当昂贵的。这是真实存在的,尤其是当您考虑到带宽成本、网络基础设施建设 (例如:数据库/邮件服务器、防火墙、不间断电源ups、容错等) 以及维护 (包括人员配置、管理和备份) ,更不用说首先要获得一个IP地址。

因此,许多小企业认为最好的方法是从托管服务提供商那里租赁空间和基础设施。托管服务可能是提供单个的物理服务器,它可以连接到互联网并由特定的IP地址标识。这台物理服务器可以托管多个域名,每个域名代表一个提供商的客户。

什么是虚拟主机?

例如,我们假设Acme Widgets Inc. 和 Vertico LLC 拥有它们的域名,www.acme-widgets.com 和 www.vertico.com ,这些域名被托管在同一台物理服务器上。应用程序被部署到各自对应的域,并且互不干扰。

在这种情况下,这些域被称为虚拟主机,从这种意义上来讲,每一个域看起来都是一个独立的“物理主机”。然而,事实上,他们(域)仅仅是同一台物理主机上不同的逻辑分区。

5.3、虚拟主机技术

有两种常用的方法来设置虚拟主机:

·基于独立IP地址的虚拟主机服务

·基于名称的虚拟主机服务

5.3.1基于独立IP地址的虚拟主机服务

使用这种技术,每个FQHN(完全合格的主机名)被解析为一个单独的IP地址。然而,这些IP中的每一个被解析后都映射到同一台物理机器上。

您可以使用以下的机制来实现此技术:

·多宿主服务器,也就是说它安装了多个网卡(NICs),每一个网卡都分配了IP地址

·使用操作系统功能来设置虚拟网络接口,为单个物理NIC(网卡)动态分配多个IP地址

无论在哪一种情况下,缺点是我们要获得多个IP地址,而且这些地址(至少对于IPv4来说)是一种有限的资源。

Web服务器监听为这些IP地址分配的端口,当Web服务器在一个特定的IP地址检测到传入的请求时,它会生成该IP地址的响应信息。

例如,您有一个web服务器,它运行在一个特定的在80端口监听 11.156.33.345 和 11.156.33.346 IP地址请求的物理主机上。此web服务器用以下方式响应请求:当收到来自主机域名www.host1.com的请求时,则映射到11.156.33.345 IP地址;反之当收到来自主机域名www.host2.com的请求时则映射到后面的 IP地址 11.156.33.346 。

当接收到一个来自11.156.33.346 IP地址的请求时,web服务器知道它应当为ww.host2.com对应的域准备响应信息。对用户来说,这是一个完全独立的物理服务器在为他提供服务。

5.3.2基于名称的虚拟主机服务

这是一种比较新的技术,它允许您把不同的域名映射到同一个IP地址。这些都是经过注册的正常的域名,多个DNS条目将这些域名映射到同一IP地址。

HTTP 1.1协议要求每个请求必须包含一个主机头:带有完全合格的主机域名,以及用户希望连接的端口号(如果已指定)。主机上运行的web服务器接收到此请求,解析此请求中的主机头信息,以确定相应的虚拟主机来响应此请求。简单、而且不使用不必要的IP地址,基于名称的虚拟主机服务是我们的首选。

然而,当您同时使用SSL(安全套接层)和虚拟主机时,您也许不得不使用基于IP地址的虚拟主机服务。原因是,在特定的虚拟主机响应请求之前,协商协议要进行证书认证。这是因为:SSL协议层位于HTTP协议层的下方,而且在握手消息认证完成之前,与客户端请求进行安全认证的模块无法读取HTTP请求头信息。

您也许可以同时使用SSL和基于名称的虚拟主机服务,如果您的web服务器和客户机支持RFC3546(传输层安全性扩展http://www.ietf.org/rfc/rfc3546.txt) 指定的服务器名称标识扩展。使用此扩展,在SSL协商期间,客户端会传输主机名称给它尝试连接的对象,从而使web服务器能够处理握手信息并为正确的主机名返回证书。

虚拟主机别名

当 web服务器解析别名信息时,例如它在主机头里看到了域名的别名,那么web服务器会把此别名当作虚拟主机的域名来处理。 例如,您把swengsol.com设置为虚拟主机域名www.swengsol.com的别名,那么在客户端url里无论是输入域名还是别名,您都会收 到来自同一个虚拟主机的响应信息。 这种方式效果不错,当一个物理主机有多个域名时,而且您不想弄乱配置文件在为每个别名创建一组条目时。

5.4、上下文(Context)

上下文或者web应用是应用自定义代码(servlet、jsp)所存活的地方。它为web应用组织资源提供了便利。

同时context容器为servlet提供了一个ServletContext实例。在许多方面,servlet规范主要是关心这个上下文组件。例如,它规定了部署上下文的格式以及部署内容的描述符。

以下是上下文的一些重要属性:

·根目录(document base):这个路径是指war包或者未压缩的项目文件所存放的目录,可以是相对的,也可以是绝对的。

·上下文路径(context path):它是指在一个host下url中唯一标识一个web应用的部分。它帮助host容器来判断该由哪一个已部署的上下文来处理传入的请求。

也许你可能配置了默认context,它可以在找不到匹配的context的情况下来处理传入请求。该默认context可以通过将其上下文路径配置为空来标记的,因此,可以通过只有主机名的路径来访问它(译注:如http://localhost:8080/来访问)。并且该context已被tomcat默认定义为根目录下的ROOT目录。

·自动重加载(automic reload):上下文中的资源会被tomcat监控,一旦资源发生改变Tomcat就会自动重新加载资源文件。虽然该功能在开发过程中非常有用,但是在生产环境这个操作代价非常高,通常需要重启产品应用。

Context配置

Context是唯一的,这主要是因为它的配置包含多个选项。而我们之前已经注意到的conf/server.xml是用来配置Tomcat实例中一些全局性的参数。虽然在这个文件中可以配置context相关的东西,但是不推荐这样做。

相反,Tomcat推荐大家将context相关的配置从server.xml中提取出来,配置到上下文段文件中,该文件会被Tomcat监控并且可以在运行过程中重新加载。

请再次注意,server.xml只有在启动时被加载一次。

同时需要确保在context中配置一个独立明确的host和engine,因为Tomcat会在CATALINA_HOME/conf///目录下查找context相关配置。而该目录下为特定主机配置的上下文段文件则是以名称.xml命名。

默认情况下,会有一个引擎Catalina和一个名称为localhost的主机,对应的工作目录为CATALINA_HOME/conf/Catalina/localhost。但是该目录也可以是有效域名,如www.swengsol.com,那么对应目录就是CATALINA_HOME/conf/Catalina/www.swengsol.com。

另外,context片段也可以在war或部署目录中被包含在META-INF目录下。这种情况下,context文件名称必须为context.xml。

此外,Context还可以被配置在web应用描述符文件web.xml中。虽然这个片段文件是Tomcat专用的,但是由于该描述符是通过Servlet规范来描述的,因此它也适用与JavaEE下的其他轻量级servlet容器。

包装器(Wrapper)

包装器wrapper对象是context容器的子容器,表示一个单独的servlet(或者由jsp文件转换而来的servlet),他就不能在包含其他容器了。它之所以称为包装器是因为它包装了java.servlet.Servlet实例。

这是容器层次结构的最底层,添加任何子类都会导致异常。

同时包装器还对它所包装的servlet负责,包括加载、实例化servlet以及调用控制servlet生命周期相关的函数,如init()、service()和destroy()方法。

此外包装器还通过它基本的Valve来调用和其包装的servlet相关的过滤器。

嵌套组件

这些组件是针对Tocmat做的特定实现,他们的主要目的是使各种Tomcat容器可以完成各自的工作。

1、阀(Valve)

valve是处理元素,它可以被包含在每个Tomcat容器的处理路径中--如engine、host、context以及servelt包装器。若要增加Valve到Tomcat容器则需要在server.xml中使用<Valve>标签。在server.xml中这些标签的执行顺序与其物理顺序相同。

而在Tomcat中也分布这大量预先编译好的valve。包括:

•在请求日志元素中将请求(如远程客户端ip地址)写入日志文件或数据库时

•根据远程客户端ip或主机名来控制某一特定web应用的访问权限时

•记录每个请求和响应头信息日志时

•在同一个虚拟主机下为多个应用配置单点登录时

如果以上这些都不能满足你的要求,那么你可以通过继承org.apache.catalina.Valve来实现自定义的Valve并将其配置到对应服务中。

// 管道过滤器的软件体系结构

但是对于一个容器来说,它并不会持有某个单独valve的引用;相反,它会持有一个称作管道(Pipeline)的单一实体的引用,并用这个管道来表示与该容器所关联的一系列valve。

当一个容器被调用来处理一个请求时,它会委托与其关联的管道来处理对应请求。

在管道中,这些valve则是基于他们在server.xml中的定义作顺序排列。其中排在队列中排在最后的valve被称为管道的基本valve,该valve用来完成去执行容器的核心功能的任务。

与单个valve不同,管道在server . xml不是一个明确的元素,而是含蓄的按照valve在给定容器中所定义的顺序组成。

并且在管道中,每个valve都知道其下一个valve;在它执行完前置处理以后,接下来它会调用链中的下一个valve,当该调用返回以后,它会在return之前执行他自身的处理任务。

这种方式和servlet规范中的过滤器链所做的事情非常相似。

在这幅图中,当接收到传入请求时引擎所配置的valve首先被触发。其中引擎中基本的valve负责确定目标主机委托该主机来处理;接下来目标主机(www.host1.com)的valve被按顺序触发。而该主机的基本valve则又决定了目标context(在这里是Context1)并且委托该context来处理该请求。最后Context1中所配置的valve被触发,然后通过context中配置的基本valve委托给适当的包装器来处理;而包装器的基本valve又将处理转交至被包装的servlet来处理。

处理完成以后,响应结果会按照以上的路径反方向返回。

由于Valve就成了Tomcat服务器实现中的一部分,并且可以为开发者提供一种方式将自定义的代码注入到处理请求的servlet容器中。因此,自定的valve类文件需要发布到CATALINA_HOME/lib目录下而不是应用的发布目录WEB-INF/classes。

由于它们并不是servlet规范中的部分,所以valve在企业级一用中属于不可移植元素。因此,如果已经依赖了一个特定的valve时,你必须在不同的应用服务器上找到对等的选择方案。

还有很重要的一点就是,为了不影响请求处理的效率必须要保证valve的代码高效执行。

2、Realm

容器管理安全方面的工作通过容器处理应用程序的身份验证和授权方面来解决。

身份验证存在的主要任务就是确保用户所说的就是她自己,而授权的主要任务是决定一个用户是否可以在某个应用下执行特定操作。

由容器来管理安全的优势是可以通过应用的发布者直接来配置安全措施。也就是说,为用户分配密码以及为用户分配角色都可以用户配置来完成,而这些配置也可以在修改任何代码的情况下来供多个web应用共用。

应用管理安全

还有一种可选方案就是通过应用来管理安全问题。这种情况下,我的web应用程序代码就是唯一的仲裁者来决定用户在我们的应用下是否有访问特定功能或资源的权限。

想要使容器来管理安全问题起作用,我们需要组装一下组件:

•安全约束:在我们的web应用部署描述器web.xml中,我们必须确定限制资源访问的url表达式以及可以访问这些资源的用户角色。

•凭证输入机制:在web.xml部署文件中,我们需要指定容器应该如何提示用户通过凭证来验证。这通常是通过弹出一个对话框提示用户输入用户名和密码来完成,但也可以配置使用其他机制,如一个定制的登录表单等。

•Realm:这是一个数据存储机制来保存用户名、密码以及角色来对用户所提供的凭证信息进行检查。它可以是一个简单的xml文件,一个通过JDBC API来访问的关系型数据库中的一张表或者是可以通过JNDI API访问的轻量级目录访问协议服务器(LDAP)。正是Realm为Tomcat提供了一致的访问机制来访问这些不同的数据源。

以上这三种组件在技术上是相互独立的。基于容器安全的威力就在于我们可以根据我们自身的安全情况从这几种方式中选出适合的一种或几种方式来混合使用。

至此,当一个用户请求一个资源时,Tomcat将检查对所请求的资源是否已经存在了安全限制。对于存在限制的资源,Tomcat将自动要求用户提供身份凭证并通过所配置的Realm来检查用户所提供凭证是否符合。只有在用户所提供的凭证信息通过了验证或者用户的角色在可访问资源的配置之列才能访问对应资源。

3、执行器 Executor

这是从tomcat 6.0.11版本开始,新增的一个组件。此组件允许您配置一个共享的线程池,以供您的连接器使用。您的连接器可能使并发线程的数量达到上限。请注意,此限制同样适用于:即使一个特定的连接器没有用完为它配置的所有线程。

4、监听器

每个主要的tomcat组件都实现了org.apache.catalina.Lifecycle接口。实现了该接口的组件注册到监听器,然后该组件的生命周期事件被触发,比如该组件的启动和停止都会被监听。一个监听器实现了org.apache.catalina.LifecycleListener接口,也实现了该接口的lifecycleEvent()方法,监听器捕捉到一个LifecycleEvent 表示事件已经发生。这就给您提供了一个机会:把您自定义的进程注入到tomcat的生命周期。

5、会话管理器

会话让使用无状态HTTP协议的应用程序完成通信。会话表示客户端和服务器之间的通信,会话功能是由javax.servlet.http.HttpSession 的实例实现的,该实例存储在服务器上而且与一个唯一的标识符相关联,客户端在与服务器的每次交互中根据请求中的标识符找到它的会话。一个新的会话在客户端请求后被创建,会话一直有效直到一段时间后客户端连接超时,或者会话直接失效例如客户退出访问服务器。

上图显示了一个非常简单的 tomcat 会话机制视图。Catalina 引擎(engine)使用了组件org.apache.catalina.Manager 去创建、查找、注销会话。该组件负责管理为上下文创建的会话以及会话的生命周期。会话管理器(Manager)把会话存放在内存中,但是它支持在服务器重启时恢复会话。当服务器停止时,会话管理器把所有活动的会话写入磁盘,当服务器重新启动时把会话重新加载到内存。

一个<Manager>必须是 <Context>的子节点,而且<Manager>负责管理与web应用程序上下文相关的会话。

会话管理器管理这样的属性:例如用来生成会话标识符的算法,每秒钟检查过期会话的频率,支持的活动会话的最大数目,以及持久化会话的文件。

会话管理器实现了这样的功能:为会话提供持久化存储,例如一个文件或一个JDBC数据库。

6、加载器

这个组件是一个给定的web应用程序的类加载器。Java中的类加载器是一个神秘的实体。简而言之,类加载器负责加载、解释Java类编译后的字节码。

一个Java类的字节码可能存放在各种不同的位置,最常见的是在本地文件系统或网络中。类加载器的主要任务是:抽象字节如何被获取以及如何重组到内存中的类的过程。

7、委托(代理)模型

自从Java 2以来,类加载机制使用了委托模型,JVM中的类加载器被组织成了父---子层次结构。据介绍,每个类加载器首先把查找和加载一个类的任务委托给它的父级,在它自己尝试做这个任务之前。

这种委托机制确保:没有应用程序可以加载一个有恶意的系统类(例如java.lang.Object),它可能危及在JVM中运行的应用程序的完整性。

类加载器层次结构的顶层是Bootstrap 类加载器,它也叫原始类加载器,它是原生代码而且是JVM的一部分。作为JVM的一部分可以保证:至少有一个可以依靠的类加载器,去加载Java的核心类(例如java.lang.Object)。这个类加载器(Bootstrap )负责从Java核心包(例如java.lang 或 java.util)里加载类文件。在SUN的JVM中,这些核心类文件存放在JAVA_HOME/jre/lib/rt.jar 。Bootstrap类加载器的独特之处在于:它是层次结构树的根节点,因此它没有父类加载器。

层次结构的下一层是Extension类加载器,它在SUN的JVM中是一个java.net.URLClassLoader,它监控 JAVA_HOME/jre/lib/ext 文件夹扩展JARs 。放在此文件夹下的任何JARs(包括类路径之外的)都会被自动加载。

层次结构最底层是系统类加载器(应用程序类加载器),它在SUN的JVM中也是一个URLClassLoader 。它监控CLASSPATH里描述的文件夹、JARs 。这个类加载器负责加载应用程序的主类。

如果一个普通的应用程序需要加载一个类(例如java.lang.String),它首先会询问应用程序类加载器去加载那个类。应用程序类加载器会委托给它的父级Extension类加载器,Extension类加载器委托给它的父级Bootstrap 类加载器,Bootstrap 类加载器然后在rt.jar里加载String.class文件并且使它成为一个可用的java.lang.Class实例。

如果需要加载一个应用程序特定的类文件(例如com.swengsol.UserModel.class),那么委托过程和前面一样。然而这次,Bootstrap 类加载器在rt.jar里没能加载到此类文件,现在轮到Extension类加载器同样没能加载到此类文件。最后,应用程序类加载器在CLASSPATH里找到了此类文件。然后这个类文件被加载并成为一个JVM可以使用的实例。

每个类加载器里都有缓存,所以每个类加载器首先要检查自己的缓存看是否已经加载了类文件。如果找到了,则直接返回类文件。

在我们前面的示例中,如果应用程序需要另一个String类,那么Bootstrap 类加载器则直接返回在它缓存里的String类实例。

Tomcat类加载

8、标准覆盖机制

J2SE 1.4 and 1.5 都包含了一个XML处理解析器的Java API 。Bootstrap 类加载器加载这个解析器的类文件,所以这个解析器会优先于任何一个安装在CLASSPATH里的解析器 被加载,即使您已经安装了新版本的解析器。标准覆盖机制允许您重写JAVA_HOME/lib/endorsed文件夹里某些特定的类(例如CORBA 和 JAXP 类)。Bootstrap 类加载器将会优先加载这些类。想详细了解此机制,请访问http://java.sun.com/j2se/1.5.0/docs/guide/standards/ 。

有关类加载的一些有趣的注意事项如下所示:

  1. •只有当一个类具有包名、类名以及加载此类文件的类加载器的实例的时候,一个类才会被认为完全合格。换句话说,同样的类被两个不同的类加载器加载,会被认为是两个不同的类。即使在同一个JVM中,这已影响到这个类实例的分配、静态属性或单例的处理。
  2. •一个类加载器只能看到位于它层次结构上面的类的目录。例如,一个Extension文件夹里的 JAR不能使用应用程序类路径里的类文件。这是因为,Extension文件夹里的类只能看到Extension类加载器以及Bootstrap 类加载器可以加载的类。
  3. •当一个类的代码中引用了另一个类时,加载引用类的类加载器同样也会加载被引用的 类,这称为自定义类加载器。一个类的自定义类加载器可以使用Class.getClassLoader()来获得。
  4. •每个线程都有一个上下文类加载器,使用Thread.currentThread().getContextClassLoader()可以查看到。每次一个新的线程被创建,这个新线程的上下文类加载器会被设置到它的创建线程。main()线程的类加载器是应用程序类加载器,它会自动向下传播到每个工作线程,除非您通过调用 Thread.currentThread().setContextClassLoader()进行干预。

9、JavaEE类加载(为什么Tomcat要自定义三个类加载器?)

在JavaEE的范畴里这种模式显得有些别扭。

首先,Servlet容器需要为web应用提供一个限制环境。

如果某个servlet直接使用系统类加载器(System Calss Loader),那么该servlet会看到启动Tocmat时所使用JVM命令中所使用的path下的所有类。(为什么这么说,因为tomcat也可以看成一道程序,当你启动Tomcat的时候,实质上也是调用的Bootstap中的main方法,所以所以的servlet可以看成这个main方法依赖的对象,所以要加载,如果直接通过系统的加载器,那么由上面的注意事项可知,对象间,彼此可见的!)这是有潜在安全风险的,因为恶意应用(部署在同一个主机商的主机上的应用)可能被允许加载到其兄弟节点上的web应用的类。正是因为如此,每个web应用必须有自己的类加载器,而该类加载器位于类加载器树的根节点并且可以优先加载在web应用的目录WEB-INF/classes以及WEB-INF/lib下所发现的类。

当所请求加载的类是java标准时,该自定义类加载器将只委托其父类加载器来加载;如果一个web应用还需要其他的类,那么这个自定义类加载器不是去委托其父类去加载,而是首先检查WEB-INF/classes以及WEB-INF/lib中是否包含该类。只有在这两个目录都找不到时,该类加载器才会委托给其父类加载器来加载,而父类加载器的的加载遵循标准委托模式。(译注:类加载相关知识请看文章末尾)。

Tomcat的额外类加载器

在启动过程中,Tomcat首先通过清除CLASSPATH并将其重新指向CATALINA_HOME/bin/bootstrap.jar(Tomcat启动所需的类)、tomcat-juli(日志功能)、tools.jar(jsp编译功能)来屏蔽系统类加载器(System ClassLoader)。这使得系统类加载器只有在加载一小部分Tomcat特殊类时才起作用。

同时Tomcat也改变了支持目录并将其重新指向CATALINA_HOME/endorsed目录。

在其自定类加载器之下,Tomcat又增加了它自己的类加载器,这其中包括server类加载器(Server class loader)、共享类加载器(Shared class loader)、通用类加载器(Common class loader)以及每个部署的应用程序都有的一个web应用程序类装入器。

当一个web应用需要加载一个类时,请求首先发送至web应用的类加载器,该加载器负责加载web应用中WEB-INF/classes和WEB-INF/lib两个目录下的类。

web应用类加载器首先会请求系统类加载器来允许各层类加载器可以查找所有java核心类。当所请求的类未找到到时,该web应用类加载器会尝试从自身类库中定位所请求的类;如果仍然未找到,它将委托通用类加载器或者如果有共享类加载器时委托共享类加载器来加载。

共享类加载器和服务器类加载器默认情况下是没有安装的,但是我们可以通过编辑 CATALINA_HOME/conf/catalina.properties文件通过增加shared.loader和server.loader来开启共享类加载器以及server类加载器;而Common类加载器会监控CATALINA_HOME/lib目录的内容,该下为一些蝉蛹的jar包,比如servlet-api.jar, jasper.jar, coyote.jar, and jsp-api.jar。此外,位于共享目录下的类将对所有web应用可见,但不是Tomcat的内部类,而放在服务器的loader目录的类将只对Tomcat内部类可见。

10、web应用中的类重载

使Tomcat支持类重载的web应用特定类加载器。

当context需要被新部署或者当一个类需要被重载时(例如当一个被重新编译过的类文件加入到WEB-INF/classes目录时),整个web应用的类加载器都会被抛弃,然后创建一个新的实例分支来加载该web应用的所有类。该新的类加载就是用来为之后的请求服务的。

11、Logger

server.xml中的logger元素在Tomcat5.5中就已经被弃用;所以在Tomcat6中是基于Java1.4中所引入的日志API来生成日志的。

然而Java日志只能在整个JVM级别来进行配置,而不能对每个类加载器单独配置。为了使每个web应用程序都能允许不同的配置文件,Tomcat实现了自定义的Java日志,这就是我们所熟知的JULI,该实现位于CATALINA_HOME/bin/tomcat-juli.jar中。

全局的配置文件CATALINA_HOME/conf/logging.properties控制着日志设置。此外,每个web应用也可以有自己的日志配置文件WEB-INF/classes/logging.properties。

如上图所示,日志由一下组件组成:

●Logger:所有的日志请求会进入Logger对象。这些对象都按层次排列,其根在根logger;同时这种层次结构也反映了包结构。在该层次中属性可以被绑定到任何级别上,而且Logger的子类也会继承器父类属性。

●Handler:指定日志消息应该发送何处。可选的有ConsoleHandler(将日志写入控制台)、FileHandler(将日志写入文件)、SocketHandler(将日志写入Tcp socket)。

●Level(日志级别):日志级别有其中:SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST、OFF(禁用)、ALL(记录所有信息),每种级别决定了需要记录何种类型的消息。

●Formatter(日志格式化):该元素决定了信息以什么格式展示。Tomcat提供了SimpleFormatter、XMLFormatter两种格式化工具。

12、资源(Resource)

和web应用上下文相关的资源包括静态资源,如类文件、HTML、JSP、CSS文件。这些资源可能以不同的格式存在。默认情况下,Tomcat支持从war格式的压缩文件(译注:war包)或者解压过的war中查找资源文件。

可想而知,一个上下文中的资源也可能从替代存储机制中访问,比如JDBC数据库。而资源组件使之成为可能。

Tomcat同时也提供基于目录服务的JNDI API,该服务可支持从未知存储方式中读取资源。

总结

这里讲了很多,现在我们需要做的就是使自己理解Tomcat架构总览中所讲解的东西。在这篇文章里,我们讨论了一些Tomcat的核心组件,看到了一个运行中的Tomcat实例是如何由各种顶级组件、连接器(Connector)以及嵌套组件构建起来的。

Tomcat通过各个组件构建起来,组件间通过管道过滤器的架构(pipeLine and  value)联系起来!

Tomcat整体架构分析的更多相关文章

  1. Tomcat系统架构分析

    Tomcat系统架构分析 关于这边blog呢,实际开发中并不会用到,但是我觉得还是很有必要认真的写一下.毕竟我们每天在本地撸码的时候使用的就是tomcat来做web服务器.一个常识就是说我们本地在to ...

  2. Tomcat源码分析 (二)----- Tomcat整体架构及组件

    前言 Tomcat的前身为Catalina,而Catalina又是一个轻量级的Servlet容器.在美国,catalina是一个很美的小岛.所以Tomcat作者的寓意可能是想把Tomcat设计成一个优 ...

  3. OpenRisc-37-OpenRISC的CPU&core的整体架构分析

    引言 前面我们分析了ORPSoC的整体架构,并对其子系统进行了深入的分析和了解.但对于ORPSoC的核心模块or1200_top及其内部的core--or1200_cpu模块却鲜有涉及,算是ORPSo ...

  4. 粗浅看 Tomcat系统架构分析

    原文出处: 吴士龙 http://www.importnew.com/21112.html Tomcat的结构很复杂,但是Tomcat也非常的模块化,找到了Tomcat最核心的模块,就抓住了Tomca ...

  5. Tomcat源码分析 -- Tomcat整体架构

    引用链接:https://blog.csdn.net/w1992wishes/article/details/79242797

  6. WebRTC音视频引擎研究(1)--整体架构分析

    WebRTC技术交流群:234795279 原文地址:http://blog.csdn.net/temotemo/article/details/7530504     1.WebRTC目的     ...

  7. MapReduce整体架构分析

    继前段时间分析Redis源代码一段时间之后.我即将開始接下来的一段技术学习的征程.研究的技术就是当前很火热的Hadoop,可是一个Hadoop生态圈是很庞大的.所以首先我的打算是挑选当中的一部分模块, ...

  8. tomcat整体架构

    1.背景 Tomcat作为JavaWeb领域的Web容器,目前在我们淘宝也使用的也非常广泛,现在基本上所有线上业务系统都是部署在Tomcat上.为了对平时开发的Web系统有更深入的理解以及出于好奇心对 ...

  9. 转: WebRTC音视频引擎研究(1)--整体架构分析

    转自: http://blog.csdn.net/temotemo/article/details/7530504   目录(?)[+]   WebRTC技术交流群:234795279 原文地址:ht ...

随机推荐

  1. python学习(十)

  2. grpc(二)记一次grpc debug--io.grpc.StatusRuntimeException: UNKNOWN

    1.起初是dingding一直报错: instance:服务器名 err:GrpcClient#placeOrder: io.grpc.StatusRuntimeException: UNKNOWN ...

  3. scrapy框架整理

    0.安装scrapy框架 pip install scrapy 注:找不到的库,或者安装部分库报错,去python第三方库中找,很详细 https://www.lfd.uci.edu/~gohlke/ ...

  4. 在github上参与开源项目贡献代码

    1 登录github, 点击自己感兴趣的repository的fork按钮,这样自己的github主页会有一个拷贝. 2 在自己本地修改同时保持和原来的repository同步: git remote ...

  5. Python—装饰器详解

    装饰器:(语法糖) 本质是函数,它是赋予函数新功能,但是不改变函数的源代码及调用方式   原则: 1.不能修改被装饰函数的源代码 2.不能修改被装饰函数的调用方式 3.函数的返回值也不变 这两点简而言 ...

  6. python 多个脚本

    1.增删改查haproxy.conf配置文件 1.查询输入:www.oldboy1.com 2.删除输入:{'backend': 'www.oldboy2.org','record':{'server ...

  7. Neo4j图数据库使用

    最近在处理一些图的数据,主要是有向图,如果图的节点不是特别大可以直接加载到内存里来处理,但是当图的节点个数特别大时,内存就放不下了:我 们牵涉到的图的节点数最大可以达到数亿个节点,已经超出的机器内存的 ...

  8. ipa 注入 dylib

    前些日子再github找到了一个内存修改器 DLGMemor 免越狱在app内植入修改器,感觉很不错,就尝试去看看是否可行. 用到的工具:  Xcode 10. optool 首先要做的,安装 opt ...

  9. oracle 查看字段说明

    SELECT    *FROM    all_col_commentsWHERE    table_name = UPPER ('t_bn_background')AND OWNER = 'VTER' ...

  10. php英语单词大全95

    abstract抽象的 -挨伯丝拽克特 access存取.访问 -挨克色丝 account账户 -厄靠恩特 action动作 -爱克身 activate激活 -爱克特维特 active活动的 -爱克得 ...