Part1Chapter 8: Loader 加载器
Part2Overview(概述)
You have seen a simple loader implementation in the previous chapters, which was used for loading servlet classes. This chapter explains the standard web application loader, or loader for short, in Catalina. A servlet container needs a customized loader and cannot simply use the system's class loader because it should not trust the servlets it is running. If it were to load all servlets and other classes needed by the servlets using the system's class loader, as we did in the previous chapters, then a servlet would be able to access any class and library included in the CLASSPATH environment variable of the running Java Virtual Machine (JVM), This would be a breach of security. A servlet is only allowed to load classes in the WEB-INF/classes directory and its subdirectories and from the libraries deployed into the WEB-INF/lib directory. That's why a servlet container requires a loader of its own. Each web application (context) in a servlet container has its own loader. A loader employs a class loader that applies certain rules to loading classes. In Catalina, a loader is represented by the org.apache.catalina.Loader interface.
在前几章中,您已经看到了一个简单的加载器实现,用于加载servlet
类。
本章介绍了Catalina
中的标准Web应用程序加载器,或简称加载器。
一个 servlet
容器需要一个自定义的加载器,不能简单地使用系统的类加载器,因为它不应该信任正在运行的servlet
。
如果它使用系统的类加载器加载所有servlet
和servlet
所需的其他类,就像我们在前几章中所做的那样,
那么一个servlet
将能够访问运行Java虚拟机(JVM)的 CLASSPATH
环境变量中包含的任何类和库,这将是一种安全漏洞。
一个servlet
只被允许加载WEB-INF/classes
目录及其子目录中的类以及部署到WEB-INF/lib目录中的库。
这就是为什么servlet
容器需要自己的加载器。在servlet
容器中,每个Web应用程序(上下文)都有自己的加载器。
加载器使用一个类加载器来加载类,并应用一定的规则。
在Catalina
中,加载器由org.apache.catalina.Loader
接口表示。
Another reason why Tomcat needs its own loader is to support automatic reloading whenever a class in the WEB-INF/classes or WEB-INF/lib directories has been modified. The class loader in the Tomcat loader implementation uses a separate thread that keeps checking the time stamps of the servlet and supporting class files. To support automatic reloading, a class loader must implement the org.apache.catalina.loader.Reloader interface.
Tomcat需要自己的加载器的另一个原因是在WEB-INF/classes
或WEB-INF/lib
目录中的类被修改时支持自动重新加载。
Tomcat加载器实现中的类加载器使用一个独立的线程来不断检查servlet和支持类文件的时间戳。
为了支持自动重新加载,一个类加载器必须实现 org.apache.catalina.loader.Reloader
接口。
The first section of this chapter briefly reviews the class loading mechanism in Java. Next, it covers the Loader interface, which all loaders must implement, followed by the Reloader interface. After a look at the implementation of the loader and class loader, the chapter presents an application that demonstrates how to use a Tomcat's loader.
本章的第一节简要回顾了Java中的类加载机制。
之后介绍所有加载器必须实现的Loader
接口,然后是 Reloader
接口。
在查看加载器和类加载器的实现之后,本章介绍了一个应用程序,演示了如何使用Tomcat的加载器。
Two terms are used extensively in this chapter: repository and resources. A repository is a place that will be searched by the class loader. The term resources refers to a DirContext object in a class loader whose document base points to the context's document base.
本章中广泛使用了两个术语:repository
和 resources
。
repository
是类加载器将搜索的位置。
resources
一词指的是类加载器中的DirContext
对象,其文档基准指向上下文的文档基准。
Part3Java Class Loader Java 类加载器
Every time you create an instance of a Java class, the class must first be loaded into memory. The JVM uses a class loader to load classes. The class loader normally searches some core Java libraries and all directories included in the CLASSPATH environment variable. If it does not find the required class, it throws a java.lang.ClassNotFoundException.
每当您创建一个 Java
类的实例时,该类必须首先被加载到内存中。
JVM 使用类加载器来加载类。类加载器通常搜索一些核心的 Java
库和 CLASSPATH
环境变量中包含的所有目录。
如果找不到所需的类,则会抛出 java.lang.ClassNotFoundException
异常。
Starting from J2SE 1.2, the JVM employs three class loaders: bootstrap class loader, extension class loader, and system class loader. Each of the three class loaders has a parent-child relationship with each other, in which the bootstrap class loader sits at the top of the hierarchy and the system class loader at the bottom.
从 J2SE 1.2 开始,JVM 使用三个类加载器:引导类加载器、扩展类加载器和系统类加载器。
这三个类加载器之间存在父子关系,其中引导类加载器位于层次结构的顶部,系统类加载器位于底部。
The bootstrap class loader is used to bootstrap the JVM. It starts working whenever you call the java.exe program. As such, it must be implemented using the native code because it is used to load the classes required for the JVM to function. Also, it is responsible for loading all the core Java classes, such as those in java.lang and java.io packages. The bootstrap class loader searches the core libraries such as rt.jar, i18n.jar, etc. Which libraries are searched depends on the version of the JVM and the operating system.
引导类加载器用于引导 JVM
。
每当调用 java.exe
程序时,它开始工作。
因此,它必须使用本机代码实现,因为它用于加载 JVM 运行所需的类。
此外,它负责加载所有核心的 Java
类,例如 java.lang
和 java.io
包中的类。
引导类加载器搜索核心库,例如 rt.jar
、i18n.jar
等。
搜索哪些库取决于 JVM
的版本和操作系统。
The extension class loader is responsible for loading classes in a standard extension directory. This is to make the programmer's life easier because they can just copy JAR files into this extension directory and the jar files will be searched automatically. The extension library differs from one vendor to another. Sun's JVM's standard extension directory is /jdk/jre/lib/ext.
扩展类加载器负责加载标准扩展目录中的类。
这样可以方便程序员,因为他们可以将 JAR 文件复制到此扩展目录中,而这些 JAR 文件将被自动搜索。
扩展库因供应商而异。
Sun
的 JVM
的标准扩展目录为 /jdk/jre/lib/ext
。
The system class loader is the default class loader and searches the directories and JAR files specified in the CLASSPATH environment variable.
系统类加载器是默认的类加载器,它搜索在 CLASSPATH
环境变量中指定的目录和 JAR 文件。
So, which class loader does the JVM use? The answer lies in the delegation model, which is there for security reasons. Every time a class needs to be loaded, the system class loader is first called. However, it does not load the class right away. Instead, it delegates the task to its parent, the extension class loader. The extension class loader also delegates it to its parent, the bootstrap class loader. Therefore, the bootstrap class loader is always given the first chance to load a class. If the bootstrap class loader can't find the class needed, the extension class loader will try to load the class. If the extension class loader also fails, the system class loader will perform the task. If the system class loader can't find the class, a java.lang.ClassNotFoundException is thrown. Why the round trip?
那么,JVM 使用哪个类加载器呢?答案在于委托模型,这是出于安全原因。
每次需要加载类时,首先调用系统类加载器。
然而,它不会立即加载类。
相反,它将任务委托给其父类加载器,即扩展类加载器。
扩展类加载器也将其委托给其父类加载器,即引导类加载器。
因此,引导类加载器始终首先获得加载类的机会。
如果引导类加载器无法找到所需的类,则扩展类加载器将尝试加载该类。
如果扩展类加载器也失败,则系统类加载器将执行该任务。
如果系统类加载器无法找到该类,则会抛出 java.lang.ClassNotFoundException
异常。为什么要来回这么多次呢?
The delegation model is very important for security. As you know, you can use the security manager to restrict access to a certain directory. Now, someone with malicious intents can write a class called java.lang.Object that can be used to access any directory in the hard disk. Because the JVM trusts the java.lang.Object class, it will not watch its activity in this regard. As a result, if the custom java.lang.Object was allowed to be loaded, the security manager would be easily paralyzed. Fortunately, this will not happen because of the delegation model. Here is how it works.
委托模型对于安全性非常重要。
正如您所知,您可以使用安全管理器来限制对某个目录的访问。
现在,有人恶意编写了一个名为 java.lang.Object
的类,可以用来访问硬盘中的任何目录。
因为 JVM 信任 java.lang.Object
类,所以它不会监视其在这方面的活动。
结果,如果允许加载自定义的java.lang.Object
类,安全管理器将很容易被瘫痪。
幸运的是,由于委托模型的存在,这种情况不会发生。
以下是它的工作原理。
When the custom java.lang.Object class is called somewhere in the program, the system class loader delegates the request to the extension class loader, which delegates to the bootstrap class loader. The bootstrap class loader searches its core libraries, and finds the standard java.lang.Object and instantiates it. As a result, the custom java.lang.Object will never be loaded.
当程序中的某个地方调用自定义的java.lang.Object
类时,系统类加载器将请求委托给扩展类加载器,后者再委托给引导类加载器。
引导类加载器搜索其核心库,并找到标准的 java.lang.Object
类并实例化它。
因此,自定义的 java.lang.Object
类将永远不会被加载。
The great thing about the class loading mechanism in Java is that you can write your own class loader by extending the abstract java.lang.ClassLoader class. The reasons why Tomcat needs a custom class loader include the following:
Java
中的类加载机制的伟大之处在于您可以通过扩展抽象类 java.lang.ClassLoader
来编写自己的类加载器。Tomcat
需要自定义类加载器的原因包括以下几点:
- To specify certain rules in loading classes.
- To cache the previously loaded classes.
- To pre-load classes so they are ready to use.
- 指定加载类的某些规则。
- 缓存先前加载的类。
- 预加载类,使其准备好供使用。
Part4The Loader Interface Loader接口
There are rules in loading servlet and other classes in a web application. For example, a servlet in an application can use classes deployed to the WEB-INF/classes directory and any sub-directory under it. However, servlets do not have access to other classes, even though those classes are included in the CLASSPATH of the JVM running Tomcat. Also, a servlet can only access libraries deployed under the WEB-INF/lib directory and not other directories.
在Web应用程序中,加载 Servlet
和其他类有一些规则。
例如,应用程序中的 Servlet
可以使用部署到WEB-INF/classes
目录及其子目录下的类。
然而,即使这些类已经包含在运行 Tomcat
的 JVM
的 CLASSPATH
中,Servlet也无法访问其他类。
此外,Servlet
只能访问部署在WEB-INF/lib
目录下的库,而不能访问其他目录。
A Tomcat loader represents a web application loader rather than a class loader. A loader must implement the org.apache.catalina.Loader interface. The loader implementation uses a custom class loader represented by the org.apache.catalina.loader.WebappClassLoader class. You can obtain the ClassLoader inside a web loader using the Loader interface's getClassLoader method.
Tomcat的加载器表示一个Web应用程序加载器,而不是一个类加载器。
加载器必须实现org.apache.catalina.Loader
接口。
加载器实现使用由org.apache.catalina.loader.WebappClassLoader
类表示的自定义类加载器。
您可以使用Loader
接口的getClassLoader
方法在Web加载器内获取ClassLoader
。
Among others, the Loader interface defines methods to work with a collection of repositories. The WEB-INF/classes and WEB-INF/lib of a web application are directories to be added as repositories. The Loader interface's addRepository method is used to add a repository and its findRepositories method returns an array of all repositories.
Loader
接口定义了与一组存储库一起工作的方法。
Web应用程序的WEB-INF/classes和WEB-INF/lib
目录是要添加为存储库的目录。
Loader
接口的 addRepository
方法用于添加存储库,而其 findRepositories
方法返回所有存储库的数组。
A Tomcat loader implementation is usually associated with a context, and the getContainer and setContainer methods of the Loader interface are used for building this association. A loader can also supports reloading, if one or more classes in a context have been modified. This way, a servlet programmer can recompile a servlet or a supporting class and the new class will be reloaded without restarting Tomcat. For the reloading purpose, the Loader interface has the modified method. In a loader implementation, the modified method must return true if one or more classes in its repositories have been modified, and therefore reloading is required. A loader does not do the reloading itself, however. Instead, it calls the Context interface's reload method. Two other methods, setReloadable and getReloadable, are used to determine if reloading is enabled in the Loader. By default, in the standard implementation of Context (the org.apache.catalina.core.StandardContext class, which is discussed in Chapter 12), reloading is not enabled. Therefore, to enable reloading of a context, you need to add a Context element for that context in your server.xml file, such as the following:
通常,Tomcat
的加载器实现与一个上下文相关联,Loader
接口的 getContainer
和 setContainer
方法用于建立这种关联。
如果上下文中的一个或多个类已被修改,加载器还可以支持重新加载。
这样,Servlet
程序员可以重新编译Servlet或支持类,新类将在不重新启动 Tomcat
的情况下重新加载。
为了实现重新加载,Loader
接口提供了 modified
方法。
在加载器实现中,如果其存储库中的一个或多个类已被修改,则 modified
方法必须返回 true
,从而需要重新加载。
然而,加载器本身不执行重新加载。
相反,它调用 Context
接口的reload
方法。
另外,还有两个方法setReloadable
和getReloadable
用于确定加载器是否启用重新加载。
在 Context
的标准实现(org.apache.catalina.core.StandardContext
类,在第12章中讨论)中,默认情况下不启用重新加载。
因此,要启用上下文的重新加载,您需要在server.xml
文件中为该上下文添加一个Context
元素,例如下面的示例:
<Context path="/myApp" docBase="myApp" debug="0" reloadable="true"/>
Also, a Loader implementation can be told whether or not to delegate to a parent class loader. For this purpose, the Loader interface provides the getDelegate and setDelegate methods.
此外,还可以告诉 Loader
实现是否委托给父类装载器。为此,Loader 接口提供了 getDelegate
和 setDelegate
方法。
The Loader interface is given in Listing 8.1.
清单 8.1 给出了 Loader
接口。
Listing 8.1: The Loader interface
清单 8.1:加载器接口
代码语言:javascript复制
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 provides the org.apache.catalina.loader.WebappLoader as an implementation of the Loader interface. For its class loader, the WebappLoader object contains an instance of the org.apache.catalina.loader.WebappClassLoader class, which extends the java.net.URLClassLoader class.
Catalina
提供 org.apache.catalina.loader.WebappLoader
作为加载器接口的实现。
对于其类加载器,WebappLoader
对象包含 org.apache.catalina.loader.WebappClassLoader
类的一个实例,该类扩展了 java.net.URLClassLoader
类。
Note Whenever the container associated with a loader needs a servlet class, i.e. when its invoke method is called, the container first calls the loader's getClassLoader method to obtain the class loader. The container then calls the loadClass method of the class loader to load the servlet class. More details on this can be found in Chapter 11, "StandardWrapper".
注意 每当与加载器关联的容器需要一个 servlet
类时,即调用其 invoke
方法时,容器首先调用加载器的 getClassLoader
方法来获取类加载器。
然后,容器调用类加载器的 loadClass
方法来加载 servlet
类。有关这方面的更多详情,请参阅第 11 章 "StandardWrapper
"。
The class diagram for the Loader interface and its implementation is given in Figure 8.1.
图 8.1 给出了加载器接口及其实现的类图。
Figure 8.1: The Loader interface and its implementation
Figure 8.1: The Loader interface and its implementation
图 8.1:加载器接口及其实现
Part5The Reloader Interface Reloader 接口
To support automatic reloading, a class loader implementation must implement the org.apache.catalina.loader.Reloader interface in Listing 8.2.
要支持自动重载,类加载器必须实现清单 8.2 中的 org.apache.catalina.loader.Reloader
接口。
Listing 8.2: The Reloader interface
清单 8.2:重载器接口
代码语言:javascript复制package org.apache.catalina.loader;
public interface Reloader {
public void addRepository(String repository);
public String[] findRepositories ();
public boolean modified();
}
The most important method of the Reloader interface is modified, which returns true if one of the servlet or supporting classes in a web application has been modified. The addRepository method is used to add a repository and the findRepositories method returns a String array of all repositories in the class loader implementing Reloader.
Reloader
接口最重要的方法是 modified
,如果 Web
应用程序中的 servlet
或支持类之一已被修改,则返回true。
addRepository
方法用于添加一个存储库,findRepositories
方法返回实现 Reloader
接口的类加载器中所有存储库的字符串数组。
Part6The WebappLoader Class(WebappLoader类)
The org.apache.catalina.loader.WebappLoader class is the implementation of the Loader interface and represents a web application loader responsible for loading classes for a web application. WebappLoader creates an instance of the org.apache.catalina.loader.WebappClassLoader class as its class loader. Like other Catalina components, WebappLoader implements org.apache.catalina.Lifecycle and it is started and stopped by the associated container. The WebappLoader class also implements the java.lang.Runnable interface so that it can dedicate a thread for repeatedly calling the modified method of its class loader. If the modified method returns true, the WebappLoader instance notifies its associated container (in this case a context). The class reloading itself is performed by the Context, not by the WebappLoader. How the Context does this is discussed in Chapter 12, "StandardContext".
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
执行,而不是 WebappLoader
。
如何执行这个过程在第12章“ StandardContext
”中讨论。
Important tasks are performed when the WebappLoader class's start method is called:
调用 WebappLoader
类的 start
方法时执行重要任务:
- Creating a class loader
- Setting repositories
- Setting the class path
- Setting permissions
- Starting a new thread for auto-reload.
- 创建类加载器
- 设置存储库
- 设置类路径
- 设置权限
- 为自动重新加载启动一个新线程。
Each of these tasks is discussed in the following sub-sections.
下面的子节讨论了每个任务。
1Creating A Class Loader(创建类加载器)
For loading classes, a WebappLoader instance employs an internal class loader. You may recall from the discussion of the Loader interface that this interface provides the getClassLoader method but there is no setClassLoader. Therefore, you cannot instantiate a class loader and pass it to the WebappLoader. Does it mean that the WebappLoader does not have the flexibility to work with a non-default class loader?
为了加载类,WebappLoader
实例使用内部类加载器。
您可能还记得在 Loader
接口的讨论中提到,该接口提供了 getClassLoader
方法,但没有 setClassLoader
方法。
因此,您无法实例化一个类加载器并将其传递给 WebappLoader
。
这是否意味着 WebappLoader
没有与非默认类加载器一起工作的灵活性?
The answer is no. The WebappLoader provides the getLoaderClass and setLoaderClass methods to obtain and change the value of its private variable loaderClass. This variable is a String representing the name of the class of the class loader. By default, the value of loaderClass is org.apache.catalina.loader.WebappClassLoader. If you wish, you can create your own class loader that extends WebappClassLoader and call the setLoaderClass to force your WebappLoader to use your custom class loader. Otherwise, when it is started, the WebappLoader will create an instance of WebappClassLoader by calling its private createClassLoader method. This method is given in Listing 8.3.
答案是否定的。WebappLoader
提供了 getLoaderClass
和 setLoaderClass
方法,以获取和更改其私有变量 loaderClass
的值。
这个变量是一个字符串,表示类加载器的类名。默认情况下,loaderClass
的值是 org.apache.catalina.loader.WebappClassLoader
。
如果希望,您可以创建一个扩展 WebappClassLoader
的自定义类加载器,并调用 setLoaderClass
来强制 WebappLoader
使用您的自定义类加载器。
否则,在启动时,WebappLoader
将通过调用其私有 createClassLoader
方法创建 WebappClassLoader
的实例。
该方法在列表8.3中给出。
Listing 8.3: The createClassLoader method
列表8.3:createClassLoader
方法的实现
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;
}
It is possible to use a different class loader other than an instance of WebappClassLoader. Note, however, that the createClassLoader method returns a WebappClassLoader. Therefore, if your custom class loader does not extend WebappClassLoader, this method will throw an exception.
可以使用除 WebappClassLoader
实例之外的其他类加载器。
但是请注意,createClassLoader
方法返回的是 WebappClassLoader
。
因此,如果您的自定义类加载器没有扩展 WebappClassLoader
,这个方法将抛出异常。
2Setting Repositories(设置仓库)
The WebappLoader class's start method calls the setRepositories method to add repositories to its class loader. The WEB-INF/classes directory is passed to the class loader's addRepository method and the WEB-INF/lib directory is passed to the class loader's setJarPath method. This way, the class loader will be able to load classes in the WEB-INF/classes directory and from any library deployed to the WEB-INF/lib directory.
WebappLoader类的start方法调用setRepositories方法向其类加载器添加仓库。
将WEB-INF/classes
目录传递给类加载器的 addRepository
方法,将WEB-INF/lib目录传递给类加载器的 setJarPath
方法。
这样,类加载器将能够加载WEB-INF/classes
目录中的类,并从部署到WEB-INF/lib
目录的任何库中加载类。
3Setting the Class Path(设置类路径)
This task is performed by the start method by calling the setClassPath method. The setClassPath method assigns to an attribute in the servlet context a string containing the class path information for the Jasper JSP compiler. It will not be discussed here.
这个任务由 start
方法通过调用 setClassPath
方法来完成。
setClassPath
方法将一个包含 Jasper
JSP编译器的类路径信息的字符串分配给 servlet
上下文中的一个属性。这里不会详细讨论。
4Setting Permissions(设置权限)
If the security manager is used when running Tomcat, the setPermissions method adds the permissions to the class loader to access necessary directories, such as WEB-INF/classes and WEB-INF/lib. If no security manager is used, this method returns right away.
如果在运行Tomcat时使用了安全管理器,setPermissions方法会向类加载器添加访问必要目录(如WEB-INF/classes和WEB-INF/lib)的权限。如果没有使用安全管理器,该方法将立即返回。
5Starting a New Thread for Auto-Reload(为自动重新加载启动新线程)
WebappLoader supports auto-reload. If a class in the WEB-INF/classes or WEB-INF/lib directories is re-compiled, the class must be reloaded automatically without restarting Tomcat. For this purpose, WebappLoader has a thread that continuously checks the date stamp of each resource every x seconds. The x here is defined by the value of the checkInterval variable. By default its value is 15, meaning that a check for auto-reload is performed every 15 seconds. The getCheckInterval and setCheckInterval methods are used to access this variable.
WebappLoader
支持自动重新加载。
如果WEB-INF/classes
或WEB-INF/lib
目录中的类重新编译,必须自动重新加载该类而无需重新启动Tomcat。
为此,WebappLoader
有一个线程,每隔x秒钟连续检查每个资源的日期时间戳。
这里的 x
由 checkInterval
变量的值定义。
默认情况下,其值为15,表示每隔15秒执行一次自动重新加载的检查。
可以使用 getCheckInterval
和 setCheckInterval
方法访问此变量。
In Tomcat 4 WebappLoader implements the java.lang.Runnable interface to support auto-reload. The implementation of the run method in WebappLoader is given in Listing 8.3.
在Tomcat 4中,WebappLoader
实现了 java.lang.Runnable
接口以支持自动重新加载。
WebappLoader
中 run
方法的实现如下(见清单8.3)。
Listing 8.3: The run method
清单 8.3:运行方法
代码语言:javascript复制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");
}
Note In Tomcat 5 the task of checking for modified classes is performed by the backgroundProcess method of the org.apache.catalina.core.StandardContext object. This method is called periodically by a dedicated thread in the org.apache.catalina.core.ContainerBase class, the parent class of StandardContext. Check the ContainerBase class's ContainerBackgroundProcessor inner class that implements Runnable. 在 Tomcat 5 中,检查是否有修改类的任务由
org.apache.catalina.core.StandardContext
对象的 backgroundProcess 方法执行。该方法由StandardContext
的父类org.apache.catalina.core.ContainerBase
类中的专用线程定期调用。检查实现Runnable
的ContainerBase
类的 ContainerBackgroundProcessor 内部类。
At its core, the run method in Listing 8.3 contains a while loop that will run until the started variable (used to indicate that the WebappLoader has been started) is set to false. The while loop does the following:
在其核心部分,清单 8.3 中的运行方法包含一个 while 循环,该循环将一直运行到 started 变量(用于指示 WebappLoader 已启动)被设置为 false
。
while
循环执行以下操作
- Sleep for the period specified by the checkInterval variable.
- Check if any class it loaded has been modified by calling the modified method of the WebappLoader instance's class loader. If not, continue.
- If a class has been modified, call the notifyContext private method to ask the Context associated with this WebappLoader to reload.
- 在
checkInterval
变量指定的时间内休眠。 - 调用
WebappLoader
实例的类加载器的modified
方法,检查加载的任何类是否已被修改。如果没有,则继续。 - 如果类已被修改,则调用
notifyContext
私有方法,要求与此WebappLoader
关联的上下文重新加载。
The notifyContext method is given in Listing 8.4
清单 8.4 中给出了 notifyContext 方法
Listing 8.4: The notifyContext method
清单 8.4:notifyContext 方法
代码语言:javascript复制private void notifyContext() {
WebappContextNotifier notifier = new WebappContextNotifier();
(new Thread(notifier)).start();
}
The notifyContext method does not call the Context interface's reload method directly. Instead, it instantiates the inner class WebappContextNotifier and passes the thread object and calls its start method. This way, the execution of reload will be performed by a different thread. The WebappContextNotifier class is given in Listing 8.5.
notifyContext
方法不会直接调用 Context 接口的 reload 方法。
相反,它会实例化内部类 WebappContextNotifier
,并传递线程对象和调用其 start 方法。
这样,重新加载将由不同的线程执行。WebappContextNotifier
类见清单 8.5。
Listing 8.5: The WebappContextNotifier inner class
清单 8.5:WebappContextNotifier
内部类
protected class WebappContextNotifier implements Runnable {
public void run() {
((Context) container).reload();
}
}
When an instance of WebappContextNotifier is passed to a Thread and the Thread object's start method is invoked, the run method of the WebappContextNotifier instance will be executed. In turn, the run method calls the reload method of the Context interface. You can see how the reload method is implemented in the org.apache.catalina.core.StandardContext class in Chapter 12.
当一个WebappContextNotifier
的实例被传递给一个Thread
对象,并且调用了Thread
对象的start
方法时,WebappContextNotifier
实例的run
方法将被执行。
接着,run方法会调用 Context
接口的 reload
方法。
你可以在第12章中查看 reload
方法在org.apache.catalina.core.StandardContext
类中的实现。
Part7The WebappClassLoader Class(WebappClassLoader 类)
The org.apache.catalina.loader.WebappClassLoader class represents the class loader responsible for loading the classes used in a web application. WebappClassLoader extends the java.net.URLClassLoader class, the class we used for loading Java classes in the applications in the previous chapters.
org.apache.catalina.loader.WebappClassLoader
类代表负责加载Web应用程序中使用的类的类加载器。
WebappClassLoader
继承自java.net.URLClassLoader
类,这是我们在前几章中用于加载Java类的类。
WebappClassLoader was designed for optimization and security in mind. For example, it caches the previously loaded classes to enhance performance. It also caches the names of classes it has failed to find, so that the next time the same classes are requested to be loaded, the class loader can throw the ClassNotFoundException without first trying to find them. WebappClassLoader searches for classes in the list of repositories as well as the specified JAR files.
WebappClassLoader
的设计考虑了优化和安全性。
例如,它缓存先前加载的类以提高性能。它还缓存了未能找到的类的名称,这样下次请求加载相同的类时,类加载器可以直接抛出ClassNotFoundException
而不是先尝试查找它们。
WebappClassLoader
在存储库列表和指定的JAR文件中搜索类。
With regard to security, the WebappClassLoader will not allow certain classes to be loaded. These classes are stored in a String array triggers and currently has one member:
关于安全性,WebappClassLoader
不允许加载某些类。这些类存储在一个字符串数组 triggers
中,目前只有一个成员。
private static final String[] triggers = {
"javax.servlet.Servlet" // Servlet API
};
Also, you are not allowed to load classes belonging to these packages and subpackages under them, without first delegating to the system class loader:
此外,在未委托系统类加载器的情况下,不允许加载属于这些包和包下子包的类:
代码语言:javascript复制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
};
Let's now look at how this class performs caching and class loading in the following sub-sections.
现在,让我们在下面的小节中看看该类是如何执行缓存和类加载的。
6Caching(缓存)
For better performance, classes that are loaded are cached so that the next time a class is required, it can be taken from the cache. Caching can be done locally, meaning that the cache is managed by the WebappClassLoader instance. In addition, the java.lang.ClassLoader maintains a Vector of previously loaded classes to prevent those classes from being garbage-collected. In this case, caching is managed by the super class.
为了提高性能,加载的类会被缓存起来,以便下次需要时可以从缓存中获取。
缓存可以在本地完成,即缓存由 WebappClassLoader 实例管理。
此外,java.lang.ClassLoader
还会维护一个以前加载过的类的向量,以防止这些类被垃圾收集。在这种情况下,缓存由超类管理。
Each class (either deployed as a class file under WEB-INF/classes or from a JAR file) that may be loaded by WebappClassLoader is referred to as a resource. A resource is represented by the org.apache.catalina.loader.ResourceEntry class. A ResourceEntry instance holds the byte array representation of the class, the last modified date, the Manifest (if the resource is from a JAR file), etc.
WebappClassLoader 可以加载的每个类(可以作为类文件部署在 WEB-INF/classes 下,也可以来自 JAR 文件)都被称为资源。
资源由 org.apache.catalina.loader.ResourceEntry 类表示。ResourceEntry 实例保存类的字节数组表示、最后修改日期、Manifest(如果资源来自 JAR 文件)等信息。
The ResourceEntry class is given in Listing 8.6.
清单 8.6 给出了 ResourceEntry 类。
Listing 8.6: The ResourceEntry class.
清单 8.6:ResourceEntry 类。
代码语言:javascript复制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;
}
All cached resources are stored in a HashMap called resourceEntries. The keys are the resource names. All resources that were not found are stored in another HashMap called notFoundResources.
所有缓存资源都存储在名为 resourceEntries
的 HashMap
中。
键是资源名称。所有未找到的资源都存储在另一个名为 notFoundResources
的 HashMap
中。
7Loading Classes(加载类)
When loading a class, the WebappClassLoader class applies these rules:
- All previously loaded classes are cached, so first check the local cache.
- If not found in the local cache, check in the cache, i.e. by calling the findLoadedClass of the java.lang.ClassLoader class.
- If not found in both caches, use the system's class loader to prevent the web application from overriding J2EE class.
- If SecurityManager is used, check if the class is allowed to be loaded. If the class is not allowed, throw a ClassNotFoundException.
- If the delegate flag is on or if the class to be loaded belongs to the package name in the package trigger, use the parent class loader to load the class. If the parent class loader is null, use the system class loader.
- Load the class from the current repositories.
- If the class is not found in the current repositories, and if the delegate flag is not on, use the parent class loader. If the parent class loader is null, use the system class loader.
- If the class is still not found, throw a ClassNotFoundException.
- 所有先前加载的类都被缓存,因此首先检查本地缓存。
- 如果在本地缓存中找不到,则通过调用
java.lang.ClassLoader
类的findLoadedClass
方法在缓存中查找。 - 如果在两个缓存中都找不到,则使用系统的类加载器来防止Web应用程序覆盖J2EE类。
- 如果使用了SecurityManager,请检查是否允许加载该类。如果不允许加载该类,则抛出
ClassNotFoundException
异常。 - 如果委托标志打开,或者要加载的类属于包名称中的包触发器,则使用父类加载器来加载该类。如果父类加载器为
null
,则使用系统类加载器。 - 从当前存储库加载类。
- 如果在当前存储库中找不到该类,并且委托标志没有打开,则使用父类加载器。如果父类加载器为
null
,则使用系统类加载器。 - 如果仍然找不到该类,则抛出
ClassNotFoundException
异常。
8The Application(应用)
The application accompanying this chapter demonstrates how to use a WebappLoader instance associated with a context. The standard implementation of Context is the org.apache.catalina.core.StandardContext class, therefore this application instantiates the StandardContext class. However, the discussion of StandardContext itself will be deferred until Chapter 12. You do not need to understand the details of this class at this stage. All you need to know about StandardContext is that it works with a listener that listens to events it fires, such as the START_EVENT and STOP_EVENT. The listener must implement the org.apache.catalina.lifecycle.LifecycleListener interface and call the StandardContext class's setConfigured method. For this application, the listener is represented by the ex08.pyrmont.core.SimpleContextConfig class, which is given in Listing 8.6.
本章附带的应用程序演示了如何使用与上下文关联的 WebappLoader
实例。
Context
的标准实现是org.apache.catalina.core.StandardContext
类,
因此该应用程序实例化 StandardContext
类。
但是,对 StandardContext
本身的讨论将推迟到第12章。
您在此阶段不需要了解有关该类的详细信息。
您只需要知道 StandardContext
与监听器一起工作,该监听器监听它触发的事件,
例如 START_EVENT
和 STOP_EVENT
。监听器必须实现org.apache.catalina.lifecycle.LifecycleListener
接口,并调用StandardContext类的setConfigured方法。对于此应用程序,监听器由ex08.pyrmont.core.SimpleContextConfig
类表示,该类在列表8.6中给出。
Listing 8.6: The SimpleContextConfig class
列表8.6:SimpleContextConfig
类
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);
}
}
}
All you need to do is instantiate both StandardContext and SimpleContextConfig and then register the latter with StandardContext by calling the addLifecycleListener method of the org.apache.catalina.Lifecycle interface. This interface was discussed in detail in Chapter 6, "Lifecycles".
你只需要实例化 StandardContext
和 SimpleContextConfig
,然后通过调用org.apache.catalina.Lifecycle
接口的addLifecycleListener方法将后者注册到 StandardContext
中。这个接口在第6章“生命周期”中详细讨论过。
In addition, the application retains the following classes from the previous chapter: SimplePipeline, SimpleWrapper, and SimpleWrapperValve.
此外,应用程序保留了上一章中的以下类:SimplePipeline
、SimpleWrapper
和 SimpleWrapperValve
。
The application can be tested using both PrimitiveServlet and ModernServlet, but this time the use of StandardContext dictates the servlets be stored under the WEB-INF/classes of an application directory. The application directory is called myApp and should have been created when you deployed the downloadable ZIP file the first time. To tell the StandardContext instance where to find the application directory, you set a system property called catalina.base with the value of the user.dir property, as follows.
可以使用 PrimitiveServlet
和 ModernServlet
来测试应用程序,但是这次使用 StandardContext
要求将 servlet
存储在应用程序目录的WEB-INF/classes
下。
应用程序目录名为 myApp
,当您第一次部署可下载的ZIP文件时应该已经创建了。
为了告诉StandardContext
实例在哪里找到应用程序目录,您需要设置一个名为catalina.base
的系统属性,其值为user.dir
属性的值,如下所示。
System.setProperty("catalina.base", System.getProperty("user.dir"));
In fact, that is the first line in the main method of the Bootstrap class. Afterwards, the main method instantiates the default connector.
事实上,这是 Bootstrap
类主方法的第一行。
之后,主方法会实例化默认连接器。
代码语言:javascript复制Connector connector = new HttpConnector();
It then creates two wrappers for the two servlets and initialize them, just like the application in the previous chapter.
然后,它为两个 servlet 创建了两个包装器,并对其进行初始化,就像上一章中的应用程序一样。
代码语言:javascript复制Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");
It then creates an instance of StandardContext and set the path as well as the document base of the context.
然后,它会创建一个 StandardContext 实例,并设置上下文的路径和文档库。
代码语言:javascript复制Context context = new StandardContext();
// StandardContext's start method adds a default mapper
context.setPath("/myApp");
context.setDocBase("myApp");
This is equivalent to having the following element in the server.xml file.
这相当于在 server.xml
文件中加入以下元素。
<Context path="/myApp" docBase="myApp"/>
Then, the two wrappers are added to the context, and it adds the mapping for both so that the context can locate the wrappers.
然后,两个包装器被添加到上下文中,并为两者添加映射,以便上下文可以定位包装器。
代码语言:javascript复制context.addChild(wrapper1);
context.addChild(wrapper2);
context.addServletMapping("/Primitive", "Primitive");
context.addServletMapping("/Modern", "Modern");
The next step is to instantiate a listener and register it with the context.
下一步是实例化一个监听器,并将其注册到上下文中。
代码语言:javascript复制LifecycleListener listener = new SimpleContextConfig();
((Lifecycle) context).addLifecycleListener(listener);
Next, it instantiates the WebappLoader and associates it with the context.
接下来,它会实例化 WebappLoader
并将其与上下文关联起来。
Loader loader = new WebappLoader();
context.setLoader(loader);
Afterwards, the context is associated with the default connector, and the connector's initialize and start methods are called, followed by the context's start method. This puts the servlet container into service.
随后,上下文会与默认连接器关联,连接器的初始化和启动方法会被调用,接着上下文的启动方法也会被调用。
这样,Servlet
容器就开始运行了。
connector.setContainer(context);
try {
connector.initialize();
((Lifecycle) connector).start();
((Lifecycle) context).start();
The next lines simply display the value of the resources' docBase and all the repositories in the class loader.
接下来的几行只是显示类加载器中资源 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]);
}
These lines will make docBase and the list of repositories displayed when you run the application.
这些行将使 docBase
和版本库列表在运行应用程序时显示。
Resources' docBase: C:HowTomcatWorksmyApp
repository: /WEB-INF/classes/
The value of docBase may be different on your machine, depending where you install the application.
docBase
的值在你的机器上可能有所不同,这取决于你在哪里安装应用程序。
Finally, the application waits until the user presses Enter on the console to stop the application.
最后,程序会等待用户在控制台按 Enter
键停止程序。
// make the application wait until we press a key.
System.in.read();
((Lifecycle) context).stop();
9Running the Application(运行应用程序)
To run the application in Windows, from the working directory, type the following:
要在 Windows
中运行应用程序,请在工作目录中键入以下内容:
java -classpath ./lib/servlet.jar;./lib/commons-collections.jar;./ ex08.pyrmont.startup.Bootstrap
In Linux, you use a colon to separate two libraries.
在 Linux 中,使用冒号分隔两个库。
代码语言:javascript复制java -classpath ./lib/servlet.jar:./lib/commons-collections.jar:./
ex08.pyrmont.startup.Bootstrap
To invoke PrimitiveServlet servlet, you use the following URL in your browser.
要调用 PrimitiveServlet servlet,请在浏览器中使用以下 URL。
http://localhost:8080/Primitive
To invoke ModernServlet, you use the following URL.
要调用 ModernServlet,请使用以下 URL。
http://localhost:8080/Modern
Part8Summary(摘要)
The web application loader, or simply a loader, is a most important component in Catalina. A loader is responsible for loading classes and therefore employs an internal class loader. This internal class loader is a customized class that is used by Tomcat to apply certain rules to the class loading within an application context. Also, the customized class loader supports caching and can check whether or not one or more classes have been modified.
网络应用程序加载器(简称加载器)是 Catalina
中最重要的组件。加载器负责加载类,因此需要使用内部类加载器。
该内部类加载器是一个定制的类,Tomcat
使用它来对应用程序上下文中的类加载应用某些规则。
此外,定制的类加载器还支持缓存,并能检查一个或多个类是否已被修改。