Tomcat研究之ClassLoader

转自:http://blog.csdn.net/chen77716/article/details/34790

在研究Tomcat之前,一般是借用现有的UML工具分析Tomcat整体结构,但要分析Tomcat的流程就必须从分析Tomcat的StartUp入手。Tomcat的启动是从解析bat文件开始,bat文件最终调用org.apache.catalina.startup.Bootstrap开始类的加载。

.Tomcat的ClassLoader:

    TOMCAT自己的类载入器(ClassLoader)

       +---------------------------+

       |         Bootstrap         |

       |             |             |

       |          System           |

       |             |             |

       |          Common           |

       |         /      /          |

       |     Catalina  Shared      |

       +---------------------------+

     其中:

     - Bootstrap - 载入JVM自带的类和$JAVA_HOME/jre/lib/ext/*.jar

     - System   

1.载入$CATALINA_HOME/bin/bootstrap.jar  初始化Tomcat,执行Main方法

2$JAVA_HOME/lib/tools.jar  Sun的工具类,包括编译Jsp为Servlet的工具类

   - Common      这个目录下的类虽然对TOMCAT和所有的WEB APP都可见.但是Web App的类不应该

                    放在这个目录下,所有未打包的Class都在$CATALINA_HOME/common/classes下,所

                    有打包的jar都在

                    $CATALINA_HOME/commons/endorsed和$CATALINA_HOME/common/lib下,默认情况会

                    包含以下几个包:

                    1. jndi.jar JNDI API接口,这个包仅在Java1.2时候装入,1.3以后的版本JDK已

                    自动装入.

                    2. naming-common.jar JNDI接口实现类,Tomcat用这些类在内存中使用Context.

                    3. naming-resources.jar JNDI实现,Tomcat用它们定位Web App的静态资源.

                    4. servlet.jar Servlet,Jsp API

                    5xerces.jar XML解析器,特定的Web App可以在自己的/WEB-INF/lib 中覆盖.

 

    Catalina  装入Tomcat实现所有接口的类,这些类对Web App是完全不可见的,所有未打包的类在

                    $CATALINA_HOME/server/classes所有jar包在$CATALINA_HOME/server/lib下.一

                    般情况该ClassLoader将Load下面几个包:

                    1. catalina.jar Servlet容器的Tomcat实现包

                    2. jakarta-regexp-X.Y.jar 正则表达式,请求过滤时使用

                    3. servlets-xxxxx.jar Servlet支持包

                    4tomcat-coyote.jar Tomcat的Coyote连接实现包

                    5. tomcat-jk.jar Web Server绑定包,允许Tomcat绑定Apache等作为Web Server

                    6. tomcat-jk2.jar 功能同上

                    7. tomcat-util.jar Tomcat工具类,可能被一些Connector用到

                    8. tomcat-warp.jar 用于Apache Server包

     - Shared   载入所有WEB APP都可见的类,对TOMCAT不可见. 所有未打包的类在

                    $CATALINA_HOME/shared/classes所有jar包在$CATALINA_HOME /lib下.

                    默认情况包含下面几个包:

                    1. jasper-compiler.jar Jsp编译器,编译Jsp为Servlet

                    2. jasper-runtime.jar Jsp(已编译成Servlet)运行支持包

                    3. naming-factory.jar 支持Web App使用JNDI的封装包

     -WebAppX   Web App ClassLoader,当Web App被部署是该ClassLoader被创建.所有class都在

                    WEB-INF/classes下,所有jar在WEB-INF/lib下.

  

  特别注意WEB APP自己的ClassLoader的实现与众不同:

  它先试图从WEB APP自己的目录里载入,如果失败则请求父ClassLoader的代理

  这样可以让不同的WEB APP之间的类载入互不干扰.另,Tomcat Server使用的是Catalina   

  ClassLoader,一般的Web App使用的是WebApp ClassLoader.

. org.apache.catalina.startup.Bootstrap

          该类是Tomcat的执行入口点,我们着重分析下面两个方法:

          1. initClassLoaders,创建ClassLoader层次.

            private void initClassLoaders() {

                try {

                          ClassLoaderFactory.setDebug(debug);

                          //创建common ClassLoader,没有父ClassLoader

                          commonLoader = createClassLoader("common", null);

 

                          //创建catalina ClassLoader,父ClassLoader为common

                          catalinaLoader = createClassLoader("server", commonLoader);

 

                          //创建shared ClassLoader, 父ClassLoader为common

                          sharedLoader = createClassLoader("shared", commonLoader);

                     } catch (Throwable t) {

                          log("Class loader creation threw exception", t);

                          System.exit(1);

                     }

             }

          2. createClassLoader,负责具体的创建工作

             在$CATALINA_HOME/conf/catalina.properties中定义了common, server, shared

             ClassLoader载入类的路径及一些包的安全权限.

         

             //common载入类的路径

             common.loader=${catalina.home}/common/classes,

             ${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar

                //server载入类的路径

                server.loader=${catalina.home}/server/classes,

                               ${catalina.home}/server/lib/*.jar

 

                //shared载入类的路径

                shared.loader=${catalina.base}/shared/classes,

                               ${catalina.base}/shared/lib/*.jar

         

        /**

          *param name:Load Name

          *param parent:父Loader

          *classLoader的资源分三种:

          *1.未打包的classes,一般是一个目录

          *2.打包的jar目录

          *3.网络资源,一般是网上的一个jar包 (Applet经常用到这样的loader)

          */

        private ClassLoader createClassLoader(String name, ClassLoader parent)

                    throws Exception {

 

          //从catalina.properties中取得改Loader的配置信息

          String value = CatalinaProperties.getProperty(name + ".loader");

          if ((value == null) || (value.equals("")))

                    return parent;

         

          //classes目录

      ArrayList unpackedList = new ArrayList();

          //jar目录

      ArrayList packedList = new ArrayList();

          //网络路径指定的包

      ArrayList urlList = new ArrayList();

 

           StringTokenizer tokenizer = new StringTokenizer(value, ",");

            

             //当前Loader该装载的类

             while (tokenizer.hasMoreElements()) {

                    String repository = tokenizer.nextToken();

                    // Check for a JAR URL repository

            try {

                //如果是网络路径追加url

                urlList.add(new URL(repository));

                continue;

            } catch (MalformedURLException e) {

                // Ignore

            }

 

            // 本地路径

            boolean packed = false;

 

           //${catalina.home}

            if (repository.startsWith(CATALINA_HOME_TOKEN)) {

                repository = getCatalinaHome()

                    + repository.substring(CATALINA_HOME_TOKEN.length());

           //${catalina.base}

           } else if (repository.startsWith(CATALINA_BASE_TOKEN)) {

                repository = getCatalinaBase()

                    + repository.substring(CATALINA_BASE_TOKEN.length());

                    }

                    /**经过上述操作,把catalina.properties里的路径替换成绝对路径*/

           

           //如果是jar文件路径

           if (repository.endsWith("*.jar")) {

                packed = true;

                repository = repository.substring

                    (0, repository.length() - "*.jar".length());

            }

            if (packed) {

                packedList.add(new File(repository));

            } else {

                unpackedList.add(new File(repository));

            }

        }

 

        File[] unpacked = (File[]) unpackedList.toArray(new File[0]);

        File[] packed = (File[]) packedList.toArray(new File[0]);

        URL[] urls = (URL[]) urlList.toArray(new URL[0]);

 

        //调用Factory的方法创建ClassLoader

        return ClassLoaderFactory.createClassLoader

            (unpacked, packed, urls, parent);

    }

 

