第八章 载入器

时间:2022-11-22 09:23:14

一、概述

本章的载入器不同于前几张的简单载入器。原因有
①servlet应该只允许载入WEB-INF/LIB目录及其子目录下的类。(安全性)
②提供自动重载的功能。即当WEB-INF/LIB目录,或WEB-INF/classes目录下的类发生变化时,web应用程序会重新载入这些类。这就需要载入器使用一个额外的线程来不断地检查servlet类。

本章有两个术语:
①仓库(repository):用来表示类载入器会在哪里搜索要载入的类。
②资源(resource):指的是一个类载入器中的DirContext对象,它的文件根路径指的就是上下文的文件根路径。

二、Loader接口

tomcat中的载入器,必须实现Loader接口。
在加载servlet及相关类的时候,需要遵守一些规则。例如,应用程序中的servelt只能引用部署在web-inf/classes目录下及其子目录下的类,只能访问web-inf/lib目录下的库。
我们在tomcat里说的载入器是指web应用程序载入器,而不仅仅是类载入器。(载入器里面有个类载入器!!!)
载入器应该实现org.apache.catalina.Loader接口,类载入器默认为WebappClassLoader。

package org.apache.catalina;  

import java.beans.PropertyChangeListener;  

public interface Loader {  
    public ClassLoader getClassLoader();  
    public Container getContainer();              //载入器通常与一个context级别的容器相联 
    public void setContainer(Container container);  
    public DefaultContext getDefaultContext();  
    public void setDefaultContext(DefaultContext defaultContext);  
    public boolean getDelegate();                 //Delegate 代表 委托 
    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();                     //如果容器中的一个或多个类被修改了 modified就会返回true 
    public void removePropertyChangeListener(PropertyChangeListenerlistener);  
} 

默认情况下,在context的标准实现—org.apache.catalina.core.StandContext中是不支持自动重载的,因此要想开启自动重载功能,就需要在server.xml文件中添加一个Context元素,如下

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

在我们这一节的程序中Catalina 提供了 WebappLoader 作为 Load 接口的实现。WebappLoader 对象包含一个org.apache.catalina.loader.WebappClassLoader 类的实例,该类扩展了Java.netURLClassLoader 类。

当与某个载入器相关联的容器需要使用某个servlet时,或者说就是要调用某个servlet的某个方法时,容器首先会调用载入器的getClassLoader()方法返回类载入器,然后再调用类载入器的loadClass()方法来加载这个servlet类。

UML类图如下:
第八章 载入器

三、Reloader接口

为了支持类的自动重载功能,类载入器需要实现该接口。

public interface Reload{
    public void addRepository(String respository);
    public String[] findRepository();
    public boolean modified();
}

其中最重要的方法是modified()方法。如果web应用程序中的某个servlet或者相关的类别修改了,modified()方法会返回true.

四、WebappLoader类

此类的实例就是tomcat的载入器,负责载入web应用程序中所使用到的类。此类会创建WebappClassLoader类的一个实例作为其类载入器。像其他组件一个,该类也实现了生命周期接口。此外,该类还实现了java.lang.Runnable接口,这样,它就可以指定一个线程来不断地调用其类载入器的modified()方法。如果modified()方法返回true,WebappLoader的实例会通知其关联的servlet容器。然后有context实例,而不是Webapploader实例,来完成类的重新载入。

五、创建类载入器

为了完成载入类的作用,WebappLoader类的实例会在内部使用一个类载入器。

private WebappClassLoader createClassLoader()  
       throws Exception {  

   //loadClass为字符串 
   //默认为 private String loaderClass ="org.apache.catalina.loader.WebappClassLoader"; 
   //可通过setLoadClass方法更改 
       Class<?> clazz = Class.forName(loaderClass);  
       WebappClassLoader classLoader = null;  

   //在构造WebAppLoader时 又构造函数的参数指定 
       if (parentClassLoader == null) {  
           // Will cause a ClassCast is the class does not extend WCL, but 
           // this is on purpose (the exception will be caught and rethrown) 
           classLoader = (WebappClassLoader) clazz.newInstance();  
       } else {  
           Class<?>[] argTypes = { ClassLoader.class };  
           Object[] args = { parentClassLoader };  
           Constructor<?> constr = clazz.getConstructor(argTypes);  
           classLoader = (WebappClassLoader) constr.newInstance(args);  
       }  
       return classLoader;  
   }  

