转载

Tomcat源码分析——类加载体系

前言

Tomcat遵循J2EE规范,实现了Web容器。很多有关web的书籍和文章都离不开对Tomcat的分析,初学者可以从Tomcat的实现对J2EE有更深入的了解。此外,Tomcat还根据Java虚拟机规范实现了经典的双亲委派模式的类加载体系。本文基于Tomcat7.0的Java源码,对其类加载体系进行分析。

概述

本节简单介绍Java虚拟机规范中提到的主要类加载器:

  • Bootstrap Loader:加载lib目录下或者System.getProperty(“sun.boot.class.path”)、或者-XBootclasspath所指定的路径或jar。
  • Extended Loader:加载lib/ext目录下或者System.getProperty(“java.ext.dirs”) 所指定的 路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:/projects/testproj/classes HelloWorld。
  • AppClassLoader:加载System.getProperty("java.class.path")所指定的 路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld。

Tomcat的类加载体系

Tomcat实现了自身的AppClassLoader。为便于理解,图1展示了Tomcat的类加载体系,各个类加载器之间不是继承关系,而是一种委派关系。

Tomcat源码分析——类加载体系

图1 Tomcat的类加载体系

这里对图1所示的类加载体系进行介绍:

  • ClassLoader:Java提供的类加载器抽象类,用户自定义的类加载器需要继承实现;
  • commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见。

源码分析

commonLoader、catalinaLoader和sharedLoader是在Tomcat容器初始化的的过程刚刚开始,即调用Bootstrap的init方法时创建的。catalinaLoader会被设置为Tomcat主线程的线程上下文类加载器,并且使用catalinaLoader加载Tomcat容器自身的class。Bootstrap的init方法的部分如代码清单1所示。

代码清单1