. ClassLoaderFactory

          ClassLoaderFactory是用于创建ClassLoader的工厂类,这个类比较简单.

         

//参数含义不再说明,参看上面的分析

public static ClassLoader createClassLoader(File unpacked[],

                                                File packed[],

                                                URL urls[],

                                                ClassLoader parent)

        throws Exception {

 

        if (debug >= 1)

            log("Creating new class loader");

 

        // Construct the "class path" for this class loader

        ArrayList list = new ArrayList();

 

        // 通过class目录构造file协议的url,并追加的list

        if (unpacked != null) {

            for (int i = 0; i < unpacked.length; i++)  {

                File file = unpacked[i];

                if (!file.exists() || !file.canRead())

                    continue;

                if (debug >= 1)

                    log("  Including directory or JAR "

                        + file.getAbsolutePath());

                URL url = new URL("file", null,

                                  file.getCanonicalPath() + File.separator);

                list.add(url.toString());

            }

        }

 

        //取出所有jar目录里的jar文件,逐一构造url,并追加的list

        if (packed != null) {

            for (int i = 0; i < packed.length; i++) {

                File directory = packed[i];

                if (!directory.isDirectory() || !directory.exists() ||

                    !directory.canRead())

                    continue;

                String filenames[] = directory.list();

                for (int j = 0; j < filenames.length; j++) {

                    String filename = filenames[j].toLowerCase();

                    if (!filename.endsWith(".jar"))

                        continue;

                    File file = new File(directory, filenames[j]);

                    if (debug >= 1)

                        log("  Including jar file " + file.getAbsolutePath());

                    URL url = new URL("file", null,

                                      file.getCanonicalPath());

                    list.add(url.toString());

                }

            }

        }

 

        //追加网络路径的资源

        if (urls != null) {

            for (int i = 0; i < urls.length; i++) {

                list.add(urls[i].toString());

            }

        }

 

        //调用StandardClassLoader创建实际的Loader

        String array[] = (String[]) list.toArray(new String[list.size()]);

        StandardClassLoader classLoader = null;

        if (parent == null)

            classLoader = new StandardClassLoader(array);

        else

            classLoader = new StandardClassLoader(array, parent);

        classLoader.setDelegate(true);

        return (classLoader);

 

    }

