How Tomcat Works

打印 上一主题 下一主题

主题 1005|帖子 1005|积分 3015

第一章:一个简单的Web服务器

ServerSocket担当http请求,将请求封装为Request,将响应封装为Response,简单返回静态资源
第2章:一个简单的Servlet容器

   总的来说,一个全功能 的servlet 容器会为servlet的每个HTTP请求做下面一些工作:
  1. 当第一次调用servlet的时间,加载该servlet类并调用servlet的init方法(仅仅一 次)。
  2. 对每次请求,构造一个javax.servlet.ServletRequest实例和一个 javax.servlet.ServletResponse 实例。
  3. 调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。
  4. 当servlet类被关闭的时间,调用servlet的destroy方法并卸载servlet类。 
  类加载器创建过程 

   这段代码展示了怎样在 Java 中动态加载类,特别是从指定的目录(`Constants.WEB_ROOT`)加载类。以下是代码的详细解析和关键点阐明:
  ### 代码解析
  ```java
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
String repository = (new URL("file", null, 
    classPath.getCanonicalPath() + File.separator)).toString();
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
```
  #### 1. **`URL[] urls = new URL[1];`**
   - 定义了一个 `URL` 数组,用于存储类加载器的资源库(repository)路径。这里只定义了一个路径,因此数组大小为 1。
  #### 2. **`URLStreamHandler streamHandler = null;`**
   - 定义了一个 `URLStreamHandler` 对象,用于处理 URL 的协议。这里设置为 `null`,表示使用默认的协议处理器。
  #### 3. **`File classPath = new File(Constants.WEB_ROOT);`**
   - 创建了一个 `File` 对象,表示类加载器的根目录。`Constants.WEB_ROOT` 是一个常量,定义了类文件地点的目录路径。
  #### 4. **`String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();`**
   - 构造了一个 `URL` 对象,表示类加载器的资源库路径。这里使用了 `file` 协议,表示本地文件系统路径。
   - `classPath.getCanonicalPath()` 获取了 `Constants.WEB_ROOT` 的规范路径,确保路径是绝对的、唯一的。
   - `File.separator` 确保路径分隔符与操作系统一致(Windows 使用 `\`,Unix/Linux 使用 `/`)。
   - `toString()` 方法将 `URL` 对象转换为字符串形式,用于后续构造 `URLClassLoader`。
  #### 5. **`urls[0] = new URL(null, repository, streamHandler);`**
   - 使用 `repository` 字符串构造了一个 `URL` 对象,并将其存储到 `urls` 数组的第一个位置。
   - 这里再次使用了 `null` 作为协议处理器,表示使用默认的协议处理器。
  #### 6. **`loader = new URLClassLoader(urls);`**
   - 使用 `urls` 数组构造了一个 `URLClassLoader` 对象。`URLClassLoader` 是 Java 提供的动态类加载器,用于从指定的 URL 加载类文件。
   - 这里的 `urls` 数组包含了一个 `file` 协议的 URL,表示类加载器将从 `Constants.WEB_ROOT` 目录加载类文件。
  ### 关键点阐明
  1. **动态加载类**
   - `URLClassLoader` 是 Java 提供的动态类加载器,允许从指定的 URL 加载类文件。这在开辟插件系统、热部署功能或加载第三方库时非常有用。
  2. **规范路径**
   - 使用 `getCanonicalPath()` 确保路径是规范的,克制因路径格式问题导致的错误。比方,它会将 `./` 和 `../` 解析为实际路径。
  3. **默认协议处理器**
   - 在构造 `URL` 和 `URLClassLoader` 时,使用了 `null` 作为协议处理器,表示使用默认的协议处理器。这在大多数情况下是足够的。
  4. **资源库路径**
   - `repository` 字符串表示类加载器的资源库路径。这里使用了 `file` 协议,表示从本地文件系统加载类文件。
  ### 示例:动态加载类
  假设 `Constants.WEB_ROOT` 指向一个包含 `.class` 文件的目录,比方:
```
Constants.WEB_ROOT = "/path/to/classes"
```
  目录结构:
```
/path/to/classes/com/example/MyClass.class
```
  可以使用以下代码动态加载 `MyClass`:
```java
String className = "com.example.MyClass";
Class<?> clazz = Class.forName(className.replace('/', '.'), true, loader);
Object instance = clazz.getDeclaredConstructor().newInstance();
```
  ### 总结
  这段代码展示了怎样使用 `URLClassLoader` 动态加载类文件。通过指定资源库路径(`Constants.WEB_ROOT`)和使用规范路径,可以确保类加载器正确加载类文件。这种机制在开辟机动的、可扩展的应用步伐时非常有用。
   
  facade类 

   try {
  servlet = (Servlet) myClass.newInstance();
  servlet.service((ServletRequest) request,(ServletResponse) response);
  }
  这会危害安全性。知道这个servlet容器的内部运作的Servlet步伐员可以分别把 ServletRequest和ServletResponse实例向下转换为ex02.pyrmont.Request和 ex02.pyrmont.Response,并调用他们的公共方法。拥有一个Request实例,它们就可以调用parse 方法。拥有一个Response实例,就可以调用sendStaticResource方法。 你不可以把parse和sendStaticResource方法设置为私有的,因为它们将会被其他的类调 用。不过,这两个方法是在个servlet内部是不可见的。其中一个办理办法就是让Request和 Response类拥有默认访问修饰,所以它们不能在ex02.pyrmont包的外部使用。不过,这里有一 个更优雅的办理办法:通过使用facade类。 
  RequestFacade实现了ServletRequest接口并通过在构造方法中传递一个引用了 ServletRequest对象的Request实例作为参数来实例化。ServletRequest接口中每个方法的实 现都调用了Request对象的相应方法。然而ServletRequest对象本身是私有的,并不能在类的 外部访问。我们构造了一个RequestFacade对象并把它传递给service方法,而不是向下转换 Request对象为ServletRequest对象并传递给service方法。Servlet步伐员仍旧可以向下转换 ServletRequest实例为RequestFacade,不过它们只可以访问ServletRequest接口里边的公共 方法。如今parseUri方法就是安全的了。
  第3章:连接器 

   Catalina 中有两个主要的模块:连接器和容器。
  一个符合Servlet 2.3和2.4 规 范 的 连 接 器 必 须 创 建 javax.servlet.http.HttpServletRequest 和 javax.servlet.http.HttpServletResponse,并传递给被调用的 servlet 的 service 方法。 
    从本章开始,每章附带的应用步伐都会分成模块。这章的应用步伐由三个模块构成: connector, startup和core。 startup模块只有一个类,Bootstrap,用来启动应用的。connector模块的类可以分为五组:
  1. 连接器和它的支持类(HttpConnector和HttpProcessor)。
  2. 指代HTTP请求的类(HttpRequest)和它的辅助类。
  3. 指代HTTP响应的类(HttpResponse)和它的辅助类。 
  4. Facade类(HttpRequestFacade 和HttpResponseFacade)。
  5. Constant类
  core 模块由两个类构成:ServletProcessor和StaticResourceProcessor。
    第2章中的HttpServer类被分离为两个类:HttpConnector 和HttpProcessor,Request 被 HttpRequest 所代替,而Response被HttpResponse 所代替。
  第2章中的HttpServer类的职责是等待HTTP请求并创建请求和响应对象。在本章的应用中, 等待 HTTP 请求的工作交给 HttpConnector 实例,而创建请求和响应对象的工作交给了 HttpProcessor 实例。 
    本章中,HTTP请求对象由实现了javax.servlet.http.HttpServletRequest的HttpRequest 类来代表。一个 HttpRequest对象将会给转换为一个HttpServletRequest实例并传递给被调用 的servlet 的 service 方法。因此,每个 HttpRequest 实例必须恰当增长字段,以便 servlet 可以使用它们。值需要赋给HttpRequest对象,包括URI,查询字符串,参数,cookies和其他 的头部等等。因为连接器并不知道被调用的servlet需要哪个值,所以连接器必须从HTTP请求 中解析所有可获得的值。不过,解析一个HTTP请求扳连昂贵的字符串和其他操作,假如只是解 析servlet 需要的值的话,连接器就能节流很多CPU周期。比方,假如servlet不 解析任何一 个请求参数(比方不调用 javax.servlet.http.HttpServletRequest 的 getParameter, getParameterMap,getParameterNames 或者 getParameterValues 方法),连接器就不需要从查询 字符串或者 HTTP 请求内容中解析这些参数。Tomcat 的默认连接器(和本章应用步伐的连接器) 试图不解析参数直到servlet真正需要它的时间,通过如许来获得更高效率。 
    Tomcat的默认连接器和我们的连接器使用SocketInputStream类来从套接字的InputStream 中读取字节流。一个 SocketInputStream 实例对从套接字的 getInputStream 方法中返回的 java.io.InputStream 实例举行包装。 SocketInputStream 类提供了两个紧张的方法: readRequestLine 和 readHeader。readRequestLine 返回一个 HTTP 请求的第一行。比方,这行 包括了URI,方法和HTTP 版本。因为从套接字的输入流中处理字节流意味着只读取一次,从第 一个字节到最后一个字节(并且不回退),因此readHeader 被调用之前,readRequestLine 必须 只被调用一次。readHeader每次被调用来获得一个头部的名/值对,并且应该被重复的调用知道 所有的头部被读取到。readRequestLine 的返回值是一个 HttpRequestLine 的实例,而 readHeader 的返回值是一个 HttpHeader 对象。 
  第四章:Tomcat 的默认连接器 

   本章中提及的“默认连接器”是指Tomcat4的默认连接器。即使默认的连呆板已经被弃用, 被更快的,代号为Coyote的连接器所代替,它仍旧是一个很好的学习工具。
   Tomcat 连接器是一个可以插入servlet 容器的独立模块,已经存在相当多的连接器了,包 括Coyote, mod_jk, mod_jk2 和 mod_webapp。一个 Tomcat 连接器必须符合以下条件:
  1. 必须实现接口org.apache.catalina.Connector。
  2. 必须创建请求对象,该请求对象的类必须实现接口org.apache.catalina.Request。
  3. 必须创建响应对象,该响应对象的类必须实现接口org.apache.catalina.Response。
    Tomcat4 的默认连接器雷同于第3章的简单连接器。它等待前来的HTTP请求,创建request 和 response 对象,然后把 request 和 response 对象传递给容器。连接器是通过调用接口 org.apache.catalina.Container 的 invoke 方法来传递 request 和 response 对象的。invoke 的方法署名如下所示:
  public void invoke( org.apache.catalina.Request request, org.apache.catalina.Response response); 
  在invoke 方法里边,容器加载servlet,调用它的service 方法,管剖析话,记录堕落日 志等等。
  
 

第五章 容器 

容器接口

   容器是一个处理用户servlet请求并返回对象给web用户的模块。 org.apache.catalina.Container 接口定义了容器的形式,有四种容器:Engine (引擎), Host(主机), Context(上下文), 和 Wrapper(包装器)。这一 章将会先容context和wrapper,而 Engine和Host会留到第十三章先容。 
  对于Catalina的容器首先需要注意的是它一共有四种不同的容器:
  Engine:表示整个Catalina的servlet引擎 ·
  Host:表示一个拥有数个上下文的假造主机 ·
  Context:表示一个Web应用,一个context包含一个或多个wrapper
  Wrapper:表示一个独立的servlet
  

  Pipelining Tasks 

    一个pipeline包含了该容器要唤醒的所有使命。每一个阀门表示了一个特定的 使命。一个容器的流水线有一个基本的阀门,但是你可以添加任意你想要添加的 阀门。阀门的数目定义为添加的阀门的个数(不包括基本阀门)。风趣的是,阀门可以通过编辑Tomcat的配置文件server.xml来动态的添加。
  一个容器可以有一个流水线。当容器的invoke方法被调用的时间,容器将会处 理流水线中的阀门,并一个接一个的处理,直到所有的阀门都被处理完毕。可以 想象流水线的invoke方法的伪代码如下所示:
  

  但是,Tomcat的设计者选择了一种不同的通过 org.apache.catalina.ValveContext 定义的方式来处理,这里先容它怎样工作 的:
  

  Pipeline 是容器中Pipeline接口的一个实例。 如今,流水线必须保证说要添加给它的阀门必须被调用一次,流水线通过创建一 个ValveContext 接口的实例来实现它。ValveContext是流水线的的内部类,这 样ValveContext 就可以访问流水线中所有的成员。ValveContext中最紧张的方 法是invokeNext 方法:
  

  在创建一个ValveContext实例之后,流水线调用ValveContext的invokeNext 方法。ValveContext 会先唤醒流水线的第一个阀门,然后第一个阀门会在完成 它的使命之前(或之后)唤醒下一个阀门。ValveContext将它本身传递给每一个阀门,那 么该阀门就可以调用ValveContext的invokeNext方法。Valve接口的invoke 署名如下:
  假如一个Valve先调用invokeNext,再实行本身的逻辑代码,那么这是一个后置阀门,即该Valve会在basic valve实行后再实行本身的逻辑代码。
  假如一个Valve先实行本身的逻辑代码,再调用invokeNext,那么这是一个前置阀门,即该Valve会在basic valve实行前实行本身的逻辑代码。
  这是使用责任链模式的长处,即valve可以通过控制invokeNext的位置,从而控制该valve是在basic valve前或者后实行本身的逻辑代码。for循环就无法提供这种功能。
  总体来说pipeline中存放一个valve列表,表示所有需要实行的valve,以及一个basic valve(用来实行容器的原来使命,如调用servlet的service方法)。ValveContext负责维护一个索引用来指向当前需要实行的valve。ValveContext的invokeNext方法会实行索引指向的valve。pipeline中整个valve列表的实行由valve在其invoke方法中通过调用ValveContext的invokeNext方法驱动。详细实行流程如下:
  1. 首先pipeline调用ValveContext的invokeNext方法,实行第一个valve
  2. valve的invoke方法中调用ValveContext的invokeNext方法驱动着对整个valve列表的递归调用,那些前置valve的逻辑代码会被调用
  3. 直到basic valve,其invoke方法不再调用ValveContext的invokeNext方法
  4. 整个递归方法栈开始逐个弹出,那些后置valve开始从后向前的实行各自的逻辑代码
   
  相关接口:
  

  

  

  

  Wrapper 接口 

    org.apache.catalina.Wrapper 接口表示了一个包装器。一个包装器是表示一个 独立servlet定义的容器。包装器继续了Container接口,并且添加了几个方法。 包装器的实现类负责管理其下层servlet的生命周期,包括servlet的 init,service,和 destroy 方法。由于包装器是最底层的容器,所以不可以将子 容器添加给它。假如addChild方法被调用的时间会产生 IllegalArgumantException 非常。
  包装器接口中紧张方法有allocate和load方法。allocate方法负责定位该包 装器表示的servlet的实例。Allocate方法必须考虑一个servlet是否实现了 avax.servlet.SingleThreadModel 接口,该部门内容将会在 11章中举行讨论。 Load 方法负责load和初始化servlet的实例。它们的署名如下:
  

  Context接口

   一个context在容器中表示一个web应用。一个context通常含有一个或多个包 装器作为其子容器。 紧张的方法包括addWrapper, createWrapper 等方法。该接口将会在第12章中 详细先容。
  SimpleWrapper 

   SimpleWrapper 实现了 Wrapper 接口。SimpleWrapper 类包括一个Pipeline和一个 Loader 类(来加载一个 servlet)。流水线包 括一个基本阀门和两个别的的阀门 ClientIPLoggerValve 和HeaderLoggerValve.该应用的类结构图如图 5.3所示: 
  

  SimpleContext

   SimpleContext表示一个上下文,它使用SimpleContextMapper作为它的mapper, SimpleContextValve 作为它的基本阀门。该上下文包括两个阀门 ClientIPLoggerValve 和 HeaderLoggerValve。用 SimpleWrapper 表示的两个包装器作为该上下文的子容器被添加。包装器把SimpleWrapperValve作为它的基 本阀门,但是没有其它的阀门了。 运行流程如下:
  1. 一个容器有一个流水线,容器的invoke方法会调用流水线的 invoke 方法。
  2. 流水线的invoke方法会调用添加到容器中的阀门的invoke方法, 然后调用基本阀门的invoke方法。
  3. 在一个包装器中,基本阀门负责加载相关的servlet类并对请求作 出相应。
  4. 在一个有子容器的上下文中,基本法门使用mapper来查找负责处 理请求的子容器。假如一个子容器被找到,子容器的invoke方法会被调 用,然后返回步调1。
   
  第六章 生命周期 

   Catalina 由多个组件构成,当Catalina启动的时间,这些组件也会启动。当 Catalina 停止的时间,这些组件也必须有机会被清除。比方,当一个容器停止 工作的时间,它必须唤醒所有加载的servlet的destroy方法,而session管理 器要生存session到二级存储器中。保持组件启动和停止一致的的机制通过实现 org.apache.catalina.Lifecycle 接口来实现。
  一个实现了Lifecycle接口的组件同是会触发一个或多个下列变乱: BEFORE_START_EVENT, START_EVENT, AFTER_START_EVENT, BEFORE_STOP_EVENT, STOP_EVENT, and AFTER_STOP_EVENT。当组件被启动的时间前三个变乱会被触发, 而组件停止的时间会触发后边三个变乱。别的,假如一个组件可以触发变乱,那 么必须存在相应的监听器来对触发的变乱作出回应。监听器使用 org.apache.catalina.LifecycleListener 来表示。 
  Lifecycle 接口  

   Catalina 的设计允许一个组件包含其它的组件。比方一个容器可以包含一系列 的组件如加载器、管理器等。一个父组件负责启动和停止其子组件。Catalina 的设计成所有的组件被一个父组件来管理(in custody),所以启动bootstrap 类只需启动一个组件即可。这种单一的启动停止机制通过继续Lifecycle来实现。
  

  LifecycleEvent类 

  
  1. public final class LifecycleEvent extends EventObject {
  2.   public LifecycleEvent(Lifecycle lifecycle, String type) {
  3.     this(lifecycle, type, null);
  4.   }
  5.   public LifecycleEvent(Lifecycle lifecycle, String type,
  6.     Object data) {
  7.   
  8.     super(lifecycle);
  9.     this.lifecycle = lifecycle;
  10.     this.type = type;
  11. this.data = data;
  12. }
  13. private Object data = null;
  14. private Lifecycle lifecycle = null;
  15. private String type = null;
  16. public Object getData() {
  17. return (this.data);
  18. }
  19. public Lifecycle getLifecycle() {
  20. return (this.lifecycle);
  21. }
  22. public String getType() {
  23. return (this.type);
  24. }
  25. }
复制代码
LifecycleListener 接口 


 
LifecycleSupport 类

  1. public final class LifecycleSupport {
  2.   public LifecycleSupport(Lifecycle lifecycle) {
  3.     super();
  4.     this.lifecycle = lifecycle;
  5.   }
  6.   
  7.   private Lifecycle lifecycle = null;
  8.   private LifecycleListener listeners[] = new LifecycleListener[0];
  9.   public void addLifecycleListener(LifecycleListener listener) {
  10.     synchronized (listeners) {
  11.       LifecycleListener results[] =
  12.         new LifecycleListener[listeners.length + 1];
  13.       for (int i = 0; i < listeners.length; i++)
  14.         results[i] = listeners[i];
  15.       results[listeners.length] = listener;
  16.         listeners = results;
  17.     }
  18.   }
  19.   
  20.   public LifecycleListener[] findLifecycleListeners() {
  21.     return listeners;
  22.   }
  23.   
  24.   public void fireLifecycleEvent(String type, Object data) {
  25.     LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
  26.     LifecycleListener interested[] = null;
  27.     synchronized (listeners) {
  28.       interested = (LifecycleListener[]) listeners.clone();
  29.     }
  30.     for (int i = 0; i < interested.length; i++)
  31.       interested[i].lifecycleEvent(event);
  32.   }
  33.   
  34.   public void removeLifecycleListener(LifecycleListener listener) {
  35.     synchronized (listeners) {
  36.       int n = -1;
  37.       for (int i = 0; i < listeners.length; i++) {
  38.         if (listeners[i] == listener) {
  39.           n = i;
  40.           break;
  41.         }
  42.       }
  43.       if (n < 0)
  44.         return;
  45.       LifecycleListener results[] =
  46.         new LifecycleListener[listeners.length - 1];
  47.   
  48.       int j = 0;
  49.       for (int i = 0; i < listeners.length; i++) {
  50.         if (i != n)
  51.         results[j++] = listeners[i];
  52.       }
  53.       listeners = results;
  54.     }
  55.   }
  56. }
复制代码
 SimpleContext

  1. public void addLifecycleListener(LifecycleListener listener) {
  2. lifecycle.addLifecycleListener(listener);
  3. }
  4. public LifecycleListener[] findLifecycleListeners() {
  5. return null;
  6. }
  7. public void removeLifecycleListener(LifecycleListener listener) {
  8. lifecycle.removeLifecycleListener(listener);
  9. }
  10. public synchronized void start() throws LifecycleException {
  11.   if (started)
  12.     throw new LifecycleException("SimpleContext has already started");
  13.   // Notify our interested LifecycleListeners
  14.   lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
  15.   started = true;
  16.   try {
  17.     // Start our subordinate components, if any
  18.     if ((loader != null) && (loader instanceof Lifecycle))
  19.       ((Lifecycle) loader).start();
  20.   
  21.     // Start our child containers, if any
  22.     Container Children[] = findChildren();
  23.     for (int i = 0; i < children.length; i++) {
  24.       if (children[i] instanceof Lifecycle)
  25.         ((Lifecycle) children[i]).start();
  26.     }
  27.   
  28.     // Start the Valves in our pipeline (including the basic),
  29.     // if any
  30.     if (pipeline instanceof Lifecycle)
  31.       ((Lifecycle) pipeline).start();
  32.     // Notify our Interested LifecycleListeners
  33.     lifecycle.firelifecycleEvent(START_EVENT, null);
  34.   }
  35.   catch (Exception e) {
  36.     e.printStackTrace();
  37.   }
  38.   // Notify our interested LifecycleListeners
  39.   lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
  40. }
  41.   
  42. public void stop() throws LifecycleException {
  43.   if (!started)
  44.     throw new LifecycleException("SimpleContext has not been started");
  45.   // Notify our interested LifecycleListeners
  46.   lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
  47.   lifecycle.fireLifecycleEvent(STOP_EVENT, null);
  48.   started = false;
  49.   try {
  50.     // Stop the Valves in our pipeline (including the basic), if any
  51.     if (pipeline instanceof Lifecycle) (
  52.   
  53.       ((Lifecycle) pipeline).stop();
  54.     }
  55.     // Stop our child containers, if any
  56.     Container children[] = findChildren();
  57.     for (int i = 0; i < children.length; i++) {
  58.       if (children[i] instanceof Lifecycle)
  59.         ((Lifecycle) children[i]).stop();
  60.     }
  61.     if ((loader != null) && (loader instanceof Lifecycle)) {
  62.       ((Lifecycle) loader).stop();
  63.     }
  64.   }
  65.   catch (Exception e) {
  66.     e.printStackTrace();
  67.   }
  68.   // Notify our interested LifecycleListeners
  69.   lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
  70. }
复制代码
第七章:日志系统

Logger接口

  1. public interface Logger {
  2.   public static final int FATAL = Integer.MIN_VALUE;
  3.   public static final int ERROR = 1;
  4.   public static final int WARNING = 2;
  5.   public static final int INFORMATION = 3;
  6.   public static final int DEBUG = 4;
  7.   
  8.   public Container getContainer();
  9.   public void setContainer(Container container);
  10.   public String getInfo();
  11.   public int getVerbosity();
  12.   public void setVerbosity(int verbosity);
  13.   public void addPropertyChangeListener(PropertyChangeListener
  14.     listener);
  15.   public void log(String message);
  16.   
  17.   public void log(Exception exception, String msg);
  18.   public void log(String message, Throwable throwable);
  19.   public void log(String message, int verbosity);
  20.   public void log(String message, Throwable throwable, int verbosity);
  21.   public void removePropertyChangeListener(PropertyChangeListener
  22.     listener);
  23. }
复制代码
Tomcat 日志系统 

 
 
 第八章:加载器

   。假如像前面章节中那样使用系统的加载器来加载servlet和其他需要的类, 如许servlet就可以进入Java假造机CLASSPATH情况下面的任何类和类库,这 会带来安全隐患。Servlet只允许访问WEB-INF/目录及其子目录下面的类以及部 署在WEB-INF/lib目录下的类库。所以一个servlet容器需要一个本身的加载器, 该加载器遵守一些特定的规则来加载类。在Catalina中,加载器使用 org.apache.catalina.Loader 接口表示。
  Tomcat 需要一个本身的加载器的另一个原因是它需要支持在WEB-INF/classes 或者是WEB-INF/lib目录被改变的时间会重新加载。Tomcat的加载器实现中使 用一个单独的线程来检查servlet和支持类文件的时间戳。要支持类的主动加载 功能,一个加载器类必须实现org.apache.catalina.loader.Reloader接口。
   Java 类加载器

   在每次创建一个Java类的实例时间,必须先将该类加载到内存中。Java假造机 (JVM)使用类加载器来加载类。Java加载器在Java焦点类库和CLASSPATH环 境下面的所有类中查找类。假如需要的类找不到,会抛出 java.lang.ClassNotFoundException 非常。 从J2SE1.2 开始,JVM使用了三种类加载器:bootstrap类加载器、extension 类加载器和systen类加载器。这三个加载器是父子关系,其中bootstrap类加 载器在顶端,而system加载器在结构的最底层。
   
  其中bootstrap类加载器用于引导JVM,一旦调用java.exe步伐,bootstrap 类加载器就开始工作。因此,它必须使用本地代码实现,然后加载JVM需要的类 到函数中。别的,它还负责加载所有的Java焦点类,比方java.lang和java.io 包。别的bootstrap类加载器还会查找焦点类库如rt.jar、i18n.jar等,这些 类库根据JVM和操作系统来查找。
   
  extension 类加载器负责加载尺度扩展目录下面的类。如许就可以使得编写步伐 变得简单,只需把JAR文件拷贝到扩展目录下面即可,类加载器会主动的在下面 查找。不同的供应商提供的扩展类库是不同的,Sun公司的JVM的尺度扩展目录 是/jdk/jre/lib/ext。
   
  system 加载器是默认的加载器,它在情况变量CLASSPATH目录下面查找相应的 类。
   
  如许,JVM使用哪个类加载器?答案在于委派模子(delegation model),这是出 于安全原因。每次一类需要加载,system 类加载器首先调用。但是,它不会马 上加载类。相反,它委派该使命给它的父类-extension 类加载器。extension 类加载器也把使命委派给它的父类bootstrap类加载器。因此,bootstrap类加 载器总是首先加载类。假如 bootstrap 类加载器不能找到所需要的类的 extension 类加载器会实验加载类。假如扩展类加载器也失败,system类加载器 将实行使命。假如系统类加载器找不到类,一个 java.lang.ClassNotFoundException 非常。为什么需要如许的来回模式? 委派模子对于安全性是非常紧张的。如你所知,可以使用安全管理器来限制访问 某个目录。如今,恶意的意图有人能写出一类叫做 java.lang.Object,可用于 访问任何在硬盘上的目录。因为JVM的信任java.lang.Object类,它不会关注 这方面的运动。因此,假如自定义java.lang.Object 被允许加载的安全管理器 将很容易瘫痪。幸运的是,这将不会发生,因为委派模子会克制这种情况的发生。 下面是它的工作原理。当自定义java.lang.Object类在步伐中被调用的时间,system类加载器将该请 求委派给extension类加载器,然后委派给bootstrap类加载器。如许bootstrap 类加载器先搜索的焦点库,找到尺度java.lang.Object并实例化它。如许,自 定义java.lang.Object 类永远不会被加载。
  Tomcat的需求自定义本身的类加载器原因包括以下内容
  1. 要订定类加载器的某些特定规则(一个context的类加载器只能加载本身目录下的类,无法加载其他应用的类)
  2. 缓存以前加载的类
  3. 事先加载类以准备使用
  Loader 接口 

   在Web应用步伐中加载servlet和其他类需要遵循一些规则。比方,在一个应用步伐中Servlet可以使用部署到WEB-INF/classes目录和任何子目录下面的类。 然而,servlet不能访问其他应用的类,即使这些类是在运行Tomcat 地点的JVM 的CLASSPATH 中。别的,一个servlet只能访问WEB-INF/lib目录下的类库,而不能访问其他目录下面的。
  一个Tomcat类加载器表示一个Web应用步伐加载器,而不是一个类加载器。一 个加载器必须实现org.apache.catalina.Loader接口。加载器的实现使用定制的类加载器org.apache.catalina.loader.WebappClassLoader。可以使用 Loader 接口的getClassLoader 方法获取一个WebappClassLoader。
  值得一提的是Loader接口定义了一系列方法跟库协作。Web应用步伐的 WEB-INF/classes 和 WEB-INF/lib 目录作为库添加上。Loader接口的 addReposity 方法用于添加一个库,findRepositories方法用于返回一个所有库 的队列。
  一个Tomcat的加载器通常跟一个上下文相关联,Loader接口的和getContainer 及setContainer 方法是创建此关联。一个加载器还可以支持重新加载,假如在 上下文中的一个或多个类已被修改。如许,一个 servlet 步伐员可以重新编译 servlet 或辅助类,新类将被重新加载而不需要不重新启动 Tomcat 加载。为了 到达重新加载的目的,Loader 接口有modify方法。在加载器的实现中,假如在其 库中一个或多个类已被修改,modeify方法必须返回true,因此需要重新加载。 一个加载器不是本身举行重新加载,而是调用上下文接口的重载方法。。别的两个方法, setReloadable 和 getReloadable,用于确定加载器中是否可以使用重加载。默认情况下,在尺度的上下文实现中(org.apache.catalina.core.StandardContext 类将在第 12 章讨论)重载机制并未启用。因此,要使得上下文启动重载机制,需要在server.xml文件添加一 些元素如下:
  

  别的,一个加载器的实现可以确定是否委派给父加载器类。为了实现这一点, Loader 接口提供了getDelegate 和setDelegate方法。
  1. public interface Loader {
  2. public ClassLoader getClassLoader();
  3. public Container getContainer();
  4. public void setContainer(Container container);
  5. public DefaultContext getDefaultContext();
  6. public void setDefaultContext(DefaultContext defaultContext);
  7. public boolean getDelegate();
  8. public void setDelegate(boolean delegate);
  9. public String getInfo();
  10. public boolean getReloadable();
  11. public void setReloadable(boolean reloadable);
  12. public void addPropertyChangeListener(PropertyChangeListener
  13. listener);
  14. public void addRepository(String repository);
  15. public String[] findRepositories();
  16. public boolean modified();
  17. public void removePropertyChangeListener(PropertyChangeListener
  18. listener);
  19. }
复制代码
Catalina 提供了org.apache.catalina.loader.WebappLoader 作为Load接口的 实现。WebappLoader 对象包含一个 org.apache.catalina.loader.WebappClassLoader 类的实例,该类扩展了 Java.netURLClassLoader 类。
  

  

  WebappLoader 类 

   org.apache.catalina.loader.WebappLoader 类是 Loader 接口的实现,它表示 一个web应用步伐的加载器,负责给web应用步伐加载类。WebappLoader创建 一个org.apache.catalina.loader.WebappClassLoader 类的实例作为它的类加 载器。像其他的Catalina组件一样,WebappLoader实现了 org.apache.catalina.Lifecycle 接口,可有由关联容器启动和停止。 WebappLoader 类还实现了java.lang.Runnable 接口,所以可以通过一个线程来 重复的调用modified方法,假如modified方法返回true,WebappLoader实例通知它的关联容器。类通过上下文重新加载本身,而不是WebappLoader。上下文怎么实现该功能会在第12章尺度上下文中先容。 
  WebappLoader 类的 start 方法被调用的时间,将会完成下面几项紧张使命:
  创建一个类加载器
  设置库
  设置类路径
  设置访问权限
  开启一个新线程用来举行主动重载
  1. 创建类加载器:
  WebappLoader 使用一个内部类加载器来加载类。可以转头看Loader接口,该接 口提供了getClassLoader方法但是并没有setClassLoader方法。因此,不能通 过传递一个WebappLoader来初始化它。如许没有默认类加载器是否意味着 WebappLoader 不敷机动的? 答案当然是否定的,WebappLoader类提供了getLoaderClass 和 setLoaderClass 方法来获得或者改变它的私有变量loaderClass的值。该变量 是一个的表示加载器类名String类型表示形式。默认的loaderClass值是 org.apahce.catalina.loader.WebappClassLoader,假如你愿意,可以创建继续 WebappClassLoader 类的本身的加载器,然后使用setLoaderClass方法来强制 WebappLoader 使用你创建的加载器。否则,当它WebappLoader启动的时间,它 会使用它的私有方法createClassLoader创建WebappClassLoader的实例
  1. private WebappClassLoader createClassLoader() throws Exception {
  2.   Class clazz = Class.forName(loaderClass);
  3.   WebappClassLoader classLoader = null;
  4.   if (parentClassLoader == null) {
  5.     // Will cause a ClassCast if the class does not extend
  6.     // WebappClassLoader, but this is on purpose (the exception will be
  7.     // caught and rethrown)
  8.     classLoader = (WebappClassLoader) clazz.newInstance();
  9.     // in Tomcat 5, this if block is replaced by the following:
  10.     // if (parentClassLoader == null) {
  11.     //   parentClassLoader =
  12.     //     Thread.currentThread().getContextClassLoader();
  13.     // }
  14.   
  15.   }
  16.   else {
  17.     Class[] argTypes = { ClassLoader.class };
  18.     Object[] args = { parentClassLoader };
  19.     Constructor constr = clazz.getConstructor(argTypes);
  20.     classLoader = (WebappClassLoader) constr.newInstance(args);
  21.   }
  22.   return classLoader;
  23. }
复制代码
2. 设置库
  WebappLoader的start方法会调用setRepositories方法来给类加载器添加一 个库。WEB-INF/classes目录传递给加载器addRepository方法,而WEB-INF/lib 传递给加载器的setJarPath方法。如许,类加载器能能从WEB-INF/classes 目 录下面和WEB-INF/lib目录下面部署的类库里加载类。
  3. 设置类路径
  该使命由start方法调用setClassPath方法完成,setClassPath方法会给 servlet上下文分配一个String类型属性生存Jasper JSP编译的类路径,该内 容先不予讨论。
  4. 设置访问权限
  假如Tomcat使用了安全管理器,setPermissions给类加载器给必要的目录添加 访问权限,比方WEB-INF/classes和WEB-INF/lib。假如不使用管理器,该方法 马上返回。
  5. 开启主动重载线程
  WebappLoader支持主动重载,假如WEB-INF/classes或者WEB-INF/lib目录被 重新编译过,在不重启Tomcat的情况下必须主动重新载入这些类。为了实现这 个目的,WebappLoader有一个单独的线程每个x秒会检查源的时间戳。x的值由 checkInterval变量定义,它的默认值是15,也就是每隔15秒会举行一次检查 是否需要主动重载。该类还提供了两个方法getCheckInterval和 setCheckInterval方法来访问或者设置checkInterval的值。
  在Tomcat4中,WebappLoader实现了java.lang.Runnable接口来支持主动重载。
  1. public void run() {
  2.   if (debug >= 1)
  3.     log("BACKGROUND THREAD Starting");
  4.   
  5.   // Loop until the termination semaphore is set
  6.   while (!threadDone) {
  7.     // Wait for our check interval
  8.     threadSleep();
  9.     if (!started)
  10.       break;
  11.     try {
  12.       // Perform our modification check
  13.       if (!classLoader.modified())
  14.         continue;
  15.     }
  16.     catch (Exception e) {
  17.       log(sm.getString("webappLoader.failModifiedCheck"), e);
  18.       continue;
  19.     }
  20.     // Handle a need for reloading
  21.     notifyContext();
  22.     break;
  23.   }
  24.   
  25.   if (debug >= 1)
  26.     log("BACKGROUND THREAD Stopping");
  27. }
  28. private void notifyContext() {
  29. WebappContextNotifier notifier = new WebappContextNotifier();
  30. (new Thread(notifier)).start();
  31. }
  32. protected class WebappContextNotifier implements Runnable {
  33. public void run() {
  34. ((Context) container).reload();
  35. }
  36. }
复制代码
 
  WebappClassLoader 类 

   类org.apache.catalina.loader.WebappClassLoader 表示在一个 web 应用步伐 中使用的加载器。WebappClassLoader类继续了java.net.URLClassLoader 类, 该类在前面章节中用于加载Java类。
  WebappClassLoader 被可以的举行了优化和安全方面的考虑。比方它缓存了以前 加载的类以改进性能,下一次收到第一次没有找到的类的请求的时间,可以直接 抛出ClassNotFound 非常。WebappClassLoader 在源列表以及特定的JAR文件中 查找类。
  处于安全性的考虑,WebappClassLoader类不允许一些特定的类被加载。这些类 被存储在一个String类型的数组中,如今仅仅有一个成员。
  
 别的在委派给系统加载器的时间,你也不允许加载属于该包的其它类或者它的子 包:
  

  缓存:
  为了提高性能,当一个类被加载的时间会被放到缓存中,如许下次需要加载该类 的时间直接从缓存中调用即可。缓存由WebappClassLoader类实例本身管理。另 外,java.lang.ClassLoader 维护了一个Vector,可以克制前面加载过的类被当 做垃圾接纳掉。在这里,缓存被该超类管理。 每一个可以被加载的类(放在 WEB-INF/classes 目录下的类文件或者 JAR 文件) 都被当做一个源。一个源被org.apache.catalina.loader.ResourceEntry类表 示。一个ResourceEntry实例生存一个byte类型的数组表示该类、最后修改的 数据或者副本等等。
  1. public class ResourceEntry {
  2. public long lastModifled = -1;
  3. // Binary content of the resource.
  4. public byte[] binaryContent = null;
  5. public Class loadedClass = null;
  6. // URL source from where the object was loaded.
  7. public URL source = null;
  8. // URL of the codebase from where the object was loaded.
  9. public URL CodeBase = null;
  10. public Manifest manifest = null;
  11. public Certificate[] certificates = null;
  12. }
复制代码
所有缓存的源被存放在一个叫做resourceEntries的HashMap中,键值为源名, 所有找不到的源都被放在一个名为notFoundResources的HashMap中。
   
  加载类:
  当加载一个类的时间,WebappClassLoader类遵循以下规则:
  · 所有加载过的类都要举行缓存,所以首先需要检查本地缓存。
  · 假如无法再本地缓存找到类,使用java.langClassLoader类 的findLoaderClass 方法在缓存查找类、
  · 假如在两个缓存中都无法找到该类
  · 假如使用了安全管理器,检查该类是否允许加载,假如该类不允许加载,则抛出ClassNotFoundException 非常。
  · 假如要加载的类使用了委派标记或者该类属于trigger包中, 使用父加载器来加载类,假如父加载器为null,使用系统加载器加载。
  · 从当前的源中加载类
  · 假如在当前的源中找不到该类并且没有使用委派标记,使用父类加载器。假如父类加载器为null,使用系统加载器
  · 假如该类仍旧找不到,抛出ClassNotFoundException非常
    一个应用步伐加载器,简单的说就是加载器是Catalina中最紧张的组件之一。 它使用一个内部的类加载器来完成加载类的工作。Tomcat使用该内部类加载器 加载应用类,它属于一个应用上下文并且遵循一系列规则。别的,该加载器还支 持缓存以及检测类修改情况的功能。 
  第九章:session管理 

   Catalina通过一个叫管理器的组件来完成session管理工作,该组件由 org.apache.catalina.Manager interface接口表示。一个管理器通常跟一个上 下文容器相关联,它负责创建、更行以及销毁session对象并能给任何请求组件 返回一个合法的session。
  一个servlet可以使用getSession方法获得一个session对象,该方法在 javax.servlet.http.HttpServletRequest定义。它在默认连接器里由 org.apache.catalina.connector.HttpRequestBase类实现。这里是 HttpRequestBase类的一些相关方法。
  1. public HttpSession getSession() {
  2.   return (getSession(true));
  3. }
  4. public HttpSession getSession(boolean create) {
  5.   ...
  6.   return doGetSession(create);
  7. }
  8. private HttpSession doGetSession(boolean create) {
  9.   // There cannot be a session if no context has been assigned yet
  10.   if (context == null)
  11.     return (null);
  12.   // Return the current session if it exists and is valid
  13.   if ((session != null) && !session.isValid())
  14.     session = null;
  15.   if (session != null)
  16.     return (session.getSession());
  17.   
  18.   // Return the requested session if it exists and is valid
  19.   Manager manager = null;
  20.   if (context != null)
  21.     manager = context.getManager();
  22.   if (manager == null)
  23.     return (null);      // Sessions are not supported
  24.   if (requestedSessionId != null) {
  25.     try {
  26.       session = manager.findSession(requestedSessionId);
  27.     }
  28.   
  29.     catch (IOException e) {
  30.       session = null;
  31.     }
  32.     if ((session != null) && !session.isValid())
  33.       session = null;
  34.     if (session != null) {
  35.       return (session.getSession());
  36.     }
  37.   }
  38.   
  39.   // Create a new session if requested and the response is not
  40.   // committed
  41.   if (!create)
  42.     return (null);
  43.   ...
  44.   session = manager.createSession();
  45.   if (session != null)
  46.     return (session.getSession());
  47.   else
  48.     return (null);
  49. }
复制代码
默认情况下管理器将session对象存储在内存中,但是Tomcat也允许将session 对象存储在文件或者数据库中(通过JDBC)。Catalina在 org.apache.catalina.session包中提供了session对象和session管理的相关 类型。
  Sessions 

   在servlet编程中,一个session对象使用javax.servlet.http.HttpSession 接口表示。该接口的尺度实现是StandardSession类,该类在 org.apache.catalina.session包中。但是出于安全的原因,管理器并不会将一 个StandardSession实例传递给servlet。而是使用 org.apache.catalina.session包中的外观类StandardSessionFacade。 在内部,一 个管理器使用了另一个接口:org.apache.catalina.Session。
  

  1. public interface Session {
  2.   public static final String SESSION_CREATED_EVENT = "createSession";
  3.   public static final String SESSION_DESTROYED_EVENT =
  4.     "destroySession";
  5.   public String getAuthType();
  6.   public void setAuthType(String authType);
  7.   public long getCreationTime();
  8.   public void setCreationTime(long time);
  9.   public String getId();
  10.   public void setId(String id);
  11.   public String getInfo();
  12.   public long getLastAccessedTime();
  13.   public Manager getManager();
  14.   public void setManager(Manager manager);
  15.   public int getMaxInactiveInterval();
  16.   public void setMaxInactiveInterval(int interval);
  17.   public void setNew(boolean isNew);
  18.   public Principal getPrincipal();
  19.   public void setPrincipal(Principal principal);
  20.   public HttpSession getSession();
  21.   public void setValid(boolean isValid);
  22.   public boolean isValid();
  23.   
  24.   public void access();
  25.   public void addSessionListener(SessionListener listener);
  26.   public void expire();
  27.   public Object getNote(String name);
  28.   public Iterator getNoteNames();
  29.   public void recycle();
  30.   public void removeNote(String name);
  31. public void removeSessionListener(SessionListener listener);
  32. public void setNote(String name, Object value);
  33. }
复制代码
由于一个Session对象常常被一个管理器持有,所以接口提供了setManager和 getManager 方法来关联一个Session对象和一个管理器。别的,一个Session 实例在跟管理器相关联的容器有一个唯一的ID。对于该ID有setId和getId方 法相关。getLastAccessedTime 方法由管理器来调用,以确定一个Session对象 是否合法。管理器调用setValid方法来重置一个session的合法性。每次一个 Session 被访问的时间,都会调用access方法更新它的最后访问时间。最后, 管理器可以调用expire方法来终止一个expire方法,使用getSession可以获得一个包装在外观内的HttpSession对象。
   
  StandardSession 类:
  StandardSession 类是 Session 接口的尺度是实现。别的,实现了 javax.servlet.http.HttpSession 和 org.apache.catalina.Session 之外,它 还实现了java.lang.Serializable 接口来使得Session对象可序列化。 该类的构造器获得一个管理器实例来强制使得每个Session对象都有一个管理器。该类的构造器获得一个管理器实例来强制使得每个Session对象都有一个管理 器。接下来是几个紧张的变量在存放Session状态。注意transient使得该关键字不 可序列化。一个Session对象假如在由maxInactiveInterval变量的时间内没有被访问则被闭幕。使用Session接口中定义的expire方法可以闭幕一个Session对象。
  1. // session attributes
  2. private HashMap attributes = new HashMap();
  3. // the authentication type used to authenticate our cached Principal,
  4. if any
  5. private transient String authType = null;
  6. private long creationTime = 0L;
  7. private transient boolean expiring = false;
  8. private transient StandardSessionFacade facade = null;
  9. private String id = null;
  10. private long lastAccessedTime = creationTime;
  11. // The session event listeners for this Session.
  12. private transient ArrayList listeners = new ArrayList();
  13. private Manager manager = null;
  14. private int maxInactiveInterval = -1;
  15. // Flag indicating whether this session is new or not.
  16. private boolean isNew = false;
  17. private boolean isValid = false;
  18. private long thisAccessedTime = creationTime;
  19. public HttpSession getSession() {
  20.   if (facade == null)
  21.     facade = new StandardSessionFacade(this);
  22.   return (facade);
  23. }
  24. public void expire(boolean notify) {
  25.   // Mark this session as "being expired" if needed
  26.   if (expiring)
  27.     return;
  28.   expiring = true;
  29.   setValid(false);
  30.   
  31.   // Remove this session from our manager's active sessions
  32.   if (manager != null)
  33.     manager.remove(this);
  34.   // Unbind any objects associated with this session
  35.   String keys [] = keys();
  36.   for (int i = 0; i < keys.length; i++)
  37.     removeAttribute(keys[i], notify);
  38.   // Notify interested session event listeners
  39.   if (notify) {
  40.     fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
  41.   }
  42.   
  43.   // Notify interested application event listeners
  44.   // FIXME - Assumes we call listeners in reverse order
  45.   Context context = (Context) manager.getContainer();
  46.   Object listeners[] = context.getApplicationListeners();
  47.   if (notify && (listeners != null)) {
  48.     HttpSessionEvent event = new HttpSessionEvent(getSession());
  49.   
  50.     for (int i = 0; i < listeners.length; i++) {
  51.       int j = (listeners.length - 1) - i;
  52.       if (!(listeners[j] instanceof HttpSessionListener))
  53.         continue;
  54.       HttpSessionListener listener =
  55.        (HttpSessionListener) listeners[j];
  56.       try {
  57.         fireContainerEvent(context, "beforeSessionDestroyed",
  58.           listener);
  59.         listener.sessionDestroyed(event);
  60.         fireContainerEvent(context, "afterSessionDestroyed",
  61. listener);
  62.       }
  63.       catch (Throwable t) {
  64.         try {
  65.           fireContainerEvent(context, "afterSessionDestroyed",
  66.             listener);
  67.         }
  68.         catch (Exception e) {
  69.           ;
  70.         }
  71.         // FIXME - should we do anything besides log these?
  72.         log(sm.getString("standardSession.sessionEvent"), t);
  73.       }
  74.     }
  75.   }
  76.   
  77.   // We have completed expire of this session
  78.   expiring = false;
  79.   if ((manager != null) && (manager instanceof ManagerBase)) {
  80.     recycle();
  81.   }
  82. }
复制代码
StandardSessionFacade类:
  要将一个Session对象传递给一个servlet,Catalina会初始化一个 StandardSession类填充StandardSessionFacade并把StandardSessionFacade传递给servlet。。如许,servlet就不能通过将HttpSession向下转化为StandardSessionFacade类来访问StandardSession的public方法。
  管理器

   管理器用来管理Session对象。比方它创建Session对象并销毁它们。管理器由 org.apache.catalina.Manager 接口表示。在 Catalina 中, org.apache.catalina.session 包中类ManagerBase类提供了常用函数的基本实现。ManagerBase 类有两个直接子类:StandardManager和 PersistentManagerBase 类。 在运行的时间,StandardManager将session 对象存放在内存中。但是,当停止 的时间,它将Session对象存放到文件中。当它再次启动的时间,重新载入 Session 对象。 PersistentManagerBase 类作为一个管理器组件将Session对象存放到二级存储 器中。它有两个直接子类:PersistentManager和DistributedManager类 (DistributedManager)类只在Tomcat4 中有。
  第十章:安全 

   有些web应用步伐的内容是有限制的,只允许有权限的用户在提供正确的用户名 和暗码的情况下才允许访问。Servlet通过配置部署文件web.xml来对安全性提 供技能支持。本章的主要内容是容器对于安全性限制的支持。 
  一个servlet通过一个叫authenticator的阀门(valve)来支持安全性限制。 当容器启动的时间,authenticator被添加到容器的流水线上。假如你忘了流水 线是怎样工作的,需要重新复习下第六章的内容。
  authenticator 阀门会在包装器阀门之前被调用。authenticator用于对用户进 行验证,假如用户熟人了正确的用户名和暗码,authenticator阀门调用下一个 用于处理请求servlet的阀门。假如验证失败,authenticator不唤醒下一个阀 门直接返回。由于验证失败,用户并不能看到请求的servlet。
  在用户验证的时间authenticator阀门调用的是上下文域(realm)内的 authenticate 方法,将用户名和暗码传递给它。该容器域可以访问合法的用户 名暗码。
  本章首先先容跟安全性相关的类(realms、principal、roles),然后通过一个 应用步伐演示了怎样在你的servlets上使用安全管理。
   
  (域)Realm 

   域是用于举行用户验证的一个组件,它可以告诉你一个用户名暗码对是否是合法 的。一个域跟一个上下文容器相联系,一个容器可以只有一个域。可以使用容器 的setRealm 方法来创建它们之间的联系。
  一个域是怎样验证一个用户的合法性的?一个域拥有所有的合法用户的暗码或 者是可以访问它们。至于它们存放在那里则取决于域的实现。在Tomcat的默认 实现里,合法用户被存储在tomcat-users.xml文件里。但是可以使用域的其它 实现来访问其它的源,如关系数据库。
  在Catalina 中,一个域用接口org.apache.catalina.Realm表示。该接口最重 要的方法是四个authenticate方法:
  1. public Principal authenticate(String username, String credentials);
  2. public Principal authenticate(String username, byte[] credentials);
  3. public Principal authenticate(String username, String digest,
  4. String nonce, String nc, String cnonce, String qop, String realm,
  5. String md5a2);
  6. public Principal authenticate(X509Certificate certs[]);
复制代码
第一个方法是最常用的方法,Realm接口还有一个getRole方法,署名如下:
  public boolean hasRole(Principal principal, String role);
  别的,域还有getContainer和setContainer方法用于创建域与容器的联系。 一个域的基本上实现是抽象类org.apache.catalina.realm.RealmBase。 org.apache.catalina.realm包中海提供了其它一些类继续了RealmBase如: JDBCRealm, JNDIRealm, MemoryRealm,和 UserDatabaseRealm。默认情况下使用 的域是MemoryRealm。
  GenericPrincipal 

   一个principal使用java.security.Principal接口来表示,Tomcat中该接口 的实现为org.apache.catalina.realm.GenericPrincipal接口。一个 GenericPrincipal必须跟一个域相关联。GenericPrincipal必须拥有一个用户名和一个暗码,别的还可选择性的传递一 列脚色。可以使用hasRole方法来检查一个principal是否有一个特定的脚色, 传递的参数为脚色的字符串表示形式。这里是Tomcat4中的hasRole方法:
  1. public GenericPrincipal(Realm realm, String name, String password) {
  2.     this(realm, name, password, null);
  3.   }
  4.   public GenericPrincipal(Realm realm, String name, String password,
  5.     List roles) {
  6.     super();
  7.     this.realm = realm;
  8.    this.name = name;
  9.     this.password = password;
  10.     if (roles != null) {
  11.       this.roles = new String[roles.size()];
  12.       this.roles = (String[]) roles.toArray(this.roles);
  13.       if (this.roles.length > 0)
  14.         Arrays.sort(this.roles);
  15.     }
  16.   }
  17. public boolean hasRole(String role) {
  18.     if (role == null)
  19.       return (false);
  20.     return (Arrays.binarySearch(roles, role) >= 0);
  21.   }
  22. public boolean hasRole(String role) {
  23.   if ("*".equals(role)) // Special 2.4 role meaning everyone
  24. return true;
  25. if (role == null)
  26. return (false);
  27. return (Arrays.binarySearch(roles, role) >= 0);
  28. }
复制代码
 
  LoginConfig 类 

   封装了realm名和验证方式,getRealmName来获取realm名(这个名称通常在登录表单中显示给用户,用于提示用户他们正在登录的域),getAuthName获取验证方式。一个验证(authentication) 的名字必须是下面的之一:BASIC, DIGEST, FORM, 或者CLIENT-CERT。假如用到的是基于表单(form)的验证,该LoginConfig对象还包括登录或者错误页面 像对应的URL。
  Tomcat 一个部署启动的时间,先读取web.xml。假如web.xml包括一个 login-confgi 元素,Tomcat 创建一LoginConfig 对象并相应的设置它的属性。 验证阀门调用LoginConfig的getRealmName 方法并将域名发送给欣赏器显示登录表单。假如getRealmName名字返回值为null,则发送给欣赏器服务器的名字和端口名。
  Authenticator 类 

   org.apache.catalina.Authenticator 接口用来表示一个验证器。该方接口并没 有方法,只是一个组件的标记器,如许就能使用instanceof来检查一个组件是 否为验证器。 Catalina 提供了Authenticator 接口的基本实现: org.apache.catalina.authenticator.AuthenticatorBase 类。除了实现 Authenticator 接口外,AuthenticatorBase 还继续了 org.apache.catalina.valves.ValveBase 类。这就是说 AuthenticatorBase 也 是一个阀门。可以在org.apache.catalina.authenticator 包中找到该接口的几 个类:BasicAuthenticator 用于基本验证, FormAuthenticator 用于基于表单的 验证, DigestAuthentication 用于摘要(digest)验证, SSLAuthenticator 用 于SSL验证。NonLoginAuthenticator 用于Tomcat没有指定验证元素的时间。 NonLoginAuthenticator 类表示只是检查安全限制的验证器,但是不举行用户验 证。
  

  一个验证器的主要工作是验证用户。因此,AuthenticatorBase类的invoke方 法调用了抽象方法authenticate,该方法的详细实现由子类完成。在 BasicAuthenticator 中,它 authenticate 使用基本验证器来验证用户。
  在部署文件中,只能出现一个login-config元素,login-config元素包括了 auth-method 元素用于定义验证方法。这也就是说一个上下文容器只能有一个 LoginConfig 对象来使用一个authentication 的实现类。
  AuthenticatorBase 的子类在上下文中被用作验证阀门,这依赖于部署文件中 auth-method 元素的值。表10.1为auth-method元素的值,可以用于确定验证 器。
  

  第十一章:StandardWrapper 

   在第五章中已经说过,一共有四种容器:engine(引擎),host(主机),context (上下文)和wrapper(包装器)。在前面的章节里也先容了怎样创建本身的 context 和 wrapper。一个上下文一样平常包括一个或者多个包装器,每一个包装器 表示一个servlet。本章将会看到Catalina中Wrapper接口的尺度实现。首先 先容了一个HTTP请求会唤醒的一系列方法,接下来先容了 javax.servlet.SingleThreadModel 接口。最后先容了StandardWrapper 和 StandardWrapperValve 类。本章的应用步伐阐明白怎样用StandardWrapper实 例来表示servlet。
  方法调用序列:
  对于每一个连接,连接器都会调用关联容器的invoke方法。接下来容器调用它 的所有子容器的invoke方法。比方,假如一个连接器跟一个StadardContext 实例相关联,那么连接器会调用StandardContext实例的invoke方法,该方法 会调用所有它的子容器的invoke方法。图11.1阐明白一个连接器收到一个HTTP 请求的时间会做的一系列变乱。
  

  本章关注的是一个servlet被调用的时间发生的细节。因此我们需要自习看 StandardWrapper 和 StandarWrapperValve 类。在学习它们之前,我们需要首先 关注下javax.servlet.SingleThreadModel。理解该接口对于理解一个包装器是 怎样工作的是非常紧张的。
   
  SingleThreadModel:
  一个servlet可以实现javax.servlet.SingleThreadModel 接口,实现此接口的 一个 servlet 通俗称为 SingleThreadModel(STM)的步伐组件。根据 Servlet 规范,实现此接口的目的是保证servlet一次只能有一个请求。Servlet 2.4规 范的第SRV.14.2.24节(Servlet 2.3的有SingleThreadModel 接口上的雷同说 明)假如一个Servlet实现此接口,将保证不会有两个线程同是使用servlet 的service 方法。 servlet容器可以保证同步进入一个servlet的一个实例,或维持的Servlet 实例池和处理每个新请求。该接口并不能克制同步而产生的问题,如访问静态 类变量或该servlet以外的类或变量。很多步伐员并没有仔细阅读它,只是以为实现了SIngleThreadModel就能保证它 们的servlet是线程安全的。单显然并非如此,重新阅读上面的引文。一个servlet实现了SIngelThreadModel之后确实能保证它的service方法不会 被两个线程同时使用。为了提高servlet容器的性能,可以创建STM servlet 的多个实例。该SingleThreadModel 接口在Servlet 2.4 中 已经废弃,因为它使Servlet步伐员产生虚假 的安全感,以为它是线程安全的。然而,无论 Servlet 2.3 和 Servlet 2.4 的容器仍旧必须 支持此接口
   
  StandardWrapper:
  一个StandardWrapper 对象的主要职责是:加载它表示的servlet并分配它的一 个实例。该StandardWrapper不会调用servlet的service方法。这个使命留给 StandardWrapperValve 对象,在 StandardWrapper 实例的基本阀门管道。 StandardWrapperValve 对象通过调用 StandardWrapper 的 allocate 方法获得 Servlet 实例。在获得Servlet实例之后的StandardWrapperValve调用servlet 的service 方法
  在servlet 第一次被请求的时间,StandardWrapper加载servlet类。它是动态 的加载servlet,所以需要知道servlet类的完全限定名称。通过 StandardWrapper 类的 setServletClass 方法将 servlet 的类名传递给 StandardWrapper。别的,使用setName方法也可以传递servlet名。
  考虑到StandardWrapper 负责在StandardWrapperValve 请求的时间分配一个 servlet 实例,它必须考虑一个servlet是否实现了SingleThreadModel 接口。 假如一个servlet没有实现SingleThreadModel接口,StandardWrapper加载该 servlet 一次,对于以后的请求返回雷同的实例即可。StandardWrapper假设 servlet 的 service 方法是现场安全的,所以并没有创建servlet的多个实例。 假如需要的话,由步伐员本身办理资源同步问题。
  对于一个STM servlet,情况就有所不同了。StandardWrapper必须保证不能同 时有两个线程提交STM servlet的service方法。假如StandardWrapper维持一 个STM servlet 的实例,下面是它怎样调用servlet的service方法:
  1. Servlet instance = <get an instance of the servlet>;
  2. if ((servlet implementing SingleThreadModel>)  {
  3. synchronized (instance) {
  4. instance.service(request, response);
  5. }
  6. }
  7. else {
  8. instance.service(request, response);
  9. }
复制代码
但是,为了性能起见,StandardWrapper维护了一个STM servlet实例池。 一个包装器还负责准备一个javax.servlet.ServletConfig的实例,这可以在 servlet 内部完成,接下来两小节讨论怎样分配和加载servlet。
   
  Allocating the Servlet:
  在本节开始的时间先容到StandardWrapperValve的invoke方法调用了包装器的 allocate 方法来获得一个请求servlet的实例。因此StandardWrapper类必须 实现该接口。该方法的署名如下:
  public javax.servlet.Servlet allocate() throws ServletException;
  注意allocate方法返回的是请求servlet的一个实例。 由于要支持STM servlet,这使得该方法更复杂了一点。实际上,该方法有两部 分构成,一部门负责非STM servlet的工作,另一部门负责STM servlet。第一 部门的结构如下:
  if (!singleThreadModel) { // returns a non-STM servlet instance }
  布尔变量singleThreadModel 负责标记一个servlet是否是STM servlet。它的 初始值是false,loadServlet方法会检测加载的servlet是否是STM的,假如 是则将它的值该为true。loadServlet方法在下面会先容到。
  该方法的第二部门处理singleThreadModel为true的情况,第二部门的框架如 下: synchronized (instancepool) { // returns an instance of the servlet from the pool }
  对于非STM servlet,StandardWrapper 定义一个java.servlet.Servlet 类型的 实例 private Servlet instance = null; 方法allocate检查该实例是否为null,假如是调用loadServlet方法来加载 servlet。然后增长contAllocated整型并返回该实例。
  1. if (!singleThreadModel) {
  2. // Load and initialize our instance if necessary
  3.     if (instance == null) {
  4.       synchronized (this) {
  5.         if (instance == null) {
  6.           try {
  7.             instance = loadServlet();
  8.           }
  9.           catch (ServletException e) {
  10.             throw e;
  11.           }
  12.           catch (Throwable e) {
  13.             throw new ServletException
  14.               (sm.getString("standardWrapper.allocate"), e);
  15.           }
  16.         }
  17.       }
  18.     }
  19.     if (!singleThreadModel) {
  20.       if (debug >= 2)
  21.         log("  Returninq non-STM instance");
  22.       countAllocated++;
  23.       return (instance);
  24.     }
  25.   }
复制代码
假如StandardWrapper表示的是一个STM servlet,方法allocate实验返回池 中的一个实例,变量intancePool是一个java.util.Stack类型的STM servlet 实例池。 private Stack instancePool = null;该变量在loadServlet方法内初始化,该部门在接下来的小节举行讨论。 方法allocate负责分配STMservlet实例,前提是实例的数目不超过最大数目, 该数目由maxInstances整型定义,默认值是20.
  private int maxInstances = 20;
  StandardWrapper提供了nInstances整型变量来定义当前STM 实例的个数。
  private int nInstances = 0;
  这里是allocate方法的第二部门
  1. synchronized (instancePool) {
  2.     while (countAllocated >= nInstances) {
  3.       // Allocate a new instance if possible, or else wait
  4.       if (nInstances < maxInstances) {
  5.         try {
  6.           instancePool.push(loadServlet());
  7.           nInstances++;
  8.         }
  9.         catch (ServletException e) {
  10.           throw e;
  11.         }
  12.         catch (Throwable e) {
  13.           throw new ServletException
  14.            (sm.getString("StandardWrapper.allocate"), e);
  15.         }
  16.       }
  17.       else {
  18.         try {
  19.           instancePool.wait();
  20.         }
  21.         catch (InterruptedException e) {
  22.           ;
  23.         }
  24.       }
  25.     }
  26.     if (debug >= 2)
  27.       log("  Returning allocated STM instance");
  28.     countAllocated++;
  29.     return (Servlet) instancePool.pop();
  30.   
  31.   }
复制代码
上面的代码使用一个while循环等待直到nInstances的数目到达countAllocated。在循环里,allocate方法检查 nInstance的值,假如低于maxInstances的值,调用loadServlet方法并将该 实例添加到池中,增长nInstances的值。假如nInstances的值等于或大于 maxInstances的值,它调用实例池堆栈的wait方法,直到一个实例被返回。
   
  Loading the Servlet:
  StandardWrapper实现了Wrapper接口的load方法,load方法调用loadServlet 方法来加载一个servlet类,并调用该servlet的init方法,传递一个 javax.servlet.ServletConfig实例。这里是loadServlet是怎样工作的。 方法loadServlet首先检查StandardWrapper是否表示一个STM servlet。假如 不是并且该实例不是null(即以前已经加载过),直接返回该实例:
  

  假如该实例是null或者是一个STM servlet,继续该方法的其它部门:
   
   
 
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

郭卫东

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表