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的類加載體系

  除了JVM規范提到的以上三種類加載器之外,Tomcat還實現了自身的類加載體系。為便於理解,圖1展示了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,有興趣的讀者可自行閱讀源碼。

 

 

如需轉載,請標明本文作者及出處——作者:jiaan.gja,本文原創首發:博客園,原文鏈接:http://www.cnblogs.com/jiaan-geng/p/4860432.html 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM