`

Tomcat学习之ClassLoader

 
阅读更多

类装载器

JDK中提供了3种不同的类加载器:启动类装载器,扩展类装载器和系统类装载器。引导类装载器,用于引导启动JAVA虚拟机,当执行一个JAVA程序时,就会启动引导类装载器,它是使用本地代码来实现的,会装载%JAVA_HOME%\\jre\lib\rt.jar,它是所有类装载器类的父装载器。扩展类装载器负责载入标准扩展目录中的类,其搜索路径是%JAVA_HOME%\jre\lib\ext,只需要将打包好的jar文件放入这个目录就可以了,给开发提供了很大的便利性。系统类装载器是默认的装载器,其搜索路径是classpath。

JVM到底使用的是哪一个类装载器,取决于类装载器的代理模式。每当需要装载一个类的时候,会首先调用系统类装载器,但是系统类装载器并不会立即装载,而是将其交给父装载器:扩展类装载器,扩展类装载器其将交给引导类装载器,引导类装载器没有父装载器,它会尝试装载这个类,如果找不到这个类,会交给扩展类装载器,如果扩展类装载器还是没有找到,会交给系统类装载器,如果系统类装载器还是没有找到这个类,则会抛出java.lang.ClassNotFoundException异常。代理模式主要是为了解决类装载的安全问题。例如:对于自定类的java.lang.Object类,永远得不到装载,除非,rt.jar中确实没有这个类。

tomcat也提供了几种不同的类装载器用于加载不同位置的jar包和class文件,特别是Context容器需要有一个单独的类装载器,因为不同应用可能有相同的类,如果用同一个类装载器去装载,就不知道该加载哪个应用里面的类了。这些类装载器之间的关系如下图所示:

系统类装载器

tomcat的系统类装载器和JDK的系统类装载器有点不同的地方是搜索路径并不相同,在catalina.bat中做了如下修改:

[plain] view plain copy
 print?
  1. rem Add on extra jar file to CLASSPATH  
  2. rem Note that there are no quotes as we do not want to introduce random  
  3. rem quotes into the CLASSPATH  
  4. if "%CLASSPATH%" == "" goto emptyClasspath  
  5. set "CLASSPATH=%CLASSPATH%;"  
  6. :emptyClasspath  
  7. set "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"  
  8. rem Add tomcat-juli.jar to classpath  
  9. rem tomcat-juli.jar can be over-ridden per instance  
  10. if not exist "%CATALINA_BASE%\bin\tomcat-juli.jar" goto juliClasspathHome  
  11. set "CLASSPATH=%CLASSPATH%;%CATALINA_BASE%\bin\tomcat-juli.jar"  
先将classpath清空,因为classpath中可能有tomcat启动相关的类会影响tomcat的正常启动。然后将bootstrap.jar和tomcat-juli.jar加入classpath中,在catalina.bat中调用了Bootstrap类的main方法,这里系统类装载器会装载Bootstrap类,Bootstrap类用到的Catalina类也是由系统类装载器装载的。
随后在Bootstrap的init方法中创建了3个类装载器:
[java] view plain copy
 print?
  1. public void init() throws Exception{  
  2.     // Set Catalina path  
  3.     setCatalinaHome();  
  4.     setCatalinaBase();  
  5.   
  6.     initClassLoaders();  
  7.   
  8.     Thread.currentThread().setContextClassLoader(catalinaLoader);  
  9.   
  10.     SecurityClassLoad.securityClassLoad(catalinaLoader);  
  11.     ...  
  12. }     
  13.   
  14. private void initClassLoaders() {  
  15.     try {  
  16.         commonLoader = createClassLoader("common"null);  
  17.         if( commonLoader == null ) {  
  18.             // no config file, default to this loader - we might be in a 'single' env.  
  19.             commonLoader=this.getClass().getClassLoader();  
  20.         }  
  21.         catalinaLoader = createClassLoader("server", commonLoader);  
  22.         sharedLoader = createClassLoader("shared", commonLoader);  
  23.     } catch (Throwable t) {  
  24.         handleThrowable(t);  
  25.         log.error("Class loader creation threw exception", t);  
  26.         System.exit(1);  
  27.     }  
  28. }  

Common Class Loader

首先创建CommonLoader,是在createClassLoader方法中完成的,代码如下:
[java] view plain copy
 print?
  1. private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {  
  2.     String value = CatalinaProperties.getProperty(name + ".loader");  
  3.     if ((value == null) || (value.equals("")))  
  4.         return parent;  
  5.   
  6.     value = replace(value);  
  7.   
  8.     List<Repository> repositories = new ArrayList<Repository>();  
  9.     StringTokenizer tokenizer = new StringTokenizer(value, ",");  
  10.     while (tokenizer.hasMoreElements()) {  
  11.         String repository = tokenizer.nextToken().trim();  
  12.         if (repository.length() == 0) {  
  13.             continue;  
  14.         }  
  15.   
  16.         // Check for a JAR URL repository  
  17.         try {  
  18.             @SuppressWarnings("unused")  
  19.             URL url = new URL(repository);  
  20.             repositories.add(new Repository(repository, RepositoryType.URL));  
  21.             continue;  
  22.         } catch (MalformedURLException e) {  
  23.             // Ignore  
  24.         }  
  25.   
  26.         // Local repository  
  27.         if (repository.endsWith("*.jar")) {  
  28.             repository = repository.substring(0, repository.length() - "*.jar".length());  
  29.             repositories.add(new Repository(repository, RepositoryType.GLOB));  
  30.         } else if (repository.endsWith(".jar")) {  
  31.             repositories.add(new Repository(repository, RepositoryType.JAR));  
  32.         } else {  
  33.             repositories.add(new Repository(repository, RepositoryType.DIR));  
  34.         }  
  35.     }  
  36.   
  37.     ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories, parent);  
  38.     ...  
  39.   
  40.     return classLoader;  
  41. }  
(1)、从bootstrap.jar包可以找到catalina.properties文件:common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar,很明显这个类装载器搜索路径就是${catalina.home}/lib和${catalina.home}/lib/*.jar,之所以叫common class loader,是因为它加载每个应用要用到的公共jar包和class文件;
(2)、遍历values,将每个value封装成Repository,然后根据repository和parent创建这个classLoader,在这个方法中parent传入的是null值,代表这个类装载器的父装载器是系统类装载器,实际上返回的是StandardClassLoader类,StandardClassLoader类是URLClassLoader的子类,即将被废弃。之所以返回的是StandardClassLoader是在ClassLoaderFactory的createClassLoader方法中被包装了一层。

Catalina Class Loader

common class loader创建好之后,又创建了catalinaLoader,其搜索路径为空,以下是catalina.properties的配置项:
[java] view plain copy
 print?
  1. common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar  
  2. server.loader=  
  3. shared.loader=  
common class loader是以common class loader为父装载器的,因此其搜索路径和common class loader一样。catalina class loader创建好后,在init方法中随即调用了Thread.currentThread().setContextClassLoader(catalinaLoader);将其设置为当前线程的类装载器

Shared Class Loader

已不再使用,tomcat早期的版本在使用这个类装载器,负责装载应用中公用的类,后来这些公用的类被移到了{catalina.base}/lib目录下,这个装载器暂时未被使用
综上所述:tomcat在启动的时候初始化了三个类加载器,commonLoader,catalinaLoader,sharedLoader.其中commonLoader是另外两个的父装载器,且为standardClassLoader类型,tomcat真正使用的是commonLoader,engine,host,connector等都是使用commonLoader装载的。
 

Webapp Class Loader

这个类装载器是tomcat自定义的类装载器,先来看看类图:

tomcat的一个service除了容器和连接器外还有很多组件,比如sessionManager,logger,loader等,这个类装载器是以组件的形式附着在每个容器上的,Engine和Host的这两个容器的loader组件为null,context里面是有值的,看看context的startInternal方法:
[java] view plain copy
 print?
  1. protected synchronized void startInternal() throws LifecycleException {  
  2. ...  
  3.        if (getLoader() == null) {  
  4.            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());  
  5.            webappLoader.setDelegate(getDelegate());  
  6.            setLoader(webappLoader);  
  7.        }  
  8. ...  
  9.        // Binding thread  
  10.        ClassLoader oldCCL = bindThread();  
  11.        try {  
  12.   
  13.            if (ok) {  
  14.                  
  15.                // Start our subordinate components, if any  
  16.                if ((loader != null) && (loader instanceof Lifecycle))  
  17.                    ((Lifecycle) loader).start();  
  18.   
  19.                // since the loader just started, the webapp classloader is now  
  20.                // created.  
  21.                // By calling unbindThread and bindThread in a row, we setup the  
  22.                // current Thread CCL to be the webapp classloader  
  23.                unbindThread(oldCCL);  
  24.                oldCCL = bindThread();  
  25. }  
  26. ...  
  27.        } finally {  
  28.            // Unbinding thread  
  29.            unbindThread(oldCCL);  
  30.        }  
很明显,context在启动的时候创建了一个loader组件,webapploader正是loader的实现类,这个类并不是最终的类装载器,在这个类里面有一个webappclassloader类型的字段叫classloader,这个classloader的创建是在loader组件的start方法中完成的
[java] view plain copy
 print?
  1.   protected void startInternal() throws LifecycleException {  
  2. ...  
  3.       // Construct a class loader based on our current repositories list  
  4.       try {  
  5.           classLoader = createClassLoader();  
  6.           classLoader.setResources(container.getResources());  
  7.           classLoader.setDelegate(this.delegate);  
  8.           classLoader.setSearchExternalFirst(searchExternalFirst);  
  9.           if (container instanceof StandardContext) {  
  10.               classLoader.setAntiJARLocking(  
  11.                       ((StandardContext) container).getAntiJARLocking());  
  12.               classLoader.setClearReferencesStatic(  
  13.                       ((StandardContext) container).getClearReferencesStatic());  
  14.               classLoader.setClearReferencesStopThreads(  
  15.                       ((StandardContext) container).getClearReferencesStopThreads());  
  16.               classLoader.setClearReferencesStopTimerThreads(  
  17.                       ((StandardContext) container).getClearReferencesStopTimerThreads());  
  18.               classLoader.setClearReferencesHttpClientKeepAliveThread(  
  19.                       ((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());  
  20.           }  
  21.   
  22.           for (int i = 0; i < repositories.length; i++) {  
  23.               classLoader.addRepository(repositories[i]);  
  24.           }  
  25.   
  26.           // Configure our repositories  
  27.           setRepositories();  
  28.           setClassPath();  
  29.   
  30.           setPermissions();  
  31.   
  32.           ((Lifecycle) classLoader).start();  
  33.     ...  
  34.       } catch (Throwable t) {  
  35.        ...  
  36.       }  
  37. ...  
  38.   }  
(1)、在createClassLoader方法中通过反射实例化了org.apache.catalina.loader.WebappClassLoader这个类,并调用了它的setParentClassLoader设置其父装载器为standardClassLoader
[java] view plain copy
 print?
  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. }  
(2)、为webappclassloader添加仓库(仓库表示类装载器会在哪些路径搜索类)
将/WEB-INF/classes目录添加到仓库中,然后将/WEB-INF/lib目录下的jar包也添加到仓库中
[java] view plain copy
 print?
  1. private void setRepositories() throws IOException {  
  2.     ...  
  3.     // Setting up the class repository (/WEB-INF/classes), if it exists  
  4.     String classesPath = "/WEB-INF/classes";  
  5.       
  6.     ...  
  7.     // Adding the repository to the class loader  
  8.     classLoader.addRepository(classesPath + "/", classRepository);  
  9.   
  10.     // Setting up the JAR repository (/WEB-INF/lib), if it exists  
  11.     String libPath = "/WEB-INF/lib";  
  12.     ...  
  13.     // Looking up directory /WEB-INF/lib in the context  
  14.     NamingEnumeration<NameClassPair> enumeration = libDir.list("");  
  15.     while (enumeration.hasMoreElements()) {  
  16.         NameClassPair ncPair = enumeration.nextElement();  
  17.         String filename = libPath + "/" + ncPair.getName();  
  18.         if (!filename.endsWith(".jar"))  
  19.             continue;  
  20.         ...  
  21.         try {  
  22.             JarFile jarFile = new JarFile(destFile);  
  23.             classLoader.addJar(filename, jarFile, destFile);  
  24.         } catch (Exception ex) {  
  25.          ...  
  26.         }  
  27.         ...  
  28.     }  
  29. }  
(3)、为类装载器设置权限,这里Globals.IS_SECURITY_ENABLED值为false,表示安全机制未打开,直接返回
(4)、启动这个loader
Webappclassloader设计的过程中考虑了优化和安全两方面。例如,它会缓存之前已经载入的类以提高性能。此外,它还会缓存失败的类的名字,下次再次请求加载相同的类时直接抛出ClassNotFoundException异常。考虑到安全性,不允许载入指定的某些类,这些类在triggers数组中,目前有两个类:
[java] view plain copy
 print?
  1. protected static final String[] triggers = {  
  2.     "javax.servlet.Servlet""javax.el.Expression"       // Servlet API  
  3. };  

WebappClassLoader装载类

loadClass是在其loadClass方法中完成的,下面详细分析这个方法:
[java] view plain copy
 print?
  1. public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {  
  2.     ...  
  3.     // 先检查本地缓存  
  4.     clazz = findLoadedClass0(name);  
  5.     if (clazz != null) {  
  6.         if (log.isDebugEnabled())  
  7.             log.debug("  Returning class from cache");  
  8.         if (resolve)  
  9.             resolveClass(clazz);  
  10.         return (clazz);  
  11.     }  
  12.   
  13.     // 如果本地缓存没有,则检查上一级缓存  
  14.     clazz = findLoadedClass(name);  
  15.     if (clazz != null) {  
  16.         if (log.isDebugEnabled())  
  17.             log.debug("  Returning class from cache");  
  18.         if (resolve)  
  19.             resolveClass(clazz);  
  20.         return (clazz);  
  21.     }  
  22.   
  23.     // 如果两个缓存都没有,则使用系统的类装载器进行装载,防止Web应用程序中的类覆盖J2EE的类  
  24.     try {  
  25.         clazz = system.loadClass(name);  
  26.         if (clazz != null) {  
  27.             if (resolve)  
  28.                 resolveClass(clazz);  
  29.             return (clazz);  
  30.         }  
  31.     } catch (ClassNotFoundException e) {  
  32.         // Ignore  
  33.     }  
  34.   
  35.     // 如果启用了SecurityManager,则检查此类是否允许被载入,如果不允许,则抛出异常  
  36.     if (securityManager != null) {  
  37.         int i = name.lastIndexOf('.');  
  38.         if (i >= 0) {  
  39.             try {  
  40.                 securityManager.checkPackageAccess(name.substring(0, i));  
  41.             } catch (SecurityException se) {  
  42.                 String error = "Security Violation, attempt to use " + "Restricted Class: " + name;  
  43.                 log.info(error, se);  
  44.                 throw new ClassNotFoundException(error, se);  
  45.             }  
  46.         }  
  47.     }  
  48.   
  49.     boolean delegateLoad = delegate || filter(name);  
  50.   
  51.     // 若打开了delegateLoad标志位,调用父装载器来加载。如果父装载器为null,使用系统类装载器装载  
  52.     if (delegateLoad) {  
  53.         if (log.isDebugEnabled())  
  54.             log.debug("  Delegating to parent classloader1 " + parent);  
  55.         ClassLoader loader = parent;  
  56.         if (loader == null)  
  57.             loader = system;  
  58.         try {  
  59.             clazz = Class.forName(name, false, loader);  
  60.             if (clazz != null) {  
  61.                 if (log.isDebugEnabled())  
  62.                     log.debug("  Loading class from parent");  
  63.                 if (resolve)  
  64.                     resolveClass(clazz);  
  65.                 return (clazz);  
  66.             }  
  67.         } catch (ClassNotFoundException e) {  
  68.             // Ignore  
  69.         }  
  70.     }  
  71.   
  72.     // 从本地仓库中载入相关类  
  73.     if (log.isDebugEnabled())  
  74.         log.debug("  Searching local repositories");  
  75.     try {  
  76.         clazz = findClass(name);  
  77.         if (clazz != null) {  
  78.             if (log.isDebugEnabled())  
  79.                 log.debug("  Loading class from local repository");  
  80.             if (resolve)  
  81.                 resolveClass(clazz);  
  82.             return (clazz);  
  83.         }  
  84.     } catch (ClassNotFoundException e) {  
  85.         // Ignore  
  86.     }  
  87.   
  88.     // 若当前仓库中没有需要的类,且delegateLoad标志位关闭,则使用父装载器。若父装载器为null,使用系统类装载器来装载  
  89.     if (!delegateLoad) {  
  90.         if (log.isDebugEnabled())  
  91.             log.debug("  Delegating to parent classloader at end: " + parent);  
  92.         ClassLoader loader = parent;  
  93.         if (loader == null)  
  94.             loader = system;  
  95.         try {  
  96.             clazz = Class.forName(name, false, loader);  
  97.             if (clazz != null) {  
  98.                 if (log.isDebugEnabled())  
  99.                     log.debug("  Loading class from parent");  
  100.                 if (resolve)  
  101.                     resolveClass(clazz);  
  102.                 return (clazz);  
  103.             }  
  104.         } catch (ClassNotFoundException e) {  
  105.             // Ignore  
  106.         }  
  107.     }  
  108.     //仍未找到,抛出异常  
  109.     throw new ClassNotFoundException(name);  
  110.   
  111. }  
整个思路是:先到缓存中获取,如果缓存中有直接返回,否则根据delegateLoad采取不同的加载方式。如果未启用这个标志:先本地仓库加载再父装载器或者系统类装载器装载;如果启用了这个标志:直接由父装载器或者系统类装载器装载。

类缓存

tomcat之所以采用自定义类装载器,除了不同应用之间有相同类不好解决之外,还有一个原因是可以缓存类以提高速度。每个由webappclassloader装载的类被视为资源,用ResourceEntry表示。加入缓存的代码是在loadclass方法中完成的,前面提到会搜索本地仓库,就是在这步调用了findClass方法完成了类的查找,并把找到的类封装成ResourceEntry,最后把这个resourceEntry放入resourceEntries中缓存起来。

 

分享到:
评论

相关推荐

    Tomcat 5.0.18 ClassLoader source code insight

    《深入解析Tomcat 5.0.18 ClassLoader源码》 在Java Web开发中,Tomcat作为一款广泛使用的应用服务器,其内部机制对于开发者来说具有极高的学习价值。尤其是Tomcat的ClassLoader机制,它是Java类加载的核心部分,...

    tomcat 学习与分析总结资料

    《Tomcat学习与分析总结资料》是一份涵盖了Tomcat服务器核心知识的综合资源,适合对Java Web应用服务器感兴趣的开发者深入学习。Tomcat是Apache软件基金会的项目,是世界上最流行的开源Servlet容器,它实现了Java ...

    Tomcat源代码学习研究

    - **Classloading**:Tomcat使用定制的ClassLoader加载Web应用的类,遵循“父类加载优先”原则。 6. **连接器与协议处理** - **NIO和Apr**:Tomcat提供了多种连接器实现,如基于Java NIO的 Coyote Connector 和...

    tomcat源码学习

    5. **ClassLoader**:Tomcat的类加载机制允许它支持多个Web应用,同时防止类冲突。理解`org.apache.catalina.loader`包下的`WebappClassLoader`及其工作原理,是深入理解Tomcat多应用隔离的关键。 6. **线程池和...

    Tomcat.ClassLoader.rar_Java编程_Java_

    Java编程世界中,ClassLoader是一个至关重要的概念,尤其是在服务器端应用如Tomcat的环境中。本资料“Tomcat.ClassLoader.rar”聚焦于Java的类加载器(Class Loader)以及它在Tomcat容器中的工作原理,这对于理解和...

    Tomcat:apache-tomcat-6.0.18

    5. **ClassLoader机制**:Tomcat使用自定义的ClassLoader来加载Web应用程序的类,确保不同应用之间的类隔离,防止冲突。 Tomcat 6.0.18版的特性包括: 1. **性能优化**:相对于之前的版本,6.0.18进行了性能调优,...

    《深入剖析 Tomcat》PDF版本下载.txt

    - **Connector**:它是Tomcat的核心组件之一,负责接收客户端请求并将请求传递给合适的Container进行处理。Connector支持多种协议,如HTTP/1.1、AJP/1.3等。通过配置不同的Connector,可以实现Tomcat同时支持多种...

    tomcat-8.0.50.zip

    5. **ClassLoader**:Tomcat使用自定义的ClassLoader来加载Web应用程序的类,这使得不同的应用程序可以使用相同的库而不会相互冲突。 6. **部署与管理**:可以通过修改conf/server.xml和conf/context.xml文件,或者...

    apache-tomcat-7.0.81-src 源码免费下载

    源码中的`classloader`目录揭示了这一机制。 9. **错误处理与日志系统**:Tomcat使用自定义的日志框架,源码中`logging`目录下的类定义了如何记录和处理错误信息。 10. **网络编程**:Tomcat底层使用NIO(非阻塞I/...

    Tomcat6的源码

    5. **ClassLoader机制**:Tomcat的类加载机制允许每个Web应用拥有自己的类加载器,避免类冲突。理解这部分源码对于理解和解决部署问题至关重要。 6. **Session管理**:Tomcat处理用户会话,包括创建、跟踪和管理...

    apache-tomcat-7.0.40-src

    1. **Servlet和JSP标准实现**:Tomcat是Java Web应用的标准之一,它遵循Java Servlet和JSP规范。在源码中,你可以看到如何实现这些规范,包括请求处理、响应生成、会话管理以及生命周期管理。 2. **Catalina组件**...

    深入剖析Tomcat 随书 源码

    《深入剖析Tomcat》这本书是Java开发者们探索Tomcat服务器内部机制的重要参考资料,它带领读者逐步揭开Tomcat的...通过学习和研究Tomcat源码,我们可以提升技术水平,解决实际问题,甚至为Tomcat社区贡献自己的力量。

    TOMCAT源代码,包括转载得别人的分析

    通过阅读"TOMCAT源码分析.doc"和解压后的"apache-tomcat-6.0.0-src.zip",你可以深入了解上述知识点,并学习如何根据源码进行调试、优化或扩展Tomcat。这份资料对于Java Web开发者来说是一份宝贵的参考资料,有助于...

    tomcat8源码的maven项目

    3. **学习部署和加载机制**:Tomcat如何加载和管理Web应用程序,包括WAR文件的部署和Classloader的工作原理。 4. **研究线程模型**:Tomcat如何使用线程来处理并发请求,以及线程池的配置和管理。 5. **深入JSP和...

    tomcat8源码

    《深入剖析Tomcat8源码》 ...通过深入学习和分析Tomcat8源码,我们可以更好地理解Web应用的工作原理,提升开发和调优能力。对于Java EE开发者来说,掌握Tomcat源码无疑能够增强对整个Web栈的掌控力。

    Classloader

    理解`Classloader`的工作原理对于深入学习Java、优化应用性能以及进行安全控制具有重要意义。 首先,让我们来看看`Classloader`的基本结构。Java中的类加载器通常遵循一种委托模型(Delegation Model)。这意味着当...

Global site tag (gtag.js) - Google Analytics