(七)类加载器

为什么servlet需要实现一个自定义的载入器
  因为servlet容器不应该完全信任它正在运行的servlet类。当使用系统载入器载入某个servlet类所使用的全部类,那么servlet就能够访问所有的类,包括当前运行的JVM中环境变量CLASSPATH指明的路径下的所有类和库, 这是非常危险的。servlet应该只允许载入WEB-INF/classes目录及其子目录下的类, 和从部署的库到WEB-INF/lib目录中载入类。
  所以,Servlet容器需要实现一个自定义的载入器.

Java的类载入器

jvm使用三种类载入器来载入所需要的类:

  1. 引导类载入器(bootstrap class loader)
    用于引导启动java虚拟机。当调用javax.exe程序时,就会启动引导类载入器。引导类载入器是本地代码实现的(什么意思), 因为它用来载入运行jvm所需要的类, 以及所有的java核心类。
  2. 扩展类载入器(extension class loader)
    负责载入扩展目录中的类,有利于程序开发。Sun公司的jvm的标准扩展目录是/jdk/jre/lib/ext.
  3. 系统类载入器(system class loader)
    默认的类载入器,它会搜索在环境变量CLASSPATH中指明的路径和JVR文件。
    三种载入器之间是父子继承关系:
graph BT
系统类载入器-->扩展类载入器
扩展类载入器-->引导类载入器

  具体的一个类究竟是由哪个加载器加载的呢,答案在于类加载器的代理模型-双亲委派模型, 这种模型可以有效的解决类载入过程中的安全性问题。
  当一个类需要载入时,首先调用系统载入器,但它不会立即载入对应的类,而是交给其父载入器-扩展类载入器, 而扩展类载入器又会将载入任务讲给其父载入器-引导载入器.
  代理模型的重要用途就是解决类载入过程中的安全性问题。

Tocmat的载入器

  Tomcat中的载入器指的是web应用程序载入器,而不仅仅是指类载入器,载入器必须实现org.apache.catalina.Loader接口,在载入器的实现中,会使用一个自定义的类载入器(WebappClassLoader的实例)
  Tomcat的载入器挺长会与一个Context级别的servlet容器相关联,Loader接口的getContainer()和setContainer()用来将载入器与某个erlet容器相关联.
  Context创建WebappLoader的步骤:

  1. start()函数中setLoader(new WebappLoader(getParentClassLoader()));
  2. setLoader函数中会有loader.setContainer(this);设置loader对应的容器;
  3. 设置完容器之后,会调用loader的start()函数。
      在Context容器启动时,有如下代码:
public class StandardContext
    extends ContainerBase
    implements Context {
    //......
    
    public synchronized void start() throws LifecycleException {
        //......
    
        // 为Context创建配套的类加载器
        if (getLoader() == null) {      // (2) Required by Manager
            if (getPrivileged()) {
                if (debug >= 1)
                    log("Configuring privileged default Loader");
                setLoader(new WebappLoader(this.getClass().getClassLoader()));
            } else {
                if (debug >= 1)
                    log("Configuring non-privileged default Loader");
                setLoader(new WebappLoader(getParentClassLoader()));
            }
        }
        
        // 设置当前线程的类加载器为刚才生成的
        // Binding thread
        ClassLoader oldCCL = bindThread();
        //...启动context里面的内容
        //最终unbindThread(oldCCL); 还原 类加载器
        
        //.......
    }
}

  上面启动的流程保证了当前Context流程下的类的生成都是由start()里生成的载入器加载的,**start()**之后还原,保证了线程在加载依次加载其他Context容器时,加载Context使用的都是同样的类加载器.
  那我们如何在运行不同应用程序的时候,使用Context对应的类加载器呢?
  关键在于Host容器的基础阀中的流程:

final class StandardHostValve
    extends ValveBase {
    public void invoke(Request request, Response response,
                       ValveContext valveContext)
        throws IOException, ServletException {    
        //...
        
        //匹配当前请求要使用的Context容器,并将线程的类加载器设置为Context容器的类加载器
        // Bind the context CL to the current thread
        Thread.currentThread().setContextClassLoader
            (context.getLoader().getClassLoader());
            
        //...
    }
}

  host容器的基础阀中会将线程的类加载器设置为Context容器的载入器里的类加载器,所以后续的类加载相关操作,都是使用Context容器里的类加载器。

WebappLoader类

  Context容器里使用的就是这种载入器,也就是web应用程序的载入器。
  看看WebappLoader的start()函数

public class WebappLoader
    implements Lifecycle, Loader, PropertyChangeListener, Runnable {
    
    public WebappLoader(ClassLoader parent) {
        super();
        this.parentClassLoader = parent;
    }
    
    public void start() throws LifecycleException {
        // 主要操作
        // 1. 创建一个类加载器;
        // 2. 设置仓库;
        // 3. 设置类路径;
        // 4. 设置访问权限;
        // 5. 启动一个县城来支持自动重载
    }
}
WebappClassLoader类

  WebappClassLoader类的设计考虑了优化和安全两方面。
  1. 优化:它会缓存之前已经加载的类来提升性能,此外,它还会缓存加载失败的类的名字, 加载类的效率更高了。

//WebappClassLoader类里的两个缓存类的变量
/**
 * The cache of ResourceEntry for classes and resources we have loaded,
 * keyed by resource name.
 */
protected HashMap resourceEntries = new HashMap();

/**
 * The list of not found resources.
 */
protected HashMap notFoundResources = new HashMap();

  此外,java.lang.ClassLoader类会维护一个Vector对象,保存已经载入的类,防止这些类在不使用的时候被当做垃圾而回收。在这种情况下,缓存是由父类来管理的.
  2. 安全性:WebappClassLoader不允许载入指定的某些类,这些类的名字存储在一个字符串数组变量triggers中,当前仅有一个元素:

/**
 * The set of trigger classes that will cause a proposed repository not
 * to be added if this class is visible to the class loader that loaded
 * this factory class.  Typically, trigger classes will be listed for
 * components that have been integrated into the JDK for later versions,
 * but where the corresponding JAR files are required to run on
 * earlier versions.
 */
private static final String[] triggers = {
    "javax.servlet.Servlet"                     // Servlet API
};

  某些特殊的包及其子包下的类也是不允许载入的,也不会将载入类的任务委托给系统类载入器去执行:

/**
 * Set of package names which are not allowed to be loaded from a webapp
 * class loader without delegating first.
 */
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
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值