- chapter11:
standardwrapper
- chapter12: 无程序
第十章 安全性
servlet容器是通过一个名为验证器的阀来支持安全限制的。当servlet容器启动时,验证器阀会被添加到Context容器的管道中。
验证器阀会调用Context容器的领域对象的authenticate()方法,传入用户输入的用户名和密码,来对用户进行身份验证。领域对象可以访问有效用户的用户名和密码的集合。
servlet编程中安全性功能的概念包括主题、角色、领域和登陆配置等。可以阅读《Java for the Web with Senlets, JSP, and EJB》一书
10.1 领域
- 领域对象用来对用户进行身份验证的组件。领域对象通常都会与一个Context容器相关联,而一个Context容器也只能有一个领域对象。
- 领域对象如何验证用户身份呢?它保存了所有有效用户的用户名和密码对,或者它会访问存储这些数据的存储器。在Tomcat中,有效信息默认存储在tomcat-user.xml文件中。但是也可以使用其他领域对象来实现。
- 在Catalina中,领域对象是org.apache.catalina.Realm接口的实例。
10.2 GenericPrincipal类
- 主体对象是java.seurity.Principal接口的实例。该接口在Catalina中的实现是org.apache.catalina.realm.GenericPrincipal类。GenericPrincipal实例必须始终与一个领域对象相关联。
10.3 LoginConfig类
登录配置是final型的org.apache.catalina.deploy.LoginConfig类的实例,其中包含一个领域对象的名字。LoginConfig实例封装了领域对象名和所要使用的身份验证方法。
在实际部署中,Tomcat在启动时需要读取web.xml文件的内容。如果web.xml文件包含login-config元素的配置,则Tomcat会创建一个LoginConfig对象。
10.4 Authenticator
验证器是Authenticator接口的实例。Authenticator接口本身并没有声明方法。只是起到了一个标记作用,这样其他组件就可以使用instanceof
关键字检查某个组件是不是一个验证器。
Catalina提供了Authenticator接口的一个基本实现,AuthenticatorBase类。除了实现Authenticator接口外,AuthenticatorBase类还扩展了org.apache.catalina.values.ValveBase类。这就是说AuthenticatorBase类也是一个阀。
10.5 安装验证器阀
在部署描述器中,login-config元素仅能出现一次。login-config元素包含一个auth-method元素来指定身份验证方法。也就是说,一个Context实例只能有一个LoginConfig实例和利用一个验证类的实现。
由于使用的验证器类是在运行时才确定的,因此该类是动态载入的。StandardContext类使用org.apache.catalina.startup.ContextConfig类来对StandardContext实例的属性进行设置。这些设置包括实例化一个验证器类,并将该实例与Context实例相关联。本章示例中SimpleContextConfig类的实例负责动态载入BasicAuthenticator类,实例化其对象,并将其作为一个阀安装到StandardContext实例中。
10.6 应用程序
simple-realm
com.lab.tomcat.Bootstrap#main
- ...
- org.apache.catalina.core.StandardContext#start
- ...
- org.apache.catalina.core.ContainerBase#start
- ...
- com.lab.tomcat.core.SimpleContextConfig#lifecycleEvent
- com.lab.tomcat.core.SimpleContextConfig#authenticatorConfig
- 检查是否有安全限制
context.findConstraints();
- 如果有安全限制,检查是否有LoginConfig对象
context.getLoginConfig();
- 一个Context实例只能有一个验证器,当发现某个阀是验证器后,直接返回
- 检查当前Context实例是否有关联的领域对象
context.getRealm()
- 若找到了领域对象,则动态载入
BasicAuthenticator
类,创建该类的一个实例,并作为阀添加到StandardContext中。
- 检查是否有安全限制
- com.lab.tomcat.core.SimpleContextConfig#authenticatorConfig
simple-user-database-realm
com.lab.tomcat.realm.SimpleUserDatabaseRealm#authenticate
- ...
浏览器请求后
org.apache.catalina.connector.http.HttpProcessor#run
- org.apache.catalina.connector.http.HttpProcessor#process
- org.apache.catalina.connector.http.HttpProcessor#parseHeaders
- 获取header上的
authorization
参数 - org.apache.catalina.connector.RequestBase#setAuthorization
- 获取header上的
- org.apache.catalina.core.StandardContext#invoke
- org.apache.catalina.core.StandardPipeline#invoke
- org.apache.catalina.core.StandardPipeline#invokeNext
- org.apache.catalina.authenticator.AuthenticatorBase#invoke
- org.apache.catalina.authenticator.BasicAuthenticator#authenticate
- 从request中取出
authorization
参数 org.apache.catalina.Request#getAuthorization - com.lab.tomcat.realm.SimpleUserDatabaseRealm#authenticate
- 从request中取出
- org.apache.catalina.authenticator.BasicAuthenticator#authenticate
- org.apache.catalina.authenticator.AuthenticatorBase#invoke
- org.apache.catalina.core.StandardPipeline#invokeNext
- org.apache.catalina.core.StandardPipeline#invoke
- org.apache.catalina.connector.http.HttpProcessor#parseHeaders
第十一章 StandardWrapper
11.1 方法调用序列
对于每个引入的HTTP请求,连接器都会调用与其关联的servlet容器的invoke()方法。然后servlet容器会调用其所有子容器的invoke()方法。
回忆下第五章内容,servlet容器包含一条管道和一个或多个阀。
具体过程如下
- 连接器创建请求和响应对象
- 连接器调用 StandardContext 的 invoke 方法
- StandardContext 实例的 invoke 方法调用其pipeline的 invoke 方法,即调用其基础阀 StandardContextValve 的 invoke 方法
- StandardContextValve 的 invoke 方法得到合适的Wrapper实例处理HTTP请求,调用Wrapper实例的 invoke 方法
- StandardWrapper 是Wrapper接口的标准实现,StandardWrapper 实例的 invoke 方法其pipeline对象的 invoke 方法
- StandardWrapper的pipeline对象的基础阀是StandardWrapperValve类的实例。 因此,会调用StandardWrapperValve 的 invoke 方法,StandardWrapperValve 的 invoke 方法会调用Wrapper实例的 allocate 方法获得一个 servlet 的实例。
- allocate 方法调用 load 方法来加载一个 servlet类,若已经载入则无需重复
- load 方法调用 servlet 的 init 方法
- StandardWrapperValve调用servlet实例的service()方法
StandardContext的构造函数中会设置StandardContextValue做基础阀
StandardWrapper的构造函数中会设置StandardWrapperValve做基础阀
本章着重说明servlet实例时如何调用的。 先对SingleThreadModel接口进行介绍,该接口是理解Wrapper如何载入servlet类的关键。
11.2 SingleThreadModel
servlet类可以实现javax.servlet.SingleThreadModel接口,这样的servlet类也称作SingleThreadModel(STM)类。根据Servlet规范,实现此接口的目的是保证servlet实例一次只处理一个请求。
实现SingleThreadModel接口的servlet类只能保证在同一时刻,只有一个线程在执行该servlet实例的service()方法,但为了提高性能,servlet容器会创建多个STM servlet实例。也就是说,STM servlet实例的service()方法会在多个STM servlet实例中并发执行。如果servlet实例需要访问静态类变量或类外的某些资源的话,就有可能引起同步问题。
(在Servlet2.4规范中,SingleThreadModel接口已经弃用了)
11.3 StandardWrapper
StandardWrapper对象的主要任务是载入它所代表的servlet类,并进行实例化。但是,StandardWrapper类并不调用servlet的service方法,而由StandardWrapperValue对象(StandardWrapper实例的pipeline基础阀)完成。StandardWrapperValue对象调用allocate()方法从StandardWrapper实例中获取servlet实例。之后调用servlet实例的service方法。
至于当StandardWrapperValue实例请求servlet实例时,StandardWrapper实例必须考虑到该servlet类是否实现了SingleThreadModel(STM)接口。
对于那些没有实现STM接口的servlet类,只会被StandardWrapper载入一次。之后的请求都访问该类的同一实例。StandardWrapper类不需要多个servlet实例,因为它假设该servlet类的service()方法在多线程环境中是线程安全的。如果必要的话,由servlet程序员来负责同步对共享资源的访问。
而对于一个STM servlet类。StandardWrapper实例必须保证每个时刻只能有一个线程在执行STM servlet类的service()方法。
但是为了更好的性能,StandardWrapper实例会维护一个STM servlet实例池。
Wrapper实例负责准备一个javax.servlet.servletConfig实例,后者在servlet实例内部可以获取到。
- 分配servlet实例
- 载入servlet类
StandardWrapper类不仅实现了Wrapper接口,还实现了javax.servlet.ServletConfig接口
StandardWrapper类并不将自身传递给servlet实例的init()方法。相反,它会在一个StandardWrapperFacade实例中包装自身,将其大部分的公共方法对servlet程序员隐藏起来。
无法单独使用一个Wrapper实例来表示一个servlet类的定义。Wrapper实例必须驻留在某个Context容器中,这样当调用其父容器的getServletContext()方法时才能返回ServletContext类的一个实例。
Wrapper的父容器只能是Context类的实现。
11.4 StandardWrapperFacade类
StandardWrapper实例会调用其所载入的servlet实例的init()方法。init()方法需要一个javax.servlet.ServletConfig实例,而StandardWrapper类本身实现了javax.servlet.ServletConfig接口,所以,理论上StandardWrapper对象可以将自己传入init()方法。但是StandardWrapper需要将大部分公共方法对servlet程序员隐藏起来。为了实现该目的,StandardWrapper类将自身实例包装成StandardWrapperFacade类的一个实例。
当创建StandardWrapper对象调用servlet实例的init()方法时,它会传入StandardWrapperFacade类的一个实例。这样,在servlet实例内调用ServletConfig类的getServletName()、getInitParameter()和getInitParameterNames()方法会直接传递给StandardWrapper类实现的相应方法。
11.5 StandardWrapperValue类
StandardWrapperValue类是StandardWrapper实例中的基础阀,要完成:
- 执行与该servlet实例关联的全部过滤器
- 调用servlet实例的service()方法
11.6 FilterConfig类
过滤器定义
11.7 ApplicationFilterConfig类
ApplicationFilterConfig类用于管理Web应用程序第一次启动时创建的所有过滤器实例。
11.8 ApplicationFilterChain类
StandardWrapperValue类的invoke()方法会创建ApplicationFilterChain类的一个实例,并调用其doFilter()方法。
11.9 应用程序
org.apache.catalina.core.StandardWrapper#allocate
- 对于非STM类,定义了
instance
属性。instance
为null则- org.apache.catalina.core.StandardWrapper#loadServlet 载入相关servlet类
++this.countAllocated;
- 若StandardWrapper表示的servlet类是STM servlet类,则试图从对象池
instancePool
中返回一个servlet实例。- 只要STM servlet实例数不超过指定的最大值,返回一个STM servlet实例。
java
...
synchronized(this.instancePool) {
while(this.countAllocated >= this.nInstances) {
if (this.nInstances < this.maxInstances) {
try {
this.instancePool.push(this.loadServlet());
++this.nInstances;
} catch (ServletException var6) {
throw var6;
} catch (Throwable var7) {
throw new ServletException(ContainerBase.sm.getString("standardWrapper.allocate"), var7);
}
} else {
try {
this.instancePool.wait();
} catch (InterruptedException var8) {
}
}
}
if (this.debug >= 2) {
this.log(" Returning allocated STM instance");
}
++this.countAllocated;
return (Servlet)this.instancePool.pop();
}
(StandardWrapperValve的方法挺一目了然的)
org.apache.catalina.core.StandardWrapperValve#invoke
- 调用StandardWrapper实例的allocate()方法获取该StandardWrapper实例所表示的servlet实例
- 调用私有方法createFilterChain(),创建过滤器链
- 调用过滤器链的doFilter()方法,其中包括调用servlet实例的service()方法。
- 释放过滤器链
- 调用Wrapper实例的deallocate()方法
- 若该servlet类再也不会被使用到,则调用Wrapper实例的unload()方法。
第十二章 StandardContext类
本章无程序
12.1 StandardContext的配置
- 正确设置后,StandardContext对象才能读取并解析默认的web.xml文件,该文件位于%CATALINA_HOME%目录下,该文件的内容会应用到所有部署到Tomcat中的应用程序。
- start()方法要做的一件事,是触发一个生命周期事件。该事件调用监听器。
- 用生命周期监听器配置StandardContext实例,当配置成功后,监听器会将其configured属性置为true。
tomcat5中start()方法与Tomcat4相似,但包含了一些与JMX相关的代码。
- start()方法需要完成
- 触发 BEFORE_START 事件
- 设置 availability 属性为 false
- 设置 configured 属性为 false
- 设置资源
- 设置载入器
- 设置Session管理器
- 初始化字符集映射器
- 启动与该Context容器相关联的组件
- 启动子容器
- 启动管道对象
- 启动Session管理器
- 触发 START 事件,在这里监听器(ContextConfig实例)会进行一系列配置操作,配置成功后,ContextConfig实例会将 StandardContext 实例的 configured 属性设置 为 true
- 检查 configured 属性的值,如果为 true,调用 postWelcomPages方法,加载需要在启动时就载入的子容器,即Wrapper实例,并将available属性设置为true。如果 configured 属性为 false,调用 stop 方法
- 触发 AFTER_START 事件
12.2 StandardContextWrapper类
- 对于每个引入的HTTP请求,都会调用StandardContext实例的管道对象的基础阀的invoke()方法来处理。
- 对于每个引入的HTTP请求,StandardContextValue实例调用Context容器的map()方法,并传入一个org.apache.catalina.Request对象。map()方法(实际上是定义在父类ContainerBase中的)会针对某个特定的协议调用findMapper()方法返回一个映射器对象,然后调用映射器对象的map()方法获取Wrapper实例。
- 在Tomcat4中用Mapper映射servlet
- Tomcat5中,Mapper接口以及其相关类已经移除了。事实上,StandardContextValue类的invoke()方法会从request对象中获取适合的Wrapper实例。
Wrapper wrapper = request.getWrapper();
- 该Wrapper实例指明了封装在request对象中的映射信息。
12.3 对重载的支持
- StandardContext类是通过其载入器实现应用程序重载的。在Tomcat4中,StandardContext对象中的WebappLoader类实现了Loader接口,并使用另一个线程检查WEB-INF目录中的所有类和JAR文件的时间戳。只需要调用其setContainer()方法将WebappLoader对象与StandardContext对象相关联就可以启动该检查线程。
12.4 backgroundProcess()方法
- 这个共享线程在ContainerBase对象中创建。ContainerBase类在其Start()方法中(即,当该容器启动时)嗲用其threadStart()方法启动该后台线程。
- ContainerBackgroundProcessor类时间上是ContainerBase类的内部类。在其run()方法中是一个while循环,周期性地调用其processChildren方法。而processChildred方法会调用自身对象地backgroundProcess()方法和其每个子容器地processChildren()方法。通过实现backgroundProcess()方法,ContainerBase类的子类可以使用一个专用线程来执行周期性任务,例如检查类的时间戳或检查session对象的超过时间。
第十三章 Host和Engine
如果想在同一个Tomcat上部署运行多个Context容器的话,你就需要使用Host容器。当只有一个Context容器时,理论上不需要使用Host容器。在Context接口的描述中有一段话。
Context容器的父容器通常是Host容器,也有可能是其他实现,或者如果不必要,就可以不使用父容器。
13.1 Host接口
- 比较重要的是map方法,该方法返回一个用来处理引入的HTTP请求的Context容器的实例。
13.2 StandardHost类
- 每引入一个HTTP请求,都会调用Host实例的invoke方法。由于StandardHost没有提供invoke方法实现,其会调用父类ContainerBase类的invoke方法,父类方法进而调用StandardHost实例的基础阀StandardHostValue实例的invoke方法。此外,StandardHostValue类的invoke方法会调用StandardHost类的map()方法来获取相应的Context实例来处理HTTP请求。
13.3 StandardHostMapper类
- 在Tomcat4中,ContainerBase类(也就是StandardHost的父类)会调用其addDefaultMapper()方法创建一个默认映射器。默认映射器的类型由mapperClass属性的值决定。
- 此外,StandardHost类的start()方法会在方法末尾调用父类的start()方法,确保默认映射器的创建完成。
Tomcat4中StandardContext类创建默认映射器的方法略有不同。其start()方法不会调用父类的start()方法。相反,StandardContext类的start()方法会调用addDefaultMapper()方法,并传入mapperClass变量来创建默认映射器。
13.4 StandardHostValue类
- 在Tomcat4中,invoke方法调用StandardHost实例的map()方法来获取一个相应Context实例
- 然后,invoke方法会获取与该request对象相关联的session的对象,并调用其access()方法。access()方法会修改session对象的最后访问时间。
13.5 为什么必须要有一个Host容器
- 在Tomcat4和5的实际部署中,若一个Context实例使用ContextConfig对象进行设置,就必须使用一个Host对象。原因:
- 使用ContextConfig对象需要知道应用程序web.xml文件的位置。在其applicationConfig()方法中它会试图打开web.xml文件。
- 其中,Constants.ApplicationWebXml的值为"/WEB-INF/web.xml",web.xml文件的相对路径,servletContext是一个ApplicationContext类型(实现了servletContext接口)的对象。
- ApplicationContext的getResource()代码中限制了如果要使用ContextConfig实例来进行配置的话,Context实例必须有一个Host实例作为父容器。即,除非自己实现ContextConfig类,否则必须使用一个Host容器。
13.7 Engine接口
- 在Engine容器中,可以设置一个默认Host或一个默认Context容器。注意,Engine容器可以与一个服务实例相关联。
13.8 StandardEngine类
- 相比于StandardContext类和StandardHost类,StandardEngine类相对小一些。
- Engine容器可以有子容器,只能是Host容器。
13.9 StandardEngineValue类
- 验证了request和response类型后,invoke()方法得到Host实例,用于处理请求。invoke方法会通过调用Engine实例的map()方法获取Host对象
感觉链路模式差不多,不进行代码调试记录了
第十四章 服务器组件和服务组件
暂略
第十五章 Digester库
暂略
第十六章 关键钩子
例如,在 Tomcat 部署通过实例化一个Server并调用它的 start 方法来启动一个 servlet 容器,该方法又调用其他组件的 start 方法。正常的情况下,可以通过一个关闭命令来让服务器关闭所有组件(如 14 章中介绍)。如果突然地关闭程序,如关闭运行中程序的控制台可能会发生意想不到的事情。
Java提供了方法可以在关闭过程中执行一些代码。
在 Java 中,虚拟机遇到两种事件的时候会关闭虚拟机:
- 应用程序正常退出如System.exit方法被调用,或者最后一个非守护进程线程退出。
- 用户突然强制终止虚拟机,例如键入 CTRL+C 或者在未关闭 Java程序的情况下从系统退出。
幸运的是,虚拟机在执行关闭操作的时候,会有经过以下两个阶段:
- 虚拟机启动所有注册的关闭钩子。关闭钩子是实现在 Runtime 上面注册的线程。所有的关闭钩子会并发执行直到完成任务。
- 虚拟机根据情况调用所有的未调用的终结器(finalizers)
本章重点说明第一个阶段,它允许虚拟机在应用程序中执行一些清理代码。关闭钩子是 java.lang.Thread 类的子类实例,可以如下创建一个关闭钩子:
- 写一个类继承 Thread 类
- 提供你的实现类中的 run 方法。该方法是应用程序被关闭的时候会执行的代码,无论是正常退出还是非正常退出。
- 在应用程序中,实例化关闭钩子类
- 使用当前的 Runtime 类的 addShutdownHook 方法来注册该关闭钩子。
你可能已经注意到,不需要像启动其他线程一样调用关闭钩子的start方法。虚拟机会在它运行其关闭序列时启动并执行关闭钩子。
第十七章 启动Tomcat
windows和linux脚本编写
暂略
第十八章 部署器
暂略
第十九章 Manager应用程序的servlet类
暂略
第二十章 基于JMX的管理
暂略