Coder 爱翻译 How Tomcat Works 第八章 第二部分

[size=large]The Loader Interface[/size]

在web应用里加载servlet和加载其它类都是有一定的规则的。例如:应用里的一个servlet可以使用部署在WEB-INF/classes目录及其子目录下的类。但是,servlet不能访问其它类,甚至是包含在JVM运行的Tomcat的CLASSPATH下的类。同样一个servlet也只能访问WEB-INF/lib目录下的类库,不能访问其他目录下的类库。

Tomcat的加载器代表了一个web应用的加载器,而不是一个类加载器。一个加载器必须实现org.apache.catalina.Loader接口。这个加载器实现使用一个
org.apache.catalina.loader.WebappClassLoader类表示的特定类的加载器。你可以在一个web加载器中使用Loader接口的getClassLoader方法来获取一个ClassLoader。

Loader接口定义了与一个仓库(repository)集合一起工作的方法。一个应用的WEB-INF/classes和WEB-INF/lib是目录作为仓库而被添加。Loader接口的addRepository方法用来添加一个仓库,findRepositories方法返回包含了所有仓库的一个数组。

一个Tomcat的加载器实现通常和是一个context相关联的,Loader接口的getContainer和setContainer方法用来在它们之间建立联系。如果在一个context里有一个或多个类被改变,加载器也支持重新加载。这样,servlet程序员编译一个servlet或者一个类,这个新的类在不需要重启Tomcat的情况下会被重新加载。为了实现重新加载,Loader接口有modified方法。在一个加载器实现里,modified方法在仓库中一个或多个类改变了,就返回true。但是一个加载器不会重新加载它自己本身。它调用Context接口的reload方法。其它两个方法:setReloadable和getReloadable用来决定是否使用重新加载这个功能。默认情况,Context的标准实现(org.apache.catalina.core.StandardContext),重新加载是关闭的。要打开context的重新加载功能,你需要在server.xml中添加一个Context元素:

<Context path="/myApp" docBase="myApp" debug="0" reloadable="true"/>

一个加载器实现可以被告知是否使用委托,把它委托给一个父类类加载器。这样,Loader接口提供getDelegate和setDelegate方法。

Listing 8.1: The Loader interface

package org.apache.catalina;
import java.beans.PropertyChangeListener;

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作为一个Loader接口的实现。WebappLoader对象包含一个org.apache.catalina.loader实例。WebappClassLoader类继承了java.net.URLClassLoader类。
注意:在任何时候,要把容器与一个加载器相关联都需要一个servlet类。当它的invoke方法被调用,容器首先调用加载器的getClassLoader方法来获取类加载器。然后,容器调用类加载器的loadClass方法来加载servlet类。
类图如下:

[img]http://dl.iteye.com/upload/attachment/365848/8d2afb72-ecbb-3aed-a91b-20c1da493775.jpg[/img]


[size=large]The Reloader Interface[/size]

为了支持自动重新加载,一个类加载器实现必须实现org.apache.catalina.loader.Reloader接口。

Listing 8.2: The Reloader interface

package org.apache.catalina.loader;
public interface Reloader {
public void addRepository(String repository);
public String[] findRepositories ();
public boolean modified();
}

它最重要的方法是modified。如果在web应用中的一个或多个servlet或类发生变化,它就返回true。它的addRepository方法用来添加一个仓库,findRepositories方法返回在实现了Reloader的类加载器里所有仓库一个字符串数组。

[size=large]The WebappLoader Class[/size]

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实例通知它的相关容器(本例是一个context)。
通过Context,类重新加载,而不是通过WebappLoader。
当WebappLoader类的start方法被调用时,有几个重要的任务:

 Creating a class loader 创建一个类加载器
 Setting repositories . 设置仓库
 Setting the class path 设置类路径
 Setting permissions 设置权限
 Starting a new thread for auto-reload. 开始一个用于自动重新加载的新线程

