欢迎您光临本小站。希望您在这里可以找到自己想要的信息。。。

Tomcat架构讲解(三)

Java Web water 2847℃ 0评论

嵌套组件

这些组件是针对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、执行器

这是从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类实例。

 

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/ 。

 

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

•只有当一个类具有包名、类名以及加载此类文件的类加载器的实例的时候,一个类才会被认为完全合格。换句话说,同样的类被两个不同的类加载器加载,会被认为是两个不同的类。即使在同一个JVM中,这已影响到这个类实例的分配、静态属性或单例的处理。

•一个类加载器只能看到位于它层次结构上面的类的目录。例如,一个Extension文件夹里的 JAR不能使用应用程序类路径里的类文件。这是因为,Extension文件夹里的类只能看到Extension类加载器以及Bootstrap 类加载器可以加载的类。

•当一个类的代码中引用了另一个类时,加载引用类的类加载器同样也会加载被引用的 类,这称为自定义类加载器。一个类的自定义类加载器可以使用Class.getClassLoader()来获得。

•每个线程都有一个上下文类加载器,使用Thread.currentThread().getContextClassLoader()可以查看到。每次一个新的线程被创建,这个新线程的上下文类加载器会被设置到它的创建线程。main()线程的类加载器是应用程序类加载器,它会自动向下传播到每个工作线程,除非您通过调用 Thread.currentThread().setContextClassLoader() 进行干预。

9、JavaEE类加载

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

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

如果某个servlet直接使用系统类加载器 (System Calss Loader),那么该servlet会看到启动Tocmat时所使用JVM命令中所使用的path下的所有类。这是有潜在安全风险的,因为恶意应用(部 署在同一个主机商的主机上的应用)可能被允许加载到其兄弟节点上的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)以及嵌套组件构建起来的。

 

 

1. 本文由程序员学架构翻译

2. 本文译自

https://www.packtpub.com/books/content/overview-tomcat-6-servlet-container-part-2

 

转载请注明:学时网 » Tomcat架构讲解(三)

喜欢 (0)or分享 (0)

您必须 登录 才能发表评论!