zoukankan      html  css  js  c++  java
  • Tomcat类载入器机制(Tomcat源代码解析六)

    要说Tomcat的Classloader机制,我们还得从Bootstrap開始。在BootStrap初始化的时候。调用了org.apache.catalina.startup.Bootstrap#initClassLoaders方法,这种方法里面创建了3个ClassLoader,它们各自是commonLoader,catalinaLoader,sharedLoader,当中catalinaLoader,sharedLoader的父亲载入器是commonLoader,initClassLoaders运行的过程中会运行createClassLoader,而createClassLoader是依据conf/catalina.properties文件里common.loader,server.loader。shared.loader的值来初始化,它的代码例如以下:

    org.apache.catalina.startup.Bootstrap#createClassLoader
    
    rivate ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
    
        String value = CatalinaProperties.getProperty(name + ".loader");
        // 1 
        if ((value == null) || (value.equals("")))
            return parent;
    
        // 2
        value = replace(value);
    
        List<Repository> repositories = new ArrayList<Repository>();
    
        StringTokenizer tokenizer = new StringTokenizer(value, ",");
        while (tokenizer.hasMoreElements()) {
            String repository = tokenizer.nextToken().trim();
            if (repository.length() == 0) {
                continue;
            }
    
            // Check for a JAR URL repository
            try {
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                repositories.add(
                        new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }
    
            // Local repository
            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());
                repositories.add(
                        new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                repositories.add(
                        new Repository(repository, RepositoryType.JAR));
            } else {
                repositories.add(
                        new Repository(repository, RepositoryType.DIR));
            }
        }
        // 3
        ClassLoader classLoader = ClassLoaderFactory.createClassLoader
            (repositories, parent);
    
    
        return classLoader;
    
    }

    以上代码删除了与本篇无关的代码,以下我们分别来分析一下标注的地方:

    1. 标注1的代码(第5行)推断假设catalina.properties中没有配置相应的loader属性的话。直接返回父载入器。而默认情况下,server.loader,shared.loader为空。那么此时的catalinaLoader,sharedLoader事实上是同一个ClassLoader.
    2. 标注2(第9行)的地方依据环境变量的配置替换字符串中的值.默认情况下。common.loader的值为common.loader=${catalina.base}/lib,${catalina.base}/lib/.jar,${catalina.home}/lib,${catalina.home}/lib/.jar,这里会将catalina.base和catalina.home用环境变量的值替换。

    3. 标注3(第46行)的代码终于调用org.apache.catalina.startup.ClassLoaderFactory#createClassLoader静态工厂方法创建了URLClassloader的实例,而详细的URL事实上就是*.loader属性配置的内容,此外假设parent为null的话,则直接用系统类载入器。

    上面分析了Tomcat在启动的时候,初始化的几个ClassLoader,接下来我们再来继续看看,这些ClassLoader详细都用在什么地方。

    我们接着来看org.apache.catalina.startup.Bootstrap#init方法,在初始化完3个classLoader以后,接下来首先通过catalinaLoader载入了org.apache.catalina.startup.Catalinal类,然后通过放射调用了org.apache.catalina.startup.Catalina#setParentClassLoader,详细代码片段例如以下:

    org.apache.catalina.startup.Bootstrap#init
    
    Class<?> startupClass =
        catalinaLoader.loadClass
        ("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.newInstance();
    
    String methodName = "setParentClassLoader";
    Class<?

    > paramTypes[] = new Class[1]; paramTypes[0] = Class.forName("java.lang.ClassLoader"); Object paramValues[] = new Object[1]; paramValues[0] = sharedLoader; Method method = startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues);

    通过上面的代码。我们能够清楚的看到调用了Catalina的setParentClassLoader放。那么到这里我们可能又要想知道,设置了parentClassLoader以后,sharedLoader又是在哪里使用的呢?这就须要我们接着来分析容器启动的代码。我们通过查看org.apache.catalina.startup.Catalina#getParentClassLoader调用栈,我们看到在StandardContext的startInternal方法中调用了它,那么我们查看一下它的代码,包括了例如以下代码片段:

    org.apache.catalina.core.StandardContext#startInternal
    
    if (getLoader() == null) {
                WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
                webappLoader.setDelegate(getDelegate());
                setLoader(webappLoader);
    }
    try {
    
        if (ok) {
    
            // Start our subordinate components, if any
            if ((loader != null) && (loader instanceof Lifecycle))
                ((Lifecycle) loader).start();
            //other code    
        }
    catch(Exception e){
    }
    通过查看上面的代码。我们看到在StandardContext启动的时候,会创建webapploader。创建webapploader的时候会将getParentClassLoader方法返回的结果(这里返回的事实上就是sharedLoader)赋值给自己的parentClassLoader变量,接着又会调用到Webapploader的start方法,由于WebappLoader符合Tomcat组件生命周期管理的模板方法模式。因此会调用到它的startInternal方法。

    我们接下来就来看看WebappLoader的startInternal。我们摘取一部分与本篇相关的代码片段例如以下:

    org.apache.catalina.loader.WebappLoader#startInternal
    
    classLoader = createClassLoader();
    classLoader.setResources(container.getResources());
    classLoader.setDelegate(this.delegate);
    classLoader.setSearchExternalFirst(searchExternalFirst);
    从上的代码能够看到调用了createClassLoader方法创建一个classLoader,那么我们再看来看看createClassLoader的代码:
    org.apache.catalina.loader.WebappLoader#createClassLoader
    
    private WebappClassLoader createClassLoader()
        throws Exception {
    
        Class<?> clazz = Class.forName(loaderClass);
        WebappClassLoader classLoader = null;
    
        if (parentClassLoader == null) {
            parentClassLoader = container.getParentClassLoader();
        }
        Class<?>[] argTypes = { ClassLoader.class };
        Object[] args = { parentClassLoader };
        Constructor<?

    > constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoader) constr.newInstance(args); return classLoader; }

    在上面的代码里面。loaderClass是WebappLoader的实例变量,其值为org.apache.catalina.loader.WebappClassLoader,那么上面的代码事实上就是通过反射调用了WebappClassLoader的构造函数,然后传递了sharedLoader作为其父亲载入器。

    代码阅读到这里。我们已经基本清楚了Tomcat中ClassLoader的整体结构,总结例如以下: 在Tomcat存在common,cataina,shared三个公共的classloader,默认情况下,这三个classloader事实上是同一个,都是common classloader,而针对每一个webapp,也就是context(相应代码中的StandardContext类),都有自己的WebappClassLoader来载入每一个应用自己的类。

    上面的描写叙述,我们能够通过下图形象化的描写叙述:





    清楚了Tomcat整体的ClassLoader结构以后,咋们就来进一步来分析一下WebAppClassLoader的代码,我们知道Java的ClassLoader机制有parent-first的机制,而这样的机制是在loadClass方法保证的。普通情况下。我们仅仅须要重写findClass方法就好了,而对于WebAppClassLoader。通过查看源码。我们发现loadClass和findClass方法都进行了重写。那么我们首先就来看看它的loadClass方法,它的代码例如以下:

    org.apache.catalina.loader.WebappClassLoader#loadClass

    public synchronized Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
    
        if (log.isDebugEnabled())
            log.debug("loadClass(" + name + ", " + resolve + ")");
        Class<?> clazz = null;
    
        // Log access to stopped classloader
        if (!started) {
            try {
                throw new IllegalStateException();
            } catch (IllegalStateException e) {
                log.info(sm.getString("webappClassLoader.stopped", name), e);
            }
        }
    
        // (0) Check our previously loaded local class cache
        // 1 
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return (clazz);
        }
    
        // (0.1) Check our previously loaded class cache
        // 2
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  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
        // 3 
        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;
                    log.info(error, se);
                    throw new ClassNotFoundException(error, se);
                }
            }
        }
    
        //4 
        boolean delegateLoad = delegate || filter(name);
    
        // (1) Delegate to our parent if requested
        // 5 
        if (delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader1 " + parent);
            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            try {
                clazz = Class.forName(name, false, loader);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
    
        // (2) Search local repositories
        if (log.isDebugEnabled())
            log.debug("  Searching local repositories");
        // 6 
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Loading class from local repository");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
    
        // (3) Delegate to parent unconditionally
        // 7
        if (!delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader at end: " + parent);
            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            try {
                clazz = Class.forName(name, false, loader);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
    
        throw new ClassNotFoundException(name);
    
    }

    我们一步步的来分析一下上面的代码做了什么事情。

    1. 标注1(第18行)代码,首先从当前ClassLoader的本地缓存中载入类,假设找到则返回。
    2. 标注2(第29行)代码。在本地缓存没有的情况下,调用ClassLoader的findLoadedClass方法查看jvm是否已经载入过此类。假设已经载入则直接返回。

    3. 标注3(第41行)代码,通过系统的来载入器载入此类,这里防止应用写的类覆盖了J2SE的类,这句代码很关键。假设不写的话。就会造成你自己写的类有可能会把J2SE的类给替换调,另外假如你写了一个javax.servlet.Servlet类,放在当前应用的WEB-INF/class中,假设没有此句代码的保证。那么你自己写的类就会替换到Tomcat容器Lib中包括的类。
    4. 标注4(第68行)代码。推断是否须要托付给父类载入器进行载入,delegate属性默觉得false,那么delegatedLoad的值就取决于filter的返回值了,filter方法中依据包名来推断是否须要进行托付载入。默认情况下会返回false.因此delegatedLoad为false
    5. 标注5(第72行)代码,由于delegatedLoad为false,那么此时不会托付父载入器去载入,这里事实上是没有遵循parent-first的载入机制。
    6. 标注6(第96行)调用findClass方法在webapp级别进行载入
    7. 标注7(第111行)假设还是没有载入到类。而且不採用托付机制的话,则通过父类载入器去载入。

    通过上面的描写叙述,我们能够知道Tomcat在载入webapp级别的类的时候,默认是不遵守parent-first的,这样做的优点是更好的实现了应用的隔离,可是坏处就是加大了内存浪费。相同的类库要在不同的app中都要载入一份。

    上面分析完了loadClass,我们接着在来分析一下findClass,通过分析findClass的代码。终于会调用org.apache.catalina.loader.WebappClassLoader#findClassInternal方法。那我们就来分析一下它的代码:

    org.apache.catalina.loader.WebappClassLoader#findClassInternal
    
    protected Class<?> findClassInternal(String name)
        throws ClassNotFoundException {
    
        //
        if (!validate(name))
            throw new ClassNotFoundException(name);
    
        String tempPath = name.replace('.', '/');
        String classPath = tempPath + ".class";
    
        ResourceEntry entry = null;
    
        if (securityManager != null) {
            PrivilegedAction<ResourceEntry> dp =
                new PrivilegedFindResourceByName(name, classPath);
            entry = AccessController.doPrivileged(dp);
        } else {
            // 1 
            entry = findResourceInternal(name, classPath);
        }
    
        if (entry == null)
            throw new ClassNotFoundException(name);
    
        Class<?

    > clazz = entry.loadedClass; if (clazz != null) return clazz; synchronized (this) { clazz = entry.loadedClass; if (clazz != null) return clazz; if (entry.binaryContent == null) throw new ClassNotFoundException(name); try { // 2 clazz = defineClass(name, entry.binaryContent, 0, entry.binaryContent.length, new CodeSource(entry.codeBase, entry.certificates)); } catch (UnsupportedClassVersionError ucve) { throw new UnsupportedClassVersionError( ucve.getLocalizedMessage() + " " + sm.getString("webappClassLoader.wrongVersion", name)); } entry.loadedClass = clazz; entry.binaryContent = null; entry.source = null; entry.codeBase = null; entry.manifest = null; entry.certificates = null; } return clazz; }

    上面的代码标注1(第19行)的地方通过名称去当前webappClassLoader的仓库中查找相应的类文件,标注2(第38行)的代码,将找到的类文件通过defineClass转变为Jvm能够识别的Class对象返回。





  • 相关阅读:
    通用测试用例(二)
    loadrunner基础学习笔记八-分析场景
    loadrunner基础学习笔记七-面向目标场景
    Detect the Virus ZOJ
    考研路茫茫——单词情结 HDU
    DNA Sequence POJ
    病毒侵袭持续中 HDU
    病毒侵袭 HDU
    Keywords Search HDU
    codeforces 949B :A Leapfrog in the Array 找规律
  • 原文地址:https://www.cnblogs.com/mfmdaoyou/p/7120624.html
Copyright © 2011-2022 走看看