下面详细讲解:

[size=large]Creating A Class Loader[/size]

要加载类,一个WebappLoader实例持有一个内部类加载器。你可以回顾Loader接口:这个接口提供getClassLoader方法,但是这里没有setClassLoader方法。此外,你不能实例化一个类加载器和把它传递给WebappLoader。是不是它意味着WebappLoader不能灵活地与一个没有默认的类加载器工作?

答案是否定的。WebappLoader提供getLoaderClass和setLoaderClass方法来获取和改变它的private变量loaderClass的值。这个变量是一个代表了类加载器类名字的字符串。默认情况下,loaderClass的值是:org.apache.catalina.loader.WebappClassLoader。如果你愿意,你可以创建你自己的类加载器,它继承自WebappClassLoader。调用setLoaderClass方法强制WebappLoader使用你的特定的类加载器。当它启动了,WebappLoader将通过调用它的privatecreateClassLoader方法来创建一个WebappClassLoader实例。

Listing 8.3: The createClassLoader method

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 = hread.currentThread().getContextClassLoader();
// }
else {
Class[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoader) constr.newInstance(args);
}
return classLoader;
}

使用WebappClassLoader加载器以外的其它类加载器是可能的。注意:createClassLoader方法返回一个WebappClassLoader。如果你的特定的类加载器没有继承WebappClassLoader的话,这个方法将会抛出一个异常。

[size=large]Setting Repositories[/size]

WebappLoader类的start方法调用setRepositories方法来为它的类加载器添加仓库。WEB-INF/classes目录被传递给类加载器的addRepository方法。WEB-INF/lib目录被传递给类加载器的setJarPath方法。这种方式,类加载器将能够加载在WEB-INF/classes目录下的类和部署在WEB-INF/lib目录下的所有类库。

[size=large]Setting the Class Path[/size]

这个任务是通过start方法,调用setClassPath方法。setClassPath方法指配一个字符串给servlet context的一个属性。这个字符串包含了Jasper JSP编译器的类路径信息。

[size=large]Setting Permissions[/size]

如果在运行Tomcat的时候使用了安全管理器,setPermissions方法添加访问权限给类加载器让它可以访问必要的目录,如:WEB-INF/classes和WEB-INF/lib。如果没有使用安全管理器,这个方法就马上返回。

[size=large]Starting a New Thread for Auto-Reload[/size]

WebappLoader支持自动重新加载。如果在WEB-INF/classes或WEB-INF/lib目录下的类被重编译,在不重启动Tomcat的情况下就可以自动地重新加载这个类。WebappLoader有一个线程每隔X秒时间就检查每一个资源时间戳。这里的X是定义的变量值:checkInterval。默认值是15,表示每隔15秒的时间就为是否需要重新加载做一次时间戳检查。getCheckInterval和setCheckInterval方法用来访问这个变量。

在Tomcat 4的WebappLoader实现了java.lang.Runnable接口来支持重新加载:

Listing 8.3: The run method

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");
}

注意:Tomcat 5中检查类是否变化这个任务:通过org.apache.catalina.core.StandardContext 对象的backgroundProcess方法完成的。这个方法在每隔一段时间将会被调用。在org.apache.catalina.core.ContainerBase类里的一个特定的线程,ContainerBase是StandardContext的父类。检查ContainerBase类的ContainerBackgroundProcessor内部类实现了Runnable。

它的核心部分,run方法包含一个while循环,它在started变量被设置为false时结束循环:

 通过checkInterval这个变量,等待(sleep)指定的时间间隔。
 任何被加载的类通过调用WebappLoader实例的类加载器的modified方法来检查它们是否被改变。如果没有,continue。
 如果一个类发生了改变,调用notifyContext的private变量告知与WebappLoader相关联的Context来重新加载这个类。

notifyContext方法:

Listing 8.4: The notifyContext method

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

