How Tomcat Works

发布于:2025-03-20 ⋅ 阅读:(17) ⋅ 点赞:(0)

第一章:一个简单的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类 

public final class LifecycleEvent extends EventObject { 
  public LifecycleEvent(Lifecycle lifecycle, String type) { 
    this(lifecycle, type, null); 
  } 
  public LifecycleEvent(Lifecycle lifecycle, String type, 
    Object data) { 
  
    super(lifecycle); 
    this.lifecycle = lifecycle; 
    this.type = type; 
this.data = data; 
} 
private Object data = null; 
private Lifecycle lifecycle = null; 
private String type = null; 
public Object getData() { 
return (this.data); 
} 
public Lifecycle getLifecycle() { 
return (this.lifecycle); 
} 
public String getType() { 
return (this.type); 
} 
}

LifecycleListener 接口 

 

LifecycleSupport 类

public final class LifecycleSupport { 
  public LifecycleSupport(Lifecycle lifecycle) { 
    super(); 
    this.lifecycle = lifecycle; 
  } 
  
  private Lifecycle lifecycle = null; 
  private LifecycleListener listeners[] = new LifecycleListener[0]; 
  public void addLifecycleListener(LifecycleListener listener) { 
    synchronized (listeners) { 
      LifecycleListener results[] = 
        new LifecycleListener[listeners.length + 1]; 
      for (int i = 0; i < listeners.length; i++) 
        results[i] = listeners[i]; 
      results[listeners.length] = listener; 
        listeners = results; 
    } 
  } 
  
  public LifecycleListener[] findLifecycleListeners() { 
    return listeners; 
  } 
  
  public void fireLifecycleEvent(String type, Object data) { 
    LifecycleEvent event = new LifecycleEvent(lifecycle, type, data); 
    LifecycleListener interested[] = null; 
    synchronized (listeners) { 
      interested = (LifecycleListener[]) listeners.clone(); 
    } 
    for (int i = 0; i < interested.length; i++) 
      interested[i].lifecycleEvent(event); 
  } 
  
  public void removeLifecycleListener(LifecycleListener listener) { 
    synchronized (listeners) { 
      int n = -1; 
      for (int i = 0; i < listeners.length; i++) { 
        if (listeners[i] == listener) { 
          n = i; 
          break; 
        } 
      } 
      if (n < 0) 
        return; 
      LifecycleListener results[] = 
        new LifecycleListener[listeners.length - 1]; 
  
      int j = 0; 
      for (int i = 0; i < listeners.length; i++) { 
        if (i != n) 
        results[j++] = listeners[i]; 
      } 
      listeners = results; 
    } 
  } 
} 

 SimpleContext

public void addLifecycleListener(LifecycleListener listener) { 
lifecycle.addLifecycleListener(listener); 
} 
public LifecycleListener[] findLifecycleListeners() { 
return null; 
} 
public void removeLifecycleListener(LifecycleListener listener) { 
lifecycle.removeLifecycleListener(listener); 
} 
public synchronized void start() throws LifecycleException { 
  if (started) 
    throw new LifecycleException("SimpleContext has already started"); 
  // Notify our interested LifecycleListeners 
  lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); 
  started = true; 
  try { 
    // Start our subordinate components, if any 
    if ((loader != null) && (loader instanceof Lifecycle)) 
      ((Lifecycle) loader).start(); 
  
    // Start our child containers, if any 
    Container Children[] = findChildren(); 
    for (int i = 0; i < children.length; i++) { 
      if (children[i] instanceof Lifecycle) 
        ((Lifecycle) children[i]).start(); 
    } 
  
    // Start the Valves in our pipeline (including the basic), 
    // if any 
    if (pipeline instanceof Lifecycle) 
      ((Lifecycle) pipeline).start(); 
    // Notify our Interested LifecycleListeners 
    lifecycle.firelifecycleEvent(START_EVENT, null); 
  } 
  catch (Exception e) { 
    e.printStackTrace(); 
  } 
  // Notify our interested LifecycleListeners 
  lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); 
} 
  
