Tomcat类加载器机制(Tomcat源码解析六)

来源:互联网 发布:软件研发成本核算方式 编辑:程序博客网 时间:2024/05/26 07:27

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

[java] view plaincopy
  1. org.apache.catalina.startup.Bootstrap#createClassLoader  
[java] view plaincopy
  1. rivate ClassLoader createClassLoader(String name, ClassLoader parent)  
  2.     throws Exception {  
  3.   
  4.     String value = CatalinaProperties.getProperty(name + ".loader");  
  5.     // 1   
  6.     if ((value == null) || (value.equals("")))  
  7.         return parent;  
  8.   
  9.     // 2  
  10.     value = replace(value);  
  11.   
  12.     List<Repository> repositories = new ArrayList<Repository>();  
  13.   
  14.     StringTokenizer tokenizer = new StringTokenizer(value, ",");  
  15.     while (tokenizer.hasMoreElements()) {  
  16.         String repository = tokenizer.nextToken().trim();  
  17.         if (repository.length() == 0) {  
  18.             continue;  
  19.         }  
  20.   
  21.         // Check for a JAR URL repository  
  22.         try {  
  23.             @SuppressWarnings("unused")  
  24.             URL url = new URL(repository);  
  25.             repositories.add(  
  26.                     new Repository(repository, RepositoryType.URL));  
  27.             continue;  
  28.         } catch (MalformedURLException e) {  
  29.             // Ignore  
  30.         }  
  31.   
  32.         // Local repository  
  33.         if (repository.endsWith("*.jar")) {  
  34.             repository = repository.substring  
  35.                 (0, repository.length() - "*.jar".length());  
  36.             repositories.add(  
  37.                     new Repository(repository, RepositoryType.GLOB));  
  38.         } else if (repository.endsWith(".jar")) {  
  39.             repositories.add(  
  40.                     new Repository(repository, RepositoryType.JAR));  
  41.         } else {  
  42.             repositories.add(  
  43.                     new Repository(repository, RepositoryType.DIR));  
  44.         }  
  45.     }  
  46.     // 3  
  47.     ClassLoader classLoader = ClassLoaderFactory.createClassLoader  
  48.         (repositories, parent);  
  49.   
  50.   
  51.     return classLoader;  
  52.   
  53. }  

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

  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,具体代码片段如下:

[java] view plaincopy
  1. org.apache.catalina.startup.Bootstrap#init  
[java] view plaincopy
  1. Class<?> startupClass =  
  2.     catalinaLoader.loadClass  
  3.     ("org.apache.catalina.startup.Catalina");  
  4. Object startupInstance = startupClass.newInstance();  
  5.   
  6. String methodName = "setParentClassLoader";  
  7. Class<?> paramTypes[] = new Class[1];  
  8. paramTypes[0] = Class.forName("java.lang.ClassLoader");  
  9. Object paramValues[] = new Object[1];  
  10. paramValues[0] = sharedLoader;  
  11. Method method =  
  12.     startupInstance.getClass().getMethod(methodName, paramTypes);  
  13. method.invoke(startupInstance, paramValues);  
通过上面的代码,我们可以清楚的看到调用了Catalina的setParentClassLoader放,那么到这里我们可能又要想知道,设置了parentClassLoader以后,sharedLoader又是在哪里使用的呢?这就需要我们接着来分析容器启动的代码。我们通过查看org.apache.catalina.startup.Catalina#getParentClassLoader调用栈,我们看到在StandardContext的startInternal方法中调用了它,那么我们查看一下它的代码,包含了如下代码片段:

[java] view plaincopy
  1. org.apache.catalina.core.StandardContext#startInternal  
[java] view plaincopy
  1. if (getLoader() == null) {  
  2.             WebappLoader webappLoader = new WebappLoader(getParentClassLoader());  
  3.             webappLoader.setDelegate(getDelegate());  
  4.             setLoader(webappLoader);  
  5. }  
  6. try {  
  7.   
  8.     if (ok) {  
  9.   
  10.         // Start our subordinate components, if any  
  11.         if ((loader != null) && (loader instanceof Lifecycle))  
  12.             ((Lifecycle) loader).start();  
  13.         //other code      
  14.     }  
  15. catch(Exception e){  
  16. }  
通过查看上面的代码,我们看到在StandardContext启动的时候,会创建webapploader,创建webapploader的时候会将getParentClassLoader方法返回的结果(这里返回的其实就是sharedLoader)赋值给自己的parentClassLoader变量,接着又会调用到Webapploader的start方法,因为WebappLoader符合Tomcat组件生命周期管理的模板方法模式,因此会调用到它的startInternal方法。我们接下来就来看看WebappLoader的startInternal,我们摘取一部分与本篇相关的代码片段如下:

[java] view plaincopy
  1. org.apache.catalina.loader.WebappLoader#startInternal  
[java] view plaincopy
  1. classLoader = createClassLoader();  
  2. classLoader.setResources(container.getResources());  
  3. classLoader.setDelegate(this.delegate);  
  4. classLoader.setSearchExternalFirst(searchExternalFirst);  
从上的代码可以看到调用了createClassLoader方法创建一个classLoader,那么我们再看来看看createClassLoader的代码:
[java] view plaincopy
  1. org.apache.catalina.loader.WebappLoader#createClassLoader  
[java] view plaincopy
  1. private WebappClassLoader createClassLoader()  
  2.     throws Exception {  
  3.   
  4.     Class<?> clazz = Class.forName(loaderClass);  
  5.     WebappClassLoader classLoader = null;  
  6.   
  7.     if (parentClassLoader == null) {  
  8.         parentClassLoader = container.getParentClassLoader();  
  9.     }  
  10.     Class<?>[] argTypes = { ClassLoader.class };  
  11.     Object[] args = { parentClassLoader };  
  12.     Constructor<?> constr = clazz.getConstructor(argTypes);  
  13.     classLoader = (WebappClassLoader) constr.newInstance(args);  
  14.   
  15.     return classLoader;  
  16.   
  17. }  

在上面的代码里面,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

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

[java] view plaincopy
  1. org.apache.catalina.loader.WebappClassLoader#findClassInternal  
[java] view plaincopy
  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对象返回。





版权声明:本文为博主原创文章,未经博主允许不得转载。

0 0