notifyContext方法不会直接调用Context接口的reload方法。它实例化内部类WebappContextNotifier和传递线程对象,然后调用它的start方法。这种方式,reload的执行将会在另一个线程里面执行。下面是WebappContextNotifier类:

Listing 8.5: The WebappContextNotifier inner class

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

当一个WebappContextNotifier实例传递给一个Thread时,这个Thread对象调用它的start方法。WebappContextNotifier实例的run方法将会被执行。run方法调用Context接口的reload方法。


[size=large]The WebappClassLoader Class[/size]

org.apache.catalina.loader.WebappClassLoader类代表负责为web应用加载类的类加载器。WebappClassLoader继承自java.net.URLClassLoader类,这个类用来加载前面几章中应用的java类。
WebappClassLoader的设计是出于优化和安全来考虑的。例如:它缓存先前已经被加载的类来增强性能。它也缓存它没找到的类的名字,以至于下一次同样的类被请求加载时,这个类加载器在不需要先尝试去找到他们就可以抛出ClassNotFoundException异常。WebappClassLoader搜索位于仓库列表下的类和指定JAR文件里面的类。

出于安全性,WebappClassLoader类不允许加载某些类。这些类被存储在一个叫做triggers的字符串数组,通常有一个成员:

private static final String[] triggers = {
"javax.servlet.Servlet" // Servlet API
};

也不用首先委托给系统类加载器,不允许你加载属于这些包及其子包下的类:

private static final String[] packageTriggers = {
"javax", // Java extensions
"org.xml.sax", // SAX 1 & 2
"org.w3c.dom", // DOM 1 & 2
"org.apache.xerces", // Xerces 1 & 2
"org.apache.xalan" // Xalan
};

然我们看看这个类怎么缓存和加载类

[size=large]Caching[/size]

为了获取更好的性能,被加载的类会被缓存起来,以便下次在这个类需要时不需要再次加载。它可以从缓存中取出来。缓存可以在本地处理,缓存是被WebappClassLoader实例管理的。此外,java.lang.ClassLoader维护一个之前被加载过的类的Vector,以防止它这些类被垃圾回收。这种情况,缓存是被它的父类管理的。

每个类会被当做一个资源,它(WEB-INF/classes下或JAR文件里的类文件)是通过WebappClassLoader来加载的。一个资源由org.apache.catalina.loader.ResourceEntry类代表。一个ResourceEntry实例持有类的代表、最后的更改日期、清单(Manifest)等等的字节数组。

The ResourceEntry class is given in Listing 8.6.
Listing 8.6: The ResourceEntry class.

package org.apache.catalina.loader;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.jar.Manifest;

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中。它的key是资源名。所有没有找到的资源被存储在另外一个叫做notFoundResources的HashMap中。

[size=large]Loading Classes[/size]

当加载一个类,WebappClassLoader遵守下面的规则:

 所有之前加载的类被缓存,所有首先会检查本地缓存
 如果没有在本地缓存里找到,就通过调用java.lang.ClassLoader类的findLoadedClass方法检查缓存
 若果两个缓存里都没找到,使用系统的类加载器,防止web应用重写J2EE里的类
 如果使用了安全管理器,检查这个类是否允许加载。如果这个类不允许加载,抛出一个ClassNotFoundException异常。
 如果delegate标识符是on或者如果这个想要被加载类是属于trigger里面包含的包下,使用父类加载器来加载这个类,如果父类加载器是null,使用系统类加载器。
 从当前仓库加载这个类。
 如果在当前仓库里没有发现这个类,如果delegation标识符不是on,使用父类加载器。如果父类加载器是null,使用系统类加载器。
 如果这个类还是没有被发现,就抛出一个ClassNotFoundException异常。

[size=large]The Application[/size]