public void stop() throws LifecycleException { 
  if (!started) 
    throw new LifecycleException("SimpleContext has not been started"); 
  // Notify our interested LifecycleListeners 
  lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null); 
  lifecycle.fireLifecycleEvent(STOP_EVENT, null); 
  started = false; 
  try { 
    // Stop the Valves in our pipeline (including the basic), if any 
    if (pipeline instanceof Lifecycle) ( 
  
      ((Lifecycle) pipeline).stop(); 
    } 
    // Stop our child containers, if any 
    Container children[] = findChildren(); 
    for (int i = 0; i < children.length; i++) { 
      if (children[i] instanceof Lifecycle) 
        ((Lifecycle) children[i]).stop(); 
    } 
    if ((loader != null) && (loader instanceof Lifecycle)) { 
      ((Lifecycle) loader).stop(); 
    } 
  } 
  catch (Exception e) { 
    e.printStackTrace(); 
  } 
  // Notify our interested LifecycleListeners 
  lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null); 
} 

第七章:日志系统

Logger接口

public interface Logger { 
  public static final int FATAL = Integer.MIN_VALUE; 
  public static final int ERROR = 1; 
  public static final int WARNING = 2; 
  public static final int INFORMATION = 3; 
  public static final int DEBUG = 4; 
  
  public Container getContainer(); 
  public void setContainer(Container container); 
  public String getInfo(); 
  public int getVerbosity(); 
  public void setVerbosity(int verbosity); 
  public void addPropertyChangeListener(PropertyChangeListener 
    listener); 
  public void log(String message); 
  
  public void log(Exception exception, String msg); 
  public void log(String message, Throwable throwable); 
  public void log(String message, int verbosity); 
  public void log(String message, Throwable throwable, int verbosity); 
  public void removePropertyChangeListener(PropertyChangeListener 
    listener); 
}

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方法。

public interface Loader { 
public ClassLoader getClassLoader(); 
public Container getContainer(); 
public void setContainer(Container container); 
public DefaultContext getDefaultContext(); 
public void setDefaultContext(DefaultContext defaultContext); 
public boolean getDelegate(); 
public void setDelegate(boolean delegate); 
public String getInfo(); 
public boolean getReloadable(); 
public void setReloadable(boolean reloadable); 
public void addPropertyChangeListener(PropertyChangeListener 
listener); 
public void addRepository(String repository); 
public String[] findRepositories(); 
public boolean modified(); 
public void removePropertyChangeListener(PropertyChangeListener 
listener); 
} 

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的实例

private WebappClassLoader createClassLoader() throws Exception { 
  Class clazz = Class.forName(loaderClass); 
  WebappClassLoader classLoader = null; 
  if (parentClassLoader == null) { 
    // Will cause a ClassCast if the class does not extend 
    // WebappClassLoader, but this is on purpose (the exception will be 
    // caught and rethrown) 
    classLoader = (WebappClassLoader) clazz.newInstance(); 
    // in Tomcat 5, this if block is replaced by the following: 
    // if (parentClassLoader == null) { 
    //   parentClassLoader = 
    //     Thread.currentThread().getContextClassLoader(); 
    // } 
  
  } 
  else { 
    Class[] argTypes = { ClassLoader.class }; 
    Object[] args = { parentClassLoader }; 
    Constructor constr = clazz.getConstructor(argTypes); 
    classLoader = (WebappClassLoader) constr.newInstance(args); 
  } 
  return classLoader; 
} 

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接口来支持自动重载。

public void run() { 
  if (debug >= 1) 
    log("BACKGROUND THREAD Starting"); 
  
  // Loop until the termination semaphore is set 
  while (!threadDone) { 
    // Wait for our check interval 
    threadSleep(); 
    if (!started) 
      break; 
    try { 
      // Perform our modification check 
      if (!classLoader.modified()) 
        continue; 
    } 
    catch (Exception e) { 
      log(sm.getString("webappLoader.failModifiedCheck"), e); 
      continue; 
    } 
    // Handle a need for reloading 
    notifyContext(); 
    break; 
  } 
  
  if (debug >= 1) 
    log("BACKGROUND THREAD Stopping"); 
} 

private void notifyContext() { 
WebappContextNotifier notifier = new WebappContextNotifier(); 
(new Thread(notifier)).start(); 
} 

protected class WebappContextNotifier implements Runnable { 
public void run() { 
((Context) container).reload(); 
} 
}

 

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类型的数组表示该类、最后修改的 数据或者副本等等。