. StandardClassLoader

          StandardClassLoader继承了URLClassLoader, URLClassLoader类具有从硬盘目录装载类,或从本地或远程装载jar文件的能力.这个类也实现了Reloader接口,提供了自动重新装载类的功能.我们主要看这个类以下及个方法:

1.      构造函数StandardClassLoader

   /**

     * @param repositories url数组,见上分析

     * @param parent 父loader

     */

    public StandardClassLoader(String repositories[], ClassLoader parent) {

       //调用父类的构造函数

       //父类的构造函数将用转换后的repositories生成URLClassPath的实例

       //ucp是类成员变量ucp= new URLClassPath(urls);

      // URLClassPath是sun的扩展包,无法继续跟踪

        super(convert(repositories), parent);

        this.parent = parent;

        this.system = getSystemClassLoader();

        securityManager = System.getSecurityManager();

     if (repositories != null) {

            for (int i = 0; i < repositories.length; i++)

                //处理url

                addRepositoryInternal(repositories[i]);

        }

    }

2.      addRepositoryInternal

   /**

     * @param repository 要处理的url

     */

    protected void addRepositoryInternal(String repository) {

 

        URLStreamHandler streamHandler = null;

        String protocol = parseProtocol(repository);

        if (factory != null)

            streamHandler = factory.createURLStreamHandler(protocol);

 

        // 当前url是指向本地或网路的jar文件,验证jar的正确性

        //下面的代码看似无用其实是在验证jar文件的正确性,如果jar文件错误抛异常中止执行.

        if (!repository.endsWith(File.separator) && !repository.endsWith("/")) {

            JarFile jarFile = null;

            try {

                Manifest manifest = null;

 

                //jar协议

                if (repository.startsWith("jar:")) {

                    URL url = new URL(null, repository, streamHandler);

                    JarURLConnection conn =

                        (JarURLConnection) url.openConnection();

                    conn.setAllowUserInteraction(false);

                    conn.setDoInput(true);

                    conn.setDoOutput(false);

                    conn.connect();

                    jarFile = conn.getJarFile();

               

                //file协议

                } else if (repository.startsWith("file://")) {

                    jarFile = new JarFile(repository.substring(7));

                //file

                } else if (repository.startsWith("file:")) {

                    jarFile = new JarFile(repository.substring(5));

 

                //本地路径的jar文件

                } else if (repository.endsWith(".jar")) {

                    URL url = new URL(null, repository, streamHandler);

                    URLConnection conn = url.openConnection();

                    JarInputStream jis =

                        new JarInputStream(conn.getInputStream());

                    manifest = jis.getManifest();

 

                //其他情况均为错误

                } else {

                    throw new IllegalArgumentException

                        ("addRepositoryInternal:  Invalid URL '" +

                         repository + "'");

                }

            } catch (Throwable t) {

                t.printStackTrace();

                throw new IllegalArgumentException

                    ("addRepositoryInternal: " + t);

            } finally {

                if (jarFile != null) {

                    try {

                        jarFile.close();

                    } catch (Throwable t) {}

                }

            }

        }

 

        //增加当前的url到系统内部的url列表

        synchronized (repositories) {

            String results[] = new String[repositories.length + 1];

            System.arraycopy(repositories, 0, results, 0, repositories.length);

            results[repositories.length] = repository;

            repositories = results;

        }

    }

