通过学习tomcat载入器了解下tomcat是如何载入整个webapp应用项目的
servlet容器需要实现一个自定义的载入器,而不能简单地使用系统的类载入器,因为servlet容器不应该完全信任它正在运行的servlet类。如果使用系统类的载入器载入某个servlet类所使用的全部类,那么servlet就能访问所有的类,包括当期运行的java虚拟机中环境变量指明的所有类、库。这是非常危险的。servlet应该只允许载入WEB-INF/classes目录及其子目录下的类,和从部署的库到WEB-INFO/lib目录载入类。
Tomcat中需要实现自定义载入器的另一个原因是,为了提供自动重载的功能,即当web-info/classes目录或者web-info/lib目录下的类发生变化时,web应用程序会重新载入这些类。在tomcat载入器的实现中,类载入器使用一个额外的线程来不断地检查servlet类和其他类的文件时间戳,若要支持自动重载功能,载入器必须实现org.apache.catalina.loader.Reloader接口。
JAVA的类加载器
- 根加载器(bootstrap class loader),加载jdk_home/lib/rt.jar
- 扩展加载器(extension class loader),加载jdk_home/jre/lib/*.jar
- 系统类加载器(system class loader),加载项目中的jar文件
- 自定义加载器,加载外部类或其他
Tomcat的载入器通常会与一个Context级别的servlet容器相关联,loader接口的getContainer()方法和setContainer()方法用来将载入器与某个servlet容器相关联。如果context容器中的一个或多个类被修改了,载入器也可以支持对类的自动重载。这样,servlet程序猿就可以重新编译servlet类及相关类,并重新载入而不需要重新启动Tomcat。Loader接口使用modified()方法来支持类的自动重载。
在默认情况下,Context接口的标准实现(org.apache.catalina.core.StandardContexgt)是禁用了自动重载功能的。如果想启用自动重载共鞥,需要在server.xml文件中添加context元素,如下:
<Context path="/myApp" docBase="myApp" debug="0” reloadable="true" />
相关接口及类代码:
- Loader接口(定义载入web应用程序,还有一些方法对仓库的集合进行操作)
package org.apache.catalina;
import java.beans.PropertyChangeListener;
public abstract interface Loader
{
public abstract ClassLoader getClassLoader();
public abstract Container getContainer();
public abstract void setContainer(Container paramContainer);
public abstract DefaultContext getDefaultContext();
public abstract void setDefaultContext(DefaultContext paramDefaultContext);
public abstract boolean getDelegate();
public abstract void setDelegate(boolean paramBoolean);
public abstract String getInfo();
public abstract boolean getReloadable();
public abstract void setReloadable(boolean paramBoolean);
public abstract void addPropertyChangeListener(PropertyChangeListener paramPropertyChangeListener);
public abstract void addRepository(String paramString);
public abstract String[] findRepositories();
public abstract boolean modified();
public abstract void removePropertyChangeListener(PropertyChangeListener paramPropertyChangeListener);
}
- Reloader接口(自动重载功能)
public interface Reloader{
public void addRepository(String respository);
public String[] findRepositories();
public boolean modified();
}
- WebappLoader类,是Loader接口的实现类。
执行start方法后,完成几项工作:创建一个类载入器;设置仓库;设置类路径;设置访问权限;启动一个新线程来支持自动重载。
public void start()
throws LifecycleException
{
if (this.started) {
throw new LifecycleException(sm.getString("webappLoader.alreadyStarted"));
}
if (this.debug >= 1) {
log(sm.getString("webappLoader.starting"));
}
this.lifecycle.fireLifecycleEvent("start", null);
this.started = true;
if (this.container.getResources() == null) {
return;
}
URLStreamHandlerFactory streamHandlerFactory = new DirContextURLStreamHandlerFactory();
try
{
URL.setURLStreamHandlerFactory(streamHandlerFactory);
}
catch (Throwable t) {}
try
{
this.classLoader = createClassLoader();
this.classLoader.setResources(this.container.getResources());
this.classLoader.setDebug(this.debug);
this.classLoader.setDelegate(this.delegate);
if ((this.container instanceof StandardContext)) {
this.classLoader.setAntiJARLocking(((StandardContext)this.container).getAntiJARLocking());
}
for (int i = 0; i < this.repositories.length; i++) {
this.classLoader.addRepository(this.repositories[i]);
}
setRepositories();
setClassPath();
setPermissions();
if ((this.classLoader instanceof Lifecycle)) {
this.classLoader.start();
}
DirContextURLStreamHandler.bind(this.classLoader, this.container.getResources());
}
catch (Throwable t)
{
throw new LifecycleException("start: ", t);
}
validatePackages();
if (this.reloadable)
{
log(sm.getString("webappLoader.reloading"));
try
{
threadStart();
}
catch (IllegalStateException e)
{
throw new LifecycleException(e);
}
}
}
## 创建加载器
private WebappClassLoader createClassLoader()
throws Exception
{
Class clazz = Class.forName(this.loaderClass);
WebappClassLoader classLoader = null;
if (this.parentClassLoader == null)
{
classLoader = (WebappClassLoader)clazz.newInstance();
}
else
{
Class[] argTypes = { ClassLoader.class };
Object[] args = { this.parentClassLoader };
Constructor constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoader)constr.newInstance(args);
}
return classLoader;
}
## 设置访问权限(为类载入器设置访问相关目录的权限)
private void setPermissions()
{
if (System.getSecurityManager() == null) {
return;
}
if (!(this.container instanceof Context)) {
return;
}
ServletContext servletContext = ((Context)this.container).getServletContext();
File workDir = (File)servletContext.getAttribute("javax.servlet.context.tempdir");
if (workDir != null) {
try
{
String workDirPath = workDir.getCanonicalPath();
this.classLoader.addPermission(new FilePermission(workDirPath, "read,write"));
this.classLoader.addPermission(new FilePermission(workDirPath + File.separator + "-", "read,write,delete"));
}
catch (IOException e) {}
}
try
{
URL rootURL = servletContext.getResource("/");
this.classLoader.addPermission(rootURL);
String contextRoot = servletContext.getRealPath("/");
if (contextRoot != null) {
try
{
contextRoot = new File(contextRoot).getCanonicalPath();
this.classLoader.addPermission(contextRoot);
}
catch (IOException e) {}
}
URL classesURL = servletContext.getResource("/WEB-INF/classes/");
this.classLoader.addPermission(classesURL);
URL libURL = servletContext.getResource("/WEB-INF/lib/");
this.classLoader.addPermission(libURL);
if (contextRoot != null)
{
if (libURL != null)
{
File rootDir = new File(contextRoot);
File libDir = new File(rootDir, "WEB-INF/lib/");
try
{
String path = libDir.getCanonicalPath();
this.classLoader.addPermission(path);
}
catch (IOException e) {}
}
}
else if (workDir != null)
{
if (libURL != null)
{
File libDir = new File(workDir, "WEB-INF/lib/");
try
{
String path = libDir.getCanonicalPath();
this.classLoader.addPermission(path);
}
catch (IOException e) {}
}
if (classesURL != null)
{
File classesDir = new File(workDir, "WEB-INF/classes/");
try
{
String path = classesDir.getCanonicalPath();
this.classLoader.addPermission(path);
}
catch (IOException e) {}
}
}
}
catch (MalformedURLException e) {}
}
//设置仓库、类载入器就能在WEB-INFO/classes目录中和部署的库到WEB-INFO/lib目录载入相关类
private void setRepositories()
{
if (!(this.container instanceof Context)) {
return;
}
ServletContext servletContext = ((Context)this.container).getServletContext();
if (servletContext == null) {
return;
}
File workDir = (File)servletContext.getAttribute("javax.servlet.context.tempdir");
if (workDir == null) {
return;
}
log(sm.getString("webappLoader.deploy", workDir.getAbsolutePath()));
this.classLoader.setWorkDir(workDir);
DirContext resources = this.container.getResources();
String classesPath = "/WEB-INF/classes";
DirContext classes = null;
try
{
Object object = resources.lookup(classesPath);
if ((object instanceof DirContext)) {
classes = (DirContext)object;
}
}
catch (NamingException e) {}
if (classes != null)
{
File classRepository = null;
String absoluteClassesPath = servletContext.getRealPath(classesPath);
if (absoluteClassesPath != null)
{
classRepository = new File(absoluteClassesPath);
}
else
{
classRepository = new File(workDir, classesPath);
classRepository.mkdirs();
copyDir(classes, classRepository);
}
log(sm.getString("webappLoader.classDeploy", classesPath, classRepository.getAbsolutePath()));
this.classLoader.addRepository(classesPath + "/", classRepository);
}
String libPath = "/WEB-INF/lib";
this.classLoader.setJarPath(libPath);
DirContext libDir = null;
try
{
Object object = resources.lookup(libPath);
if ((object instanceof DirContext)) {
libDir = (DirContext)object;
}
}
catch (NamingException e) {}
if (libDir != null)
{
boolean copyJars = false;
String absoluteLibPath = servletContext.getRealPath(libPath);
File destDir = null;
if (absoluteLibPath != null)
{
destDir = new File(absoluteLibPath);
}
else
{
copyJars = true;
destDir = new File(workDir, libPath);
destDir.mkdirs();
}
try
{
NamingEnumeration enumeration = resources.listBindings(libPath);
while (enumeration.hasMoreElements())
{
Binding binding = (Binding)enumeration.nextElement();
String filename = libPath + "/" + binding.getName();
if (filename.endsWith(".jar"))
{
File destFile = new File(destDir, binding.getName());
log(sm.getString("webappLoader.jarDeploy", filename, destFile.getAbsolutePath()));
Resource jarResource = (Resource)binding.getObject();
if ((!copyJars) ||
(copy(jarResource.streamContent(), new FileOutputStream(destFile))))
{
JarFile jarFile = new JarFile(destFile);
this.classLoader.addJar(filename, jarFile, destFile);
}
}
}
}
catch (NamingException e) {}catch (IOException e)
{
e.printStackTrace();
}
}
}
## 设置类路径
private void setClassPath()
{
if (!(this.container instanceof Context)) {
return;
}
ServletContext servletContext = ((Context)this.container).getServletContext();
if (servletContext == null) {
return;
}
StringBuffer classpath = new StringBuffer();
ClassLoader loader = getClassLoader();
int layers = 0;
int n = 0;
while ((layers < 3) && (loader != null) &&
((loader instanceof URLClassLoader)))
{
URL[] repositories = ((URLClassLoader)loader).getURLs();
for (int i = 0; i < repositories.length; i++)
{
String repository = repositories[i].toString();
if (repository.startsWith("file://"))
{
repository = repository.substring(7);
}
else if (repository.startsWith("file:"))
{
repository = repository.substring(5);
}
else
{
if (!repository.startsWith("jndi:")) {
continue;
}
repository = servletContext.getRealPath(repository.substring(5));
}
if (repository != null)
{
if (n > 0) {
classpath.append(File.pathSeparator);
}
classpath.append(repository);
n++;
}
}
loader = loader.getParent();
layers++;
}
servletContext.setAttribute("org.apache.catalina.jsp_classpath", classpath.toString());
}
## 如果WEB-INFO/classes目录或WEB-INFO/lib目录下的某些类被重新编译了,这个类会自动载入,无需重启Tomcat
public void run()
{
if (this.debug >= 1) {
log("BACKGROUND THREAD Starting");
}
while (!this.threadDone)
{
threadSleep();
if (this.started)
{
try
{
if (!this.classLoader.modified()) {
continue;
}
}
catch (Exception e)
{
log(sm.getString("webappLoader.failModifiedCheck"), e);
}
continue;
notifyContext();
}
}
if (this.debug >= 1) {
log("BACKGROUND THREAD Stopping");
}
}
Tomcat载入器WebappClassLoader类在载入时需要遵守如下规则:
1. 因为所有已经载入的类都会缓存起来,所以载入类时要先检查本地缓存;
2. 若本地缓存中没有,则检查上一层缓存,即调用java.lang.ClassLoader类的findLoadedClass()方法;
3. 若两个缓存中都没好友,则使用系统的类载入器进行加载,防止web应用程序中的类覆盖J2EE的类;
4. 若启用了SecurityManager,则检查是否允许载入该类。若该类是禁止载入的类,抛出ClassNotFoundException异常;
5. 若打开标志位delegate,或者待载入的类是属于包触发器中的包名,则调用父载入器来载入相关类。如果父类载入器为null,则使用系统的类载入器;
6. 从单签仓库中载入相关类;
7. 若当前仓库中没有需要的类,且标志位delegate关闭,则使用父类载入器。若父类载入器为null,则使用系统的类载入器进行加载。
8. 若未找到需要的类,抛出ClassNotFoundException异常。
这是整理自《How tamcat works》的第八章,学习了这一节,在结合之前的wrapper容器自动加载相关的内容。大致明白了tomcat是如何载入webapp程序的。用了这么久的tomcat服务器,感觉真是一款优秀的作品,给作者点赞。完整代码请看相关资源包。
原创文章,转载请标明本文链接: 通过学习tomcat载入器了解下tomcat是如何载入整个webapp应用项目的