public class ResourceEntry { 
public long lastModifled = -1; 
// Binary content of the resource. 
public byte[] binaryContent = null; 
public Class loadedClass = null; 
// URL source from where the object was loaded. 
public URL source = null; 
// URL of the codebase from where the object was loaded. 
public URL CodeBase = null; 
public Manifest manifest = null; 
public Certificate[] certificates = null; 
} 

所有缓存的源被存放在一个叫做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类的一些相关方法。

public HttpSession getSession() { 
  return (getSession(true)); 
} 
public HttpSession getSession(boolean create) { 
  ... 
  return doGetSession(create); 
} 
private HttpSession doGetSession(boolean create) { 
  // There cannot be a session if no context has been assigned yet 
  if (context == null) 
    return (null); 
  // Return the current session if it exists and is valid 
  if ((session != null) && !session.isValid()) 
    session = null; 
  if (session != null) 
    return (session.getSession()); 
  
  // Return the requested session if it exists and is valid 
  Manager manager = null; 
  if (context != null) 
    manager = context.getManager(); 
  if (manager == null) 
    return (null);      // Sessions are not supported 
  if (requestedSessionId != null) { 
    try { 
      session = manager.findSession(requestedSessionId); 
    } 
  
    catch (IOException e) { 
      session = null; 
    } 
    if ((session != null) && !session.isValid()) 
      session = null; 
    if (session != null) { 
      return (session.getSession()); 
    } 
  } 
  
  // Create a new session if requested and the response is not 
  // committed 
  if (!create) 
    return (null); 
  ... 
  session = manager.createSession(); 
  if (session != null) 
    return (session.getSession()); 
  else 
    return (null); 
} 

默认情况下管理器将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。

public interface Session { 
  public static final String SESSION_CREATED_EVENT = "createSession"; 
  public static final String SESSION_DESTROYED_EVENT = 
    "destroySession"; 
  public String getAuthType(); 
  public void setAuthType(String authType); 
  public long getCreationTime(); 
  public void setCreationTime(long time); 
  public String getId(); 
  public void setId(String id); 
  public String getInfo(); 
  public long getLastAccessedTime(); 
  public Manager getManager(); 
  public void setManager(Manager manager); 
  public int getMaxInactiveInterval(); 
  public void setMaxInactiveInterval(int interval); 
  public void setNew(boolean isNew); 
  public Principal getPrincipal(); 
  public void setPrincipal(Principal principal); 
  public HttpSession getSession(); 
  public void setValid(boolean isValid); 
  public boolean isValid(); 
  
  public void access(); 
  public void addSessionListener(SessionListener listener); 
  public void expire(); 
  public Object getNote(String name); 
  public Iterator getNoteNames(); 
  public void recycle(); 
  public void removeNote(String name); 
public void removeSessionListener(SessionListener listener); 
public void setNote(String name, Object value); 
} 

由于一个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对象。

// session attributes 
private HashMap attributes = new HashMap(); 
// the authentication type used to authenticate our cached Principal, 
if any 
private transient String authType = null; 
private long creationTime = 0L; 
private transient boolean expiring = false; 
private transient StandardSessionFacade facade = null; 
private String id = null; 
private long lastAccessedTime = creationTime; 
// The session event listeners for this Session. 
private transient ArrayList listeners = new ArrayList(); 
private Manager manager = null; 
private int maxInactiveInterval = -1; 
// Flag indicating whether this session is new or not. 
private boolean isNew = false; 
private boolean isValid = false; 
private long thisAccessedTime = creationTime; 

public HttpSession getSession() { 
  if (facade == null) 
    facade = new StandardSessionFacade(this); 
  return (facade); 
} 

