嵌套组件
这些组件是针对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类实例。
未完待续......