repositories是一个类变量,存放着所有的url列表.

 

通过这个方法我们基本可以确定几点:

1.      ClassLoader可以把一个以”/”或File.separator结尾的路径作为装载类的资源

2.      也可以把本地或网路上的jar文件作为装入class的资源,其他任何形式的资源(比如zip)都是不合法的.

3.      网络上的资源必须是jar包的形式

到此,ClassLoader的构造结束.
3.loadClass方法

          上面我们做了很多的工作,目的是构建一个classLoader,构建ClassLoader的目的是用它load想要的class,因此loadClass方法才是我们的重心,同时通过它可以彻底解开ClassLoader的神秘面纱.

    /**

      *param name:要load的类名

      *param resolve:如果是true将调用resolveClass

     */

    public Class loadClass(String name, boolean resolve)

        throws ClassNotFoundException {

 

        if (debug >= 2)

            log("loadClass(" + name + ", " + resolve + ")");

        Class clazz = null;

 

        //检查缓存,看是否类已经被load

        clazz = findLoadedClass(name);

        if (clazz != null) {

            if (debug >= 3)

                log("  Returning class from cache");

            if (resolve)

                resolveClass(clazz);

            return (clazz);

        }

 

        // 如果是java包用系统ClassLoader取load

        if( name.startsWith("java.") ) {

            ClassLoader loader = system;

            clazz = loader.loadClass(name);

            if (clazz != null) {

                if (resolve)

                    resolveClass(clazz);

                return (clazz);

            }

            throw new ClassNotFoundException(name);

        }

 

        // 检查是否有权限对该类对应的包做load,包的load权限在catalina.properties里定义

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

                }

            }

        }

 

        //该类是否委派给父ClassLoader去装载

        if (delegate) {

            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) {

                ;

            }

        }

 

        //从本地装载该类

        if (debug >= 3)

            log("  Searching local repositories");

        try {

           //调用Tomcat实现ClassLoader的核心方法去寻找类

            clazz = findClass(name);

            if (clazz != null) {

                if (debug >= 3)

                    log("  Loading class from local repository");

                if (resolve)

                    resolveClass(clazz);

                return (clazz);

            }

        } catch (ClassNotFoundException e) {

            ;

        }

 

        //如果该类没有被委派到父类,则用系统loader去装载

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

 

    }

4.      findClass方法

public Class findClass(String name) throws ClassNotFoundException {

 

        if (debug >= 3)

            log("    findClass(" + name + ")");

 

        // 检查包的定义,我们不对此处做深究可略过

        if (securityManager != null) {

            int i = name.lastIndexOf('.');

            if (i >= 0) {

                try {

                    if (debug >= 4)

                        log("      securityManager.checkPackageDefinition");

                    securityManager.checkPackageDefinition(name.substring(0,i));

                } catch (Exception se) {

                    if (debug >= 4)

                        log("      -->Exception-->ClassNotFoundException", se);

                    throw new ClassNotFoundException(name);

                }

            }

        }

        // 如果在本地不能地位类则请求父类加载

        // (throws ClassNotFoundException if it is not found)

        Class clazz = null;

        try {

            if (debug >= 4)

                log("      super.findClass(" + name + ")");

            try {

                synchronized (this) {                             

                    clazz = findLoadedClass(name);

                    if (clazz != null)

                        return clazz;

                   //请求父类加载

                    clazz = super.findClass(name);

                }

            } catch(AccessControlException ace) {

                throw new ClassNotFoundException(name);

            } catch (RuntimeException e) {

                if (debug >= 4)

                    log("      -->RuntimeException Rethrown", e);

                throw e;

            }

            if (clazz == null) {

                if (debug >= 3)

                    log("    --> Returning ClassNotFoundException");

                throw new ClassNotFoundException(name);

            }

        } catch (ClassNotFoundException e) {

            if (debug >= 3)

                log("    --> Passing on ClassNotFoundException", e);

            throw e;

        }

        // Return the class we have located

        if (debug >= 4)

            log("      Returning class " + clazz);

        if ((debug >= 4) && (clazz != null))

            log("      Loaded by " + clazz.getClassLoader());

        return (clazz);

 

    }

 

