zoukankan      html  css  js  c++  java
  • Tomcat类加载器机制

    Tomcat为什么需要定制自己的ClassLoader:

    1、定制特定的规则:隔离webapp,安全考虑,reload热插拔

    2、缓存类

    3、事先加载

    要说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的值来初始化,它的代码如下:

    默认情况下,这3个ClassLoader是同一个实例变量

    private 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);//这里返回的是一个UrlClassLoader实例
    
    
        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,具体代码片段如下:

    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方法中调用了它,那么我们查看一下它的代码,包含了如下代码片段:

    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,我们摘取一部分与本篇相关的代码片段如下:

    classLoader = createClassLoader();
    classLoader.setResources(container.getResources());
    classLoader.setDelegate(this.delegate);
    classLoader.setSearchExternalFirst(searchExternalFirst);

    从上的代码可以看到调用了createClassLoader方法创建一个classLoader,那么我们再看来看看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方法,它的代码如下:

      1 public synchronized Class<?> loadClass(String name, boolean resolve)
      2     throws ClassNotFoundException {
      3 
      4     if (log.isDebugEnabled())
      5         log.debug("loadClass(" + name + ", " + resolve + ")");
      6     Class<?> clazz = null;
      7 
      8     // Log access to stopped classloader
      9     if (!started) {
     10         try {
     11             throw new IllegalStateException();
     12         } catch (IllegalStateException e) {
     13             log.info(sm.getString("webappClassLoader.stopped", name), e);
     14         }
     15     }
     16 
     17     // (0) Check our previously loaded local class cache
     18     // 1 
     19     clazz = findLoadedClass0(name);
     20     if (clazz != null) {
     21         if (log.isDebugEnabled())
     22             log.debug("  Returning class from cache");
     23         if (resolve)
     24             resolveClass(clazz);
     25         return (clazz);
     26     }
     27 
     28     // (0.1) Check our previously loaded class cache
     29     // 2
     30     clazz = findLoadedClass(name);
     31     if (clazz != null) {
     32         if (log.isDebugEnabled())
     33             log.debug("  Returning class from cache");
     34         if (resolve)
     35             resolveClass(clazz);
     36         return (clazz);
     37     }
     38 
     39     // (0.2) Try loading the class with the system class loader, to prevent
     40     //       the webapp from overriding J2SE classes
     41     // 3 
     42     try {
     43         clazz = system.loadClass(name);
     44         if (clazz != null) {
     45             if (resolve)
     46                 resolveClass(clazz);
     47             return (clazz);
     48         }
     49     } catch (ClassNotFoundException e) {
     50         // Ignore
     51     }
     52 
     53     // (0.5) Permission to access this class when using a SecurityManager
     54     if (securityManager != null) {
     55         int i = name.lastIndexOf('.');
     56         if (i >= 0) {
     57             try {
     58                 securityManager.checkPackageAccess(name.substring(0,i));
     59             } catch (SecurityException se) {
     60                 String error = "Security Violation, attempt to use " +
     61                     "Restricted Class: " + name;
     62                 log.info(error, se);
     63                 throw new ClassNotFoundException(error, se);
     64             }
     65         }
     66     }
     67 
     68     //4 
     69     boolean delegateLoad = delegate || filter(name);
     70 
     71     // (1) Delegate to our parent if requested
     72     // 5 
     73     if (delegateLoad) {
     74         if (log.isDebugEnabled())
     75             log.debug("  Delegating to parent classloader1 " + parent);
     76         ClassLoader loader = parent;
     77         if (loader == null)
     78             loader = system;
     79         try {
     80             clazz = Class.forName(name, false, loader);
     81             if (clazz != null) {
     82                 if (log.isDebugEnabled())
     83                     log.debug("  Loading class from parent");
     84                 if (resolve)
     85                     resolveClass(clazz);
     86                 return (clazz);
     87             }
     88         } catch (ClassNotFoundException e) {
     89             // Ignore
     90         }
     91     }
     92 
     93     // (2) Search local repositories
     94     if (log.isDebugEnabled())
     95         log.debug("  Searching local repositories");
     96     // 6 
     97     try {
     98         clazz = findClass(name);
     99         if (clazz != null) {
    100             if (log.isDebugEnabled())
    101                 log.debug("  Loading class from local repository");
    102             if (resolve)
    103                 resolveClass(clazz);
    104             return (clazz);
    105         }
    106     } catch (ClassNotFoundException e) {
    107         // Ignore
    108     }
    109 
    110     // (3) Delegate to parent unconditionally
    111     // 7
    112     if (!delegateLoad) {
    113         if (log.isDebugEnabled())
    114             log.debug("  Delegating to parent classloader at end: " + parent);
    115         ClassLoader loader = parent;
    116         if (loader == null)
    117             loader = system;
    118         try {
    119             clazz = Class.forName(name, false, loader);
    120             if (clazz != null) {
    121                 if (log.isDebugEnabled())
    122                     log.debug("  Loading class from parent");
    123                 if (resolve)
    124                     resolveClass(clazz);
    125                 return (clazz);
    126             }
    127         } catch (ClassNotFoundException e) {
    128             // Ignore
    129         }
    130     }
    131 
    132     throw new ClassNotFoundException(name);
    133 
    134 }

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

    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方法,那我们就来分析一下它的代码:

     1 protected Class<?> findClassInternal(String name)
     2     throws ClassNotFoundException {
     3 
     4     //
     5     if (!validate(name))
     6         throw new ClassNotFoundException(name);
     7 
     8     String tempPath = name.replace('.', '/');
     9     String classPath = tempPath + ".class";
    10 
    11     ResourceEntry entry = null;
    12 
    13     if (securityManager != null) {
    14         PrivilegedAction<ResourceEntry> dp =
    15             new PrivilegedFindResourceByName(name, classPath);
    16         entry = AccessController.doPrivileged(dp);
    17     } else {
    18         // 1 
    19         entry = findResourceInternal(name, classPath);
    20     }
    21 
    22     if (entry == null)
    23         throw new ClassNotFoundException(name);
    24 
    25     Class<?> clazz = entry.loadedClass;
    26     if (clazz != null)
    27         return clazz;
    28 
    29     synchronized (this) {
    30         clazz = entry.loadedClass;
    31         if (clazz != null)
    32             return clazz;
    33 
    34         if (entry.binaryContent == null)
    35             throw new ClassNotFoundException(name);
    36 
    37         try {
    38             // 2
    39             clazz = defineClass(name, entry.binaryContent, 0,
    40                     entry.binaryContent.length,
    41                     new CodeSource(entry.codeBase, entry.certificates));
    42         } catch (UnsupportedClassVersionError ucve) {
    43             throw new UnsupportedClassVersionError(
    44                     ucve.getLocalizedMessage() + " " +
    45                     sm.getString("webappClassLoader.wrongVersion",
    46                             name));
    47         }
    48         entry.loadedClass = clazz;
    49         entry.binaryContent = null;
    50         entry.source = null;
    51         entry.codeBase = null;
    52         entry.manifest = null;
    53         entry.certificates = null;
    54     }
    55 
    56     return clazz;
    57 
    58 }

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

    Reference

    Tomcat类加载器机制(Tomcat源代码阅读系列之六)

    Tomcat学习之ClassLoader

    tomcat的classloader机制

  • 相关阅读:
    [ Algorithm ] N次方算法 N Square 动态规划解决
    [ Algorithm ] LCS 算法 动态规划解决
    sql server全文索引使用中的小坑
    关于join时显示no join predicate的那点事
    使用scvmm 2012的动态优化管理群集资源
    附加数据库后无法创建发布,error 2812 解决
    浅谈Virtual Machine Manager(SCVMM 2012) cluster 过载状态检测算法
    windows 2012 r2下安装sharepoint 2013错误解决
    sql server 2012 数据引擎任务调度算法解析(下)
    sql server 2012 数据引擎任务调度算法解析(上)
  • 原文地址:https://www.cnblogs.com/549294286/p/3711712.html
Copyright © 2011-2022 走看看