设置仓库
调用setRepositories,设置WEB-ING/classes目录与WEB-INF/lib目录

设置类路径
和jasper JSP编译器有关

设置访问权限
setPermissions 可以设置类载入器访问相关路径的权限。例如只能访问WEB-INf/classes目录与WEB-INF/lib目录

开启新线程执行类的重新载入
WebappLoader类支持自动重载功能。如果WEB-INF/classes目录或WEB/INF/lib目录下的某些类被重新编译了,那么这个类会自动重新载入,而无须重启tomcat。为了实现此功能,WebappLoader类使用一个线程周期性的检查每个资源的时间戳。间隔时间有变量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已经被设置为false 
   //直到程序关闭时,threadDone才会变为true 
       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 threadSleep() {          //让程序休眠一段时间 时间由checkInterval指定 
       try {  
           Thread.sleep(checkInterval * 1000L);  
       } catch (InterruptedException e) {  
           ;  
       }  
   } 

一旦有更改classLoader.modified()会返回true,直接调用notifyContext();

private void notifyContext() {  
        WebappContextNotifier notifier = new WebappContextNotifier();  
        (new Thread(notifier)).start();  
    }  
protected class WebappContextNotifier implements Runnable {  

        /** * Perform the requested notification. */  
        public void run() {  
            ((Context) container).reload();  
        }  
    } 

WebappContextNotifier是webapploader的内部类。
这里重新启了一个线程,避免了拥塞。

六、WebappClassLoader类

类缓存
为了达到更好的性能,会缓存已经载入的类,这样一来下次在使用这个类的时候,就不用再起加载了。

缓存分两级,一级在本地执行,由webappclassloader实例来管理。
此外,java.lang.ClassLoader类也会维护一个Vector对象,保存已经载入的类,此时缓存由父类管理。

每个由WebappClassLoader载入的类,都视为资源。是org.apache.catalina.loader.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 中,键值为载入的资源名称,所有找不到的资源都被放在一个名为 notFoundResources 的 HashMap 中。

载入类
所有加载过的类都要进行缓存,所以首先需要检查本地缓存。
· 如果无法再本地缓存找到类,使用 java.langClassLoader 类的 findLoaderClass 方法在缓存查找类、
· 如果在两个缓存中都无法找到该类,使用系统的类加载器避免从 J2EE 类中覆盖来的 web 应用程序。
· 如果使用了安全管理器,检查该类是否允许加载,如果该类不允许加载,则抛出 ClassNotFoundException 异常。
· 如果要加载的类使用了委派标志或者该类属于 trigger 包中,使用父加载器来加载类,如果父加载器为 null,使用系统加载器加载。
· 从当前的源中加载类
· 如果在当前的源中找不到该类并且没有使用委派标志,使用父类加载器。如果父类加载器为 null,使用系统加载器
· 如果该类仍然找不到,抛出 ClassNotFoundException 异常