Context的标准实现是org.apache.catalina.core.StandardContext类。这个应用实例化StandardContext类。StandardContext会在十二章中讨论。你在本章不需要知道关于这个类的详细情况。你只需要知道StandardContext它与一个监听器一起工作。它监听事件的触发。例如:START_EVENT 和STOP_EVENT。监听器必须实现org.apache.catalina.lifecycle.LifecycleListener接口,调用StandardContext类的setConfigured方法。本章的监听器就是由ex08.pyrmont.core.SimpleContextConfig类来表示。

Listing 8.6: The SimpleContextConfig class

package ex08.pyrmont.core;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;

public class SimpleContextConfig implements LifecycleListener {
public void lifecycleEvent(LifecycleEvent event) {
if (Lifecycle.START_EVENT.equals(event.getType())) {
Context context = (Context) event.getLifecycle();
context.setConfigured(true);
}
}
}

你所需要做的是实例化StandardContext和SimpleContextConfig,然后调用org.apache.catalina.Lifecycle接口的addLifecycleListener方法用StandardContext来注册SimpleContextConfig。

使用StandardContext 的WEB-INF/classes目录下存储的servlet。这个叫做myApp是应用目录在当你第一次部署可下载的ZIP文件时被创建。告诉StandardContext实例在哪里可以找到这个应用目录,你设置一个系统属性:catalina.base 。它的值是user.dir属性。

System.setProperty("catalina.base", System.getProperty("user.dir"));

实际上,这是BootStrap类main方法的第一行代码,然后,main方法实例化默认的连接器。

Connector connector = new HttpConnector();

然后为两个servlet创建各自的wrapper,并实例化它们:

Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");

然后创建一个StandardContext实例,也设置context的文档库:

Context context = new StandardContext();
// StandardContext's start method adds a default mapper
context.setPath("/myApp");
context.setDocBase("myApp");

配置下面的元素在server.xml文件中:

<Context path="/myApp" docBase="myApp"/>

然后把两个wrapper加入到context中,添加映射以便context可以定位wrapper。

context.addChild(wrapper1);
context.addChild(wrapper2);
context.addServletMapping("/Primitive", "Primitive");
context.addServletMapping("/Modern", "Modern");

下一步是实例化监听器和注册:

LifecycleListener listener = new SimpleContextConfig();
((Lifecycle) context).addLifecycleListener(listener);

接下来实例化WebappLoader,然后把它与context相关联起来:

Loader loader = new WebappLoader();
context.setLoader(loader);

最后,把context和默认的连接器相关联起来,调用连接器的initialize和start方法。

connector.setContainer(context);
try {
connector.initialize();
((Lifecycle) connector).start();
((Lifecycle) context).start();

下面几行简单地展示了资源的docBase的值和在类加载器里的所有仓库:

// now we want to know some details about WebappLoader
WebappClassLoader classLoader = (WebappClassLoader)
loader.getClassLoader();
System.out.println("Resources' docBase: " +
((ProxyDirContext)classLoader.getResources ()).getDocBase());
String[] repositories = classLoader.findRepositories();
for (int i=0; i<repositorles.length; i++) {
System.out.println("repository: " + repositories[i]);
}

这几行让docBase和仓库的列表当你运行这个应用程序的时候会被打印出来。

Resources’ docBase: C:\HowTomcatWorks\myApp
repository: /WEB-INF/classes/

docBase的值在你的机器上会有不同的值,依赖于你安装的应用.

最后,直到用户在控制台按了Enter键,就停止应用程序。不然应用就一直处于等待状态。

// make the application wait until we press a key.
System.in.read();
((Lifecycle) context).stop();

[size=large]Running the Application[/size]

总结:web应用加载器或一个简单的加载器,是Catalina中最重要的一个组件。一个加载器负责加载类和持有一个内部类加载器。这个内部类加载器是一个特定的类,它被Tomcat用来执行某种规则来加载类。这个特定的类加载器支持缓存和检查单个或多个类是否发生变化。

注:把repository翻译为仓库
第八章 完
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值