/**      * Initialize daemon.      */     public void init()  throws Exception     {  // Set Catalina path  setCatalinaHome();  setCatalinaBase();  initClassLoaders();  Thread.currentThread().setContextClassLoader(catalinaLoader);  SecurityClassLoad.securityClassLoad(catalinaLoader);  // 省略后边的代码 

代码清单1中有关类加载器的执行步骤如下:

  1. 初始化commonLoader、catalinaLoader和sharedLoader;
  2. 将catalinaLoader设置为Tomcat主线程的线程上下文类加载器;
  3. 线程安全的加载class。

初始化类加载器分析

initClassLoaders方法的实现如代码清单2所示。

代码清单2

private void initClassLoaders() {  try {   commonLoader = createClassLoader("common", null);   if( commonLoader == null ) {    // no config file, default to this loader - we might be in a 'single' env.    commonLoader=this.getClass().getClassLoader();   }   catalinaLoader = createClassLoader("server", commonLoader);   sharedLoader = createClassLoader("shared", commonLoader);  } catch (Throwable t) {   log.error("Class loader creation threw exception", t);   System.exit(1);  } } 

从代码清单2可以看到initClassLoaders调用createClassLoader方法来创建commonLoader、catalinaLoader和sharedLoader,我们来看看createClassLoader的实现,见代码清单3。

代码清单3

private ClassLoader createClassLoader(String name, ClassLoader parent)   throws Exception {   String value = CatalinaProperties.getProperty(name + ".loader");   if ((value == null) || (value.equals("")))    return parent;   ArrayList<String> repositoryLocations = new ArrayList<String>();   ArrayList<Integer> repositoryTypes = new ArrayList<Integer>();   int i;   StringTokenizer tokenizer = new StringTokenizer(value, ",");   while (tokenizer.hasMoreElements()) {    String repository = tokenizer.nextToken();    // Local repository    boolean replace = false;    String before = repository;    while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {     replace=true;     if (i>0) {     repository = repository.substring(0,i) + getCatalinaHome()       + repository.substring(i+CATALINA_HOME_TOKEN.length());     } else {      repository = getCatalinaHome()        + repository.substring(CATALINA_HOME_TOKEN.length());     }    }    while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {     replace=true;     if (i>0) {     repository = repository.substring(0,i) + getCatalinaBase()       + repository.substring(i+CATALINA_BASE_TOKEN.length());     } else {      repository = getCatalinaBase()        + repository.substring(CATALINA_BASE_TOKEN.length());     }    }    if (replace && log.isDebugEnabled())     log.debug("Expanded " + before + " to " + repository);    // Check for a JAR URL repository    try {     new URL(repository);     repositoryLocations.add(repository);     repositoryTypes.add(ClassLoaderFactory.IS_URL);     continue;    } catch (MalformedURLException e) {     // Ignore    }    if (repository.endsWith("*.jar")) {     repository = repository.substring      (0, repository.length() - "*.jar".length());     repositoryLocations.add(repository);     repositoryTypes.add(ClassLoaderFactory.IS_GLOB);    } else if (repository.endsWith(".jar")) {     repositoryLocations.add(repository);     repositoryTypes.add(ClassLoaderFactory.IS_JAR);    } else {     repositoryLocations.add(repository);     repositoryTypes.add(ClassLoaderFactory.IS_DIR);    }   }   String[] locations = repositoryLocations.toArray(new String[0]);   Integer[] types = repositoryTypes.toArray(new Integer[0]);   ClassLoader classLoader = ClassLoaderFactory.createClassLoader    (locations, types, parent);   // 省略无关代码   return classLoader;  } 

createClassLoader的处理步骤如下:

  1. 定位资源路径与资源类型;
  2. 使用ClassLoaderFactory创建类加载器org.apache.catalina.loader.StandardClassLoader。

需要注意的是,Tomcat默认只会指定commonLoader(通过common属性,默认值为${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar),catalinaLoader和sharedLoader实际也是commonLoader。属性catalina.home默认为Tomcat的根目录。

安全加载class分析

首先回头看看SecurityClassLoad.securityClassLoad(catalinaLoader)的实现,见代码清单4。

代码清单4

public static void securityClassLoad(ClassLoader loader)  throws Exception {  if( System.getSecurityManager() == null ){   return;  }  loadCorePackage(loader);  loadLoaderPackage(loader);  loadSessionPackage(loader);  loadUtilPackage(loader);  loadJavaxPackage(loader);  loadCoyotePackage(loader);    loadTomcatPackage(loader); } 

securityClassLoad方法主要负责加载Tomcat容器所需的class,包括:

  • Tomcat核心class,即org.apache.catalina.core路径下的class;
  • org.apache.catalina.loader.WebappClassLoader$PrivilegedFindResourceByName;
  • Tomcat有关session的class,即org.apache.catalina.session路径下的class;
  • Tomcat工具类的class,即org.apache.catalina.util路径下的class;
  • javax.servlet.http.Cookie;
  • Tomcat处理请求的class,即org.apache.catalina.connector路径下的class;
  • Tomcat其它工具类的class,也是org.apache.catalina.util路径下的class;

以加载Tomcat核心class的loadCorePackage方法为例(见代码清单5),查看其实现。

代码清单5

private final static void loadCorePackage(ClassLoader loader)  throws Exception {  String basePackage = "org.apache.catalina.";  loader.loadClass   (basePackage +    "core.ApplicationContextFacade$1");  loader.loadClass   (basePackage +    "core.ApplicationDispatcher$PrivilegedForward");  loader.loadClass   (basePackage +    "core.ApplicationDispatcher$PrivilegedInclude");  loader.loadClass   (basePackage +   "core.AsyncContextImpl");  loader.loadClass   (basePackage +   "core.AsyncContextImpl$AsyncState");  loader.loadClass   (basePackage +   "core.AsyncContextImpl$DebugException");  loader.loadClass   (basePackage +   "core.AsyncContextImpl$1");  loader.loadClass   (basePackage +   "core.AsyncContextImpl$2");  loader.loadClass   (basePackage +   "core.AsyncListenerWrapper");  loader.loadClass   (basePackage +    "core.ContainerBase$PrivilegedAddChild");  loader.loadClass   (basePackage +    "core.DefaultInstanceManager$1");  loader.loadClass   (basePackage +    "core.DefaultInstanceManager$2");  loader.loadClass   (basePackage +    "core.DefaultInstanceManager$3");  loader.loadClass   (basePackage +    "core.DefaultInstanceManager$4");  loader.loadClass   (basePackage +    "core.DefaultInstanceManager$5");  loader.loadClass   (basePackage +    "core.ApplicationHttpRequest$AttributeNamesEnumerator"); } 

WebappClassLoader 的实现分析

至此,我们还没有看到WebappClassLoader。启动StandardContext的时候会创建WebappLoader,启动StandardContext的方法startInternal的实现见代码清单6。

代码清单6

/**  * Start this component and implement the requirements  * of {@link LifecycleBase#startInternal()}.  *  * @exception LifecycleException if this component detects a fatal error  *  that prevents this component from being used  */ @Override protected synchronized void startInternal() throws LifecycleException {      // 省略前边无关的代码       if (getLoader() == null) {         WebappLoader webappLoader = new WebappLoader(getParentClassLoader());         webappLoader.setDelegate(getDelegate());         setLoader(webappLoader);     }    // 省略中间无关的代码     // Start our subordinate components, if any    if ((loader != null) && (loader instanceof Lifecycle))         ((Lifecycle) loader).start();     // 省略后边无关的代码  } 

代码清单6的最后会调用WebappLoader的start方法,start又调用了startInternal方法,WebappLoader的startInternal的实现见代码清单7。

代码清单7

/**  * Start associated {@link ClassLoader} and implement the requirements  * of {@link LifecycleBase#startInternal()}.  *  * @exception LifecycleException if this component detects a fatal error  *  that prevents this component from being used  */ @Override protected void startInternal() throws LifecycleException {     // 省略无关代码// Construct a class loader based on our current repositories list     try {  classLoader = createClassLoader();  classLoader.setResources(container.getResources());  classLoader.setDelegate(this.delegate);  classLoader.setSearchExternalFirst(searchExternalFirst);  if (container instanceof StandardContext) {      classLoader.setAntiJARLocking(       ((StandardContext) container).getAntiJARLocking());      classLoader.setClearReferencesStatic(       ((StandardContext) container).getClearReferencesStatic());      classLoader.setClearReferencesStopThreads(       ((StandardContext) container).getClearReferencesStopThreads());      classLoader.setClearReferencesStopTimerThreads(       ((StandardContext) container).getClearReferencesStopTimerThreads());      classLoader.setClearReferencesThreadLocals(       ((StandardContext) container).getClearReferencesThreadLocals());  }  for (int i = 0; i < repositories.length; i++) {      classLoader.addRepository(repositories[i]);  } 

最后我们看看WebappLoader的createClassLoader方法的实现,见代码清单8。

代码清单8

/**  * Create associated classLoader.  */ private WebappClassLoader createClassLoader()  throws Exception {  //loaderClass即字符串org.apache.catalina.loader.WebappClassLoader  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; } 

代码清单8中的parentClassLoader实际就是sharedLoader,即org.apache.catalina.loader.StandardClassLoader。由此也证实了图1中的WebappClassLoader的父类加载器是sharedLoader。至此,整个Tomcat的类加载体系构建完毕。最后我们看看WebappClassLoader(见代码清单9)是如何实现以及部署在tomcat中的各个webapp的资源是如何隔离的?

代码清单9

 @Override  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   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   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   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);     }    }   }   boolean delegateLoad = delegate || filter(name);   // (1) Delegate to our parent if requested   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");   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   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);  } 

从代码清单9,可以看到WebappClassLoader加载class的步骤如下:

  1. 从之前加载的class的缓存中查找;
  2. 委托sun.misc.Launcher$AppClassLoader加载class;
  3. 安全检查通过后,委托父类加载器org.apache.catalina.loader.StandardClassLoader加载class;
  4. WebappClassLoader自己加载class。

有关WebappClassLoader的findClass方法的实现很简单,其中主要调用findClassInternal方法来加载webapp自身路径下的class,有兴趣的读者可自行阅读源码。

正文到此结束
Loading...