第一章:一个简单的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,继续该方法的其它部门:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |