Tomcat源碼分析 - 遵循雙親委派模型與打破雙親委派共存,Tomcat類加載器源碼分析

背景java

咱們項目在類加載時通常都遵循雙親委派模型,但在Tomcat項目源碼中卻打破了雙親委派機制的模型,在每一個Webapp應用啓動加載時建立獨立WebappClassLoader類加載器,這樣作是出於哪些重要的考慮?於此同時Tomcat也建立了多種類型的全局類加載器,而且它們負責加載特定的配置路徑,它爲何要這樣設計,是出於什麼考慮?今天咱們經過源碼就要弄清楚這兩個問題。web


類加載與類加載器apache

類加載是將class文件中的二進制字節碼(流)讀入JVM的過程。它很是靈活,能夠從本地磁盤、緩存甚至網絡直接讀取二進制流。緩存

類加載的過程很明確首先經過類的徹底限定名,查找到該類的二進制流,而後將二進制流的表明的靜態結構轉換成虛擬機內存方法區的運行時數據結構,最後在內存中生成java.lang.Class對象並做爲該類訪問的入口。
安全

JVM 設計者把類加載這個動做放到 java 虛擬機外部去實現,以便讓應用程序決定如何獲取所須要的類,這樣作很是靈活,而實現這種加載的工具就叫類加載器。對於任何一個類都須要加載它的類加載器和該類的對象來肯定其在JVM中的惟一性。網絡

image.png

雙親委派模型數據結構

工做過程爲:當一個類加載器收到類請求時,首先不考慮本身嘗試加載這個類,而是將請求直接委派給它的父加載器去完成;每一個層級的加載器都採用這種方式,所以默認狀況下可能被傳遞到啓動類加載器,只有在父加載器沒法完成請求時,這個類加載器才本身去加載,這種模式就叫雙(其實應該叫作「單」)親委派。
app

這種模式要求除啓動類加載器外,都應該有父類加載器,子類加載器不經過繼承父類加載器實現,而是採用組合關係複用父加載器的代碼實現。webapp

該模式的優勢是:讓java類和它的類加載器具備帶優先級的層次關係,同時保證了程序的穩定性和安全性。ide

遵循雙親委派模式的類加載,在JDK中的依賴關係是

image.png

Tomcat類加載的考慮

1、隔離性

Web應用項目包之間相互隔離,避免庫相互依賴和應用包相互影響。好比:在一個端口配置兩個應用包,這兩個應用包之間除了某些三方依賴包版本不同外,其餘都同樣。這時若是採用同一個類加載器,會不會出問題?答案是確定的(包的三方依賴包會被覆蓋,而致使其中一個應用沒法正常啓動)。

2、靈活性

由於考慮了隔離性,因此每個應用的類加載器相互獨立。若是某個應用從新部署時,只有該應用的類加載器會從新加載,而不會影響其它的應用。能夠針對單個應用單獨升級或部署。

3、性能

由於每一個Web應用都有本身的類加載器,因此不會去加載與本應用無關的依賴庫包,顯然類加載性能要高於全部應用都共享一個類加載器的方案。


Tomcat類加載器結構

由於有以上三點的考慮後,Tomcat的類加載器結構爲

image.png

Tomcat類加載器源碼分析

Bootstrap啓動類,入口main()方法

image.png

Bootstrap啓動類,init()初始化方法

public void init() throws Exception {
   /**
    * 定義和初始化各類類加載器
    * common-classloader 公共類加載器
    * catalina-classloader 主類加載器
    * shared-classloader 共享類加載器
    */
   
initClassLoaders();
   
/**
    * 應儘早設置上下文類加載器避免發生ClassNotFound的異常
    */
   
Thread.currentThread().setContextClassLoader(catalinaLoader);
   
/**
    * 設置安全類加載器爲catalina-classloader
    */
   
SecurityClassLoad.securityClassLoad(catalinaLoader);

   
// Load our startup class and call its process() method
   
if (log.isDebugEnabled())
       log.debug("Loading startup class");
   
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
   
Object startupInstance = startupClass.getConstructor().newInstance();

   
/**
    * 反射設置Cataline類的默認父類加載器爲shared-classloader
    */
   
if (log.isDebugEnabled())
       log.debug("Setting startup class properties");
   
String methodName = "setParentClassLoader";
   
Class<?> paramTypes[] = new Class[1];
   
paramTypes[0] = Class.forName("java.lang.ClassLoader");
   
Object paramValues[] = new Object[1];
   
paramValues[0] = sharedLoader;
   
Method method =
       startupInstance.getClass().getMethod(methodName, paramTypes);
   
method.invoke(startupInstance, paramValues);

   
catalinaDaemon = startupInstance;
}

Bootstrap啓動類initClassLoaders()方法

/**
* 初始化和建立各類加載器實現
* 經過解析/conf/catalina.properties文件
*/
private void initClassLoaders() {
   try {
       /**
        * 建立common-classloader公共類加載器
        */
       
commonLoader = createClassLoader("common", null);
       if
( commonLoader == null ) {
           /**
            * 若common.loader屬性未配置加載路徑,則採用默認的AppClassLoader加載器
            */
           
// no config file, default to this loader - we might be in a 'single' env.
           
commonLoader=this.getClass().getClassLoader();
       
}
       /**
        * 建立catalina-classloader服務主類加載器
        */
       
catalinaLoader = createClassLoader("server", commonLoader);
       
/**
        * 建立shared-classloader共享類加載器
        */
       
sharedLoader = createClassLoader("shared", commonLoader);
   
} catch (Throwable t) {
       handleThrowable(t);
       
log.error("Class loader creation threw exception", t);
       
System.exit(1);
   
}
}

/conf/catalina-properties配置文件

image.png

建立類加載器公共封裝createClassLoader();

/**
* 建立類加載器
*
@param name 加載器名稱
*
@param parent 父類加載器
*
@return classloader
*
@throws Exception
*/
private ClassLoader createClassLoader(String name, ClassLoader parent)
   throws Exception {
   /**
    * 構建節點名稱,從catalina-properties文件解析加載路徑
    */
   
String value = CatalinaProperties.getProperty(name + ".loader");
   
/**
    * 當節點未配置加載器路徑時,統一採用common-classloader做爲類加載器
    */
   
if ((value == null) || (value.equals("")))
       return parent;

   
value = replace(value);

   
List<Repository> repositories = new ArrayList<>();

   
String[] repositoryPaths = getPaths(value);

   for
(String repository : repositoryPaths) {
       // Check for a JAR URL repository
       
try {
           @SuppressWarnings("unused")
           URL url = new URL(repository);
           
repositories.add(
                   new Repository(repository, RepositoryType.URL));
           continue;
       
} catch (MalformedURLException e) {
           // Ignore
       
}
       // Local repository
       
if (repository.endsWith("*.jar")) {
           repository = repository.substring
               (0, repository.length() - "*.jar".length());
           
repositories.add(
                   new Repository(repository, RepositoryType.GLOB));
       
} else if (repository.endsWith(".jar")) {
           repositories.add(
                   new Repository(repository, RepositoryType.JAR));
       
} else {
           repositories.add(
                   new Repository(repository, RepositoryType.DIR));
       
}
   }
   /**
    * 經過工廠類建立類UrlClassLoader類型加載器
    */
   
return ClassLoaderFactory.createClassLoader(repositories, parent);
}

ClassLoaderFactory工廠類建立類加載器

image.png

WebappClassLoader源碼解析

在StandardContext應用程序包上下文類啓動時,Tomcat經過解析web.xml建立獨立的WebappClassLoader,每一個應用包建立一個獨立的類加載器。入口方法爲startInternal()啓動方法實現。

image.png


經過LifecycleBase生命週期骨架類,開啓web.xml配置文件解析

image.png

WebappClassLoaderBase抽象基類,打破雙親委派重寫loadClass()方法;

@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

   synchronized (getClassLoadingLock(name)) {
       if (log.isDebugEnabled())
           log.debug("loadClass(" + name + ", " + resolve + ")");
       
Class<?> clazz = null;

       
// Log access to stopped class loader
       
checkStateForClassLoading(name);

       
// (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 Java SE classes. This implements
       //       SRV.10.7.2
       
String resourceName = binaryNameToPath(name, false);

       
ClassLoader javaseLoader = getJavaseClassLoader();
       boolean
tryLoadingFromJavaseLoader;
       try
{
           // Use getResource as it won't trigger an expensive
           // ClassNotFoundException if the resource is not available from
           // the Java SE class loader. However (see
           // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
           // details) when running under a security manager in rare cases
           // this call may trigger a ClassCircularityError.
           // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
           // details of how this may trigger a StackOverflowError
           // Given these reported errors, catch Throwable to ensure any
           // other edge cases are also caught
           
URL url;
           if
(securityManager != null) {
               PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
               
url = AccessController.doPrivileged(dp);
           
} else {
               url = javaseLoader.getResource(resourceName);
           
}
           tryLoadingFromJavaseLoader = (url != null);
       
} catch (Throwable t) {
           // Swallow all exceptions apart from those that must be re-thrown
           
ExceptionUtils.handleThrowable(t);
           
// The getResource() trick won't work for this class. We have to
           // try loading it directly and accept that we might get a
           // ClassNotFoundException.
           
tryLoadingFromJavaseLoader = true;
       
}

       if (tryLoadingFromJavaseLoader) {
           try {
               clazz = javaseLoader.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, true);

       
// (1) Delegate to our parent if requested
       
if (delegateLoad) {
           if (log.isDebugEnabled())
               log.debug("  Delegating to parent classloader1 " + parent);
           try
{
               clazz = Class.forName(name, false, parent);
               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);
           try
{
               clazz = Class.forName(name, false, parent);
               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);
}

總結

以上就是Tomcat類加載相關源代碼的解析過程,在實際的開發中咱們能夠利用Tomcat類加載的機制優化咱們的項目部署。好比:在多個子應用包一塊兒部署的時,咱們能夠把這些應用中公共的依賴包放入shared.loader配置的加載路徑下從而減小類加載器重複加載依賴包,優化類加載性能;咱們也能夠將一些全局性較強且安全驗證、受權等相關組件包放入server.loader加載路徑下,它們在多個應用中起做用但對於應用開發人員不須要關注此類包的細節,從而起到安全隔離和應用之間解耦的效果。更多Tomcat源碼的技術點分享,請繼續關注!

相關文章
相關標籤/搜索