public void expire(boolean notify) { 
  // Mark this session as "being expired" if needed 
  if (expiring) 
    return; 
  expiring = true; 
  setValid(false); 
  
  // Remove this session from our manager's active sessions 
  if (manager != null) 
    manager.remove(this); 
  // Unbind any objects associated with this session 
  String keys [] = keys(); 
  for (int i = 0; i < keys.length; i++) 
    removeAttribute(keys[i], notify); 
  // Notify interested session event listeners 
  if (notify) { 
    fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null); 
  } 
  
  // Notify interested application event listeners 
  // FIXME - Assumes we call listeners in reverse order 
  Context context = (Context) manager.getContainer(); 
  Object listeners[] = context.getApplicationListeners(); 
  if (notify && (listeners != null)) { 
    HttpSessionEvent event = new HttpSessionEvent(getSession()); 
  
    for (int i = 0; i < listeners.length; i++) { 
      int j = (listeners.length - 1) - i; 
      if (!(listeners[j] instanceof HttpSessionListener)) 
        continue; 
      HttpSessionListener listener = 
       (HttpSessionListener) listeners[j]; 
      try { 
        fireContainerEvent(context, "beforeSessionDestroyed", 
          listener); 
        listener.sessionDestroyed(event); 
        fireContainerEvent(context, "afterSessionDestroyed", 
listener); 
      } 
      catch (Throwable t) { 
        try { 
          fireContainerEvent(context, "afterSessionDestroyed", 
            listener); 
        } 
        catch (Exception e) { 
          ; 
        } 
        // FIXME - should we do anything besides log these? 
        log(sm.getString("standardSession.sessionEvent"), t); 
      } 
    } 
  } 
  
  // We have completed expire of this session 
  expiring = false; 
  if ((manager != null) && (manager instanceof ManagerBase)) { 
    recycle(); 
  } 
}

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方法:

public Principal authenticate(String username, String credentials); 
public Principal authenticate(String username, byte[] credentials); 
public Principal authenticate(String username, String digest, 
String nonce, String nc, String cnonce, String qop, String realm, 
String md5a2); 
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方法:

public GenericPrincipal(Realm realm, String name, String password) { 
    this(realm, name, password, null); 
  } 
  public GenericPrincipal(Realm realm, String name, String password, 
    List roles) { 
    super(); 
    this.realm = realm; 
   this.name = name; 
    this.password = password; 
    if (roles != null) { 
      this.roles = new String[roles.size()]; 
      this.roles = (String[]) roles.toArray(this.roles); 
      if (this.roles.length > 0) 
        Arrays.sort(this.roles); 
    } 
  }

public boolean hasRole(String role) { 
    if (role == null) 
      return (false); 
    return (Arrays.binarySearch(roles, role) >= 0); 
  } 

public boolean hasRole(String role) { 
  if ("*".equals(role)) // Special 2.4 role meaning everyone 
return true; 
if (role == null) 
return (false); 
return (Arrays.binarySearch(roles, role) >= 0); 
} 

 

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方法:

Servlet instance = <get an instance of the servlet>; 
if ((servlet implementing SingleThreadModel>)  { 
synchronized (instance) { 
instance.service(request, response); 
} 
} 
else { 
instance.service(request, response); 
} 

但是,为了性能起见,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整型并返回该实例。

if (!singleThreadModel) { 
// Load and initialize our instance if necessary 
    if (instance == null) { 
      synchronized (this) { 
        if (instance == null) { 
          try { 
            instance = loadServlet(); 
          } 
          catch (ServletException e) { 
            throw e; 
          } 
          catch (Throwable e) { 
            throw new ServletException 
              (sm.getString("standardWrapper.allocate"), e); 
          } 
        } 
      } 
    } 
    if (!singleThreadModel) { 
      if (debug >= 2) 
        log("  Returninq non-STM instance"); 
      countAllocated++; 
      return (instance); 
    } 
  } 

如果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方法的第二部分

synchronized (instancePool) { 
    while (countAllocated >= nInstances) { 
      // Allocate a new instance if possible, or else wait 
      if (nInstances < maxInstances) { 
        try { 
          instancePool.push(loadServlet()); 
          nInstances++; 
        } 
        catch (ServletException e) { 
          throw e; 
        } 
        catch (Throwable e) { 
          throw new ServletException 
           (sm.getString("StandardWrapper.allocate"), e); 
        } 
      } 
      else { 
        try { 
          instancePool.wait(); 
        } 
        catch (InterruptedException e) { 
          ; 
        } 
      } 
    } 
    if (debug >= 2) 
      log("  Returning allocated STM instance"); 
    countAllocated++; 
    return (Servlet) instancePool.pop(); 
  
  } 

上面的代码使用一个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,继续该方法的其它部分: