在实践过程中,从WebSphere中实现一个EJB的容器以及从WebLogic中实现一个JMS几乎都是不可能的,然而来自Apache基金会的servlet容器Tomcat至少在理论上是可能做到的。
请注意,这里所说的“接口”也包含抽象类。规范的API可能会提供一个实现的模板,其中包括定义了一些抽象的基本类型的操作来供服务提供者去实现。
而服务提供者应提供这些接口和抽象类的具体实现。例如,在Tomcat中HttpSession接口被以org.apache.catalina.session.StandardSession的形式实现。
下面让我们来看看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,它负责处理请求并生成响应结果。
引擎将生成的结果返回给连接器,连接器再通过指定的协议将结果回传至客户端。
未完待续......