public Class<?> loadClass(String name, boolean resolve)  
    throws ClassNotFoundException {  
    if (debug >= 2)  
        log("loadClass(" + name + ", " + resolve + ")");  
    Class<?> clazz = null;  

    // Don't load classes if class loader is stopped 
    if (!started) {                           //若webappclassloader没有启动.... 
        log("Lifecycle error : CL stopped");  
        throw new ClassNotFoundException(name);  
    }  

    // (0) Check our previously loaded local class cache 
    clazz = findLoadedClass0(name);           //看下面临时放进来的代码 等于是先在本地存储的已加载的类(hashmap)中找 
    if (clazz != null) {  
        if (debug >= 3)  
            log(" Returning class from cache");  
        if (resolve)  
            resolveClass(clazz);  
        return (clazz);  
    }  
/* protected Class<?> findLoadedClass0(String name) { ResourceEntry entry = (ResourceEntry) resourceEntries.get(name); if (entry != null) { return entry.loadedClass; } return (null); } */  

    // (0.1) Check our previously loaded class cache 
    clazz = findLoadedClass(name);                  //再去虚拟机中去找 
    if (clazz != null) {  
        if (debug >= 3)  
            log(" Returning class from cache");  
        if (resolve)  
            resolveClass(clazz);  
        return (clazz);  
    }  

    // (0.2) Try loading the class with the system class loader, to prevent 
    // the webapp from overriding J2SE classes 
    try {  
        //system 为系统类加载器 sun.misc.Launcher$AppClassLoader 
    //先用系统加载器加载 
    //为什么先用系统加载器 如果你写了一个类 全名为"java.lang.Object" 懂了吧? 
    //大家可能会想 系统加载器不是什么都能加载吗? 那还要后面的代码做什么? 
    //系统加载器的加载目录是什么 大家看看就明白了 
        clazz = system.loadClass(name);  
        if (clazz != null) {  
            if (resolve)  
                resolveClass(clazz);  
            return (clazz);  
        }  
    } catch (ClassNotFoundException e) {  
        // Ignore 
    }  

//关于安全的问题 我没有研究过里面具体的代码 
    // (0.5) Permission to access this class when using a SecurityManager 
    if (securityManager != null) {  
        int i = name.lastIndexOf('.');  
        if (i >= 0) {  
            try {  
                securityManager.checkPackageAccess(name.substring(0,i));  
            } catch (SecurityException se) {  
                String error = "Security Violation, attempt to use " +  
                    "Restricted Class: " + name;  
                System.out.println(error);  
                se.printStackTrace();  
                log(error);  
                throw new ClassNotFoundException(error);  
            }  
        }  
    }  

//delegate默认为false 
//filter 如果要加载的类是packageTrigers里面所限定的 就返回true 
//书上说packageTrigers 里面的包是不允许载入的 
//可代码里没有体现不能加载呀? 谁知道为什么,麻烦告知我一声 
//另外 就像前面几章http://localhost:8080/Primitive请求的PrimitiveServlet 
// delegate为false, filter(name)也是false 
  boolean delegateLoad = delegate || filter(name);  

    System.out.println(delegate+" "+filter(name)+" ddd");  
    // (1) Delegate to our parent if requested 
    if (delegateLoad) {  
        if (debug >= 3)  
            log(" Delegating to parent classloader");  
        ClassLoader loader = parent;  
        System.out.println(loader+" loader");  
        if (loader == null)  
            loader = system;  
        try {  
            clazz = loader.loadClass(name);  
            if (clazz != null) {  
                if (debug >= 3)  
                    log(" Loading class from parent");  
                if (resolve)  
                    resolveClass(clazz);  
                return (clazz);  
            }  
        } catch (ClassNotFoundException e) {  
            ;  
        }  
    }  

    // (2) Search local repositories 
//至于repositories属性 是在webappLoader类start方法里 setRepositories()设定的 
//使用的各个方法如下 
//findClass findClassInternal findResourceInternal 
//在findResourceInternal中先加载WEB-INF/classes中的文件,然后加载WEB-INF/lib下的jar文件 
//最后 resourceEntries.put(name, entry); 
//会把加载的enety放入resourceEntries中缓存起来 
    if (debug >= 3)  
        log(" Searching local repositories");  
    try {  
        clazz = findClass(name);  
        if (clazz != null) {  
            if (debug >= 3)  
                log(" Loading class from local repository");  
            if (resolve)  
                resolveClass(clazz);  
            return (clazz);  
        }  
    } catch (ClassNotFoundException e) {  
        ;  
    }  

//系统加载器前面已经加载过来 如果程序能运行到这里就说明系统加载器不行 为什么还要再加载一遍 
    // (3) Delegate to parent unconditionally 
    if (!delegateLoad) {  
        if (debug >= 3)  
            log(" Delegating to parent classloader");  
        ClassLoader loader = parent;  
        if (loader == null)  
            loader = system;  
        try {  
            clazz = loader.loadClass(name);  
            if (clazz != null) {  
                if (debug >= 3)  
                    log(" Loading class from parent");  
                if (resolve)  
                    resolveClass(clazz);  
                return (clazz);  
            }  
        } catch (ClassNotFoundException e) {  
            ;  
        }  
    }  

    // This class was not found 
    throw new ClassNotFoundException(name);  

}