5.      父类(URLClassLoader)的findClass方法

      前面我们曾经说过,URLClassLoader具有从本地装载class,从本地或网络装载jar的功能.

简单说findClass方法只是简单地调用了PrivilegedExceptionAction的run方法,关于这个类我们不必细致了解,只需看下面打蓝色的部分就可以了.

 

    protected Class findClass(final String name) throws ClassNotFoundException  {

      try {

          return (Class)

                AccessController.doPrivileged(new PrivilegedExceptionAction() {

                    public Object run() throws ClassNotFoundException {

                           //将类名替换成硬盘绝对路径

                           String path = name.replace('.', '/').concat(".class");

                           //关于ucp类变量的说明见上面

                           //该调用主要是把本地的class文件转换成Resource(不考虑转换细节)

                           Resource res = ucp.getResource(path, false);

                           if (res != null) {

                               try {

                                     //通过类名与类的Resource生成Class

                                     return defineClass(name, res);

                               } catch (IOException e) {

                                     throw new ClassNotFoundException(name, e);

                               }

                           } else {

                               throw new ClassNotFoundException(name);

                           }

                    }

                }, acc);

      } catch (java.security.PrivilegedActionException pae) {

          throw (ClassNotFoundException) pae.getException();

      }

    }

6. defineClass方法

      该方法主要是调用JVM的native方法构建一个class对象,关于这个类的细节我们没必要很清除,我们要知道的是JVM利用Resource构造了Class对象.我们只需关注下面蓝色的部分.

      /**

        *param name:类名

        *param res:这个参数是通过ucp变量得到,ucp是通过我们传入的url构建,url就是

        *ClassLoader Load Class 的路径

        *换句话说我们辛苦构造的StandClassLoader就是为了得到Resource

        *Resource是JVM用来构造Class必须条件

        */

    private Class defineClass(String name, Resource res) throws IOException {

      int i = name.lastIndexOf('.');

      URL url = res.getCodeSourceURL();

      if (i != -1) {

          String pkgname = name.substring(0, i);

          // Check if package already loaded.

          Package pkg = getPackage(pkgname);

          Manifest man = res.getManifest();

          if (pkg != null) {

                // Package found, so check package sealing.

                if (pkg.isSealed()) {

                    // Verify that code source URL is the same.

                    if (!pkg.isSealed(url)) {

                           throw new SecurityException(

                               "sealing violation: package " + pkgname + " is sealed");

                    }

 

                } else {

                    // Make sure we are not attempting to seal the package

                    // at this code source URL.

                    if ((man != null) && isSealed(pkgname, man)) {

                           throw new SecurityException(

                               "sealing violation: can't seal package " + pkgname +

                               ": already loaded");

                    }

                }

          } else {

                if (man != null) {

                    definePackage(pkgname, man, url);

                } else {

                    definePackage(pkgname, null, null, null, null, null, null, null);

                }

          }

      }

      //下面是JVM通过二进制代码构造一个Class

      byte[] b = res.getBytes();

      java.security.cert.Certificate[] certs = res.getCertificates();

      CodeSource cs = new CodeSource(url, certs);

      return defineClass(name, b, 0, b.length, cs);

    }

   至此,我们彻底破译了Tomcat的ClassLoader,下面我们再画一下ClassLoader的构造及loadClass的过程.这里不再画sequence图,只是简单表示一下各方法之间的调用关系.其实,从上面我们可以看出真正起到创建ClassLoader关键作用的是URLClassLoader和JVM的defineClass方法,这些方法已被封装或是本地调用,要进一步研究就需要花费更多的精力.阅读了这个流程后相信每人都能定制出自己需要的ClassLoader,比如加密ClassLoader.

 

BootsStrap

 

createClassLoader(构造三种url,)

initClassLoaders(始化三个ClassLoader)

 

ClassLoaderFactory

createClassLoader(构造带协议的,真正的url)

StandardClassLoader

 

loadClass

findClass 查询类

URLClassLoader

findClass

defineClass

 

 

 

 

参考资料:

     http://jakarta.apache.org/tomcat/tomcat-4.1-doc/class-loader-howto.html

备注:

     本文所讲解的Tomcat是以5.0为基础的.

     本人非常喜欢Tomcat这个开源服务器,想研究其奥妙单甚感一人势单力薄,希望志同道合的朋友与我联系,大家一起破解apache神话.([email protected])

猜你喜欢

转载自xinklabi.iteye.com/blog/2020358