• How Tomcat Works(十一)


    本文接下来分析tomcat的类载入器,tomcat需要实现一个自定义的载入器,而不能使用系统类载入器

    (1)限制serlvet访问当前运行的java虚拟机中环境变量CLASSPATH指明的路径下的所有类和库,而只允许载入WEB-INF/class目录及其子目录下的类,和从部署的库到WEB-INF/lib目录载入类

    (2)提供自动重载的功能,即当WEB-INF/class目录或WEB-INF/lib目录下的类发生变化时,Web应用程序会重新载入这些类

    我们先来回顾一下java的类载入器,当我们创建java类的实例时,都必须先将类载入到内存中,java虚拟机使用类载入器来载入需要的类

    JVM使用三种类型的类载入器来载入所需要的类,分别为引导类载入器(bootstrap class loader)、扩展类载入器(extensions class loader)和系统类载入器(system class loader)

    • 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
    • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
    • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

    JVM采用一种代理模型的类载入机制,可以解决类载入过程中的安全性问题;每当系统需要载入一个类的时候,会首先调用系统类载入器,而系统类载入器将载入类的任务委派给其父载入器,即扩展类载入器,同样扩展类载入器将载入类的任务委派给其父载入器,即引导类载入器;因此引导类载入器会首先执行载入某个类的任务,如果引导类载入器找不到需要载入的类,那么扩展类载入器尝试载入该类,如果扩展类载入器也找不到该类,就轮到系统类载入器继续执行载入任务。如果系统类载入器还是找不到该类,则会抛出java.lang.ClassNotFoundException异常

    Tomcat中的载入器是指Web应用程序载入器,而不仅仅指类载入器,载入器必须实现org.apache.catalina.Loader接口

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

    下面我们来具体来分析tomcat容器中 Web应用程序载入器的具体实现,即org.apache.catalina.loader.WebappLoader类实现了上面的Loader接口,负责载入Web应用程序中所使用到的类。

    WebappLoader类会创建org.apache.catalina.loader.WebappClassLoader类的实例作为其类载入器;

    同时WebappLoader类也实现了org.apache.catalina.Lifecycle接口,可以由其相关联的容器来启动或关闭;

    此外,WebappLoader类还实现了java.lang.Runnable接口,通过一个线程不断地调用其类载入器的modified()方法。如果modified()方法返回true, WebappLoader的实例会通知其关联的servlet容器(在这里是Context类的实例),然后由Context实例来完成类的重新载入。

    当调用WebappLoader类的start()方法时,会完成以下几项重要工作:

    (1)创建一个类载入器

    (2)设置仓库

    (3)设置类路径

    (4)设置访问权限

    (5)启动一个新线程来支持自动重载

    WebappLoader类会调用其私有方法createClassLoader()方法来创建默认的类载入器

     /**
         * Create associated classLoader.
         */
        private WebappClassLoader createClassLoader()
            throws Exception {
    
            Class clazz = Class.forName(loaderClass);
            WebappClassLoader classLoader = null;
    
            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;
    
        }

    String loaderClass 的默认值为org.apache.catalina.loader.WebappClassLoader

    启动方法后面的设置仓库、设置类路径、设置访问权限等都与类载入器的初始化相关

    WebappLoader类支持自动重载功能,如果WEB-INF/class目录或WEB-INF/lib目录下的某些类被重新编译了,那么这些类会自动重新载入,而无需重启tomcat。WebappLoader类使用一个线程周期性地检查每个资源的时间戳,我们可以调用setCheckInterval()方法设置间隔时间

    /**
         * The background thread that checks for session timeouts and shutdown.
         */
        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");
    
        }

    在上面run()方法里面的循环中,首先使线程休眠一段时间,然后调用 WebappLoader实例的类载入器的 modified()方法检查已经载入的类是否被修改,肉没有修改则重新执行循环;否则调用私有方法notifyContext(),通知与WebappLoader实例相关联的Context容器重新载入相关类

    /**
         * Notify our Context that a reload is appropriate.
         */
        private void notifyContext() {
    
            WebappContextNotifier notifier = new WebappContextNotifier();
            (new Thread(notifier)).start();
    
        }

    上面方法中的WebappContextNotifier为内部类,实现了Runnable接口

    /**
         * Private thread class to notify our associated Context that we have
         * recognized the need for a reload.
         */
        protected class WebappContextNotifier implements Runnable {
    
            /**
             * Perform the requested notification.
             */
            public void run() {
                ((Context) container).reload();
            }
        }

    下面我们来分析web应用程序中负责载入类的类载入器org.apache.catalina.loader.WebappClassLoader,该类继承自java.net.URLClassLoader,同时实现了org.apache.catalina.loader.Reloader接口

    public interface Reloader {
       
        public void addRepository(String repository);
    
        public String[] findRepositories();
       
        public boolean modified();
    
    }

    该接口用来与仓库操作相关,同时检查web应用程序中的某个servlet或相关的类是否被修改

    每个由WebappClassLoader载入的类,都视为资源,由org.apache.catalina.loader.ResourceEntry类的实例表示,存储了类的相关信息

    public class ResourceEntry {
      
        public long lastModified = -1;
       
        public byte[] binaryContent = null;
       
        public Class loadedClass = null;
    
        public URL source = null;
    
        public URL codeBase = null;
    
        public Manifest manifest = null;
    
        public Certificate[] certificates = null;
    
    }

    在WebappClassLoader类中,所有已经缓存的类存储在名为resourceEntries的HashMap类型的变量中,而载入失败的类被存储在另一个名为notFoundResources的HashMap类型的变量中

    下面是WebappClassLoader的loadClass()方法的具体实现

    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) {
                log("Lifecycle error : CL stopped");
                throw new ClassNotFoundException(name);
            }
    
            // (0) Check our previously loaded local class cache
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (debug >= 3)
                    log("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
    
            // (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 {
                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);
                    }
                }
            }
    
            boolean delegateLoad = delegate || filter(name);
    
            // (1) Delegate to our parent if requested
            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) {
                    ;
                }
            }
    
            // (2) Search local repositories
            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);
    
        }

    在WebappClassLoader实例载入类时,首先是检查缓存,然后再载入指定类(如果设置了安全管理。在代理载入器载入前还要检测类型的安全)

    --------------------------------------------------------------------------- 

    本系列How Tomcat Works系本人原创 

    转载请注明出处 博客园 刺猬的温驯 

    本人邮箱: chenying998179#163.com (#改为@

    本文链接http://www.cnblogs.com/chenying99/p/3237389.html

  • 相关阅读:
    LeetCode 10 Regular Expression Matching(字符串匹配)
    LeetCode 9 Palindrome Number(回文数字判断)
    操作系统期末复习资料分享
    计算机网络那些事~(二)
    seL4之hello-3征途
    计算机网络那些事~(一)
    seL4之hello-2旅途(完成更新)
    博客声明
    seL4环境配置
    Canvas链式操作
  • 原文地址:https://www.cnblogs.com/chenying99/p/3237389.html
Copyright © 2020-2023  润新知