日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制

發布時間:2023/12/3 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載自?違反ClassLoader雙親委派機制三部曲第二部——Tomcat類加載機制

前言:
本文是基于 ClassLoader雙親委派機制源碼分析 了解過正統JDK類加載機制及其實現原理的基礎上,進而分析這種思想如何應用到Tomcat這個web容器中,從源碼的角度對 違反ClassLoader雙親委派機制三部曲之首部——JDBC驅動加載 中提出的Tomcat是如何完成多個web應用之間相互隔離,又如何保證多個web應用都能加載到基礎類庫的問題進行了解答,我們按如下的思路布局整篇文章:

  • 先給出Tomcat整體的類加載體系結構
  • 通過查看源碼驗證該類加載體系的正確性
  • 總結Tomcat如何設計保證多應用隔離
    另外本文是基于Tomcat7的源碼進行分析的,因此讀者最好先搭建一套基于Tomcat7的環境,以便查閱源碼以及運行調試,可以按照該文章的方式進行搭建:Tomcat源碼導入Idea

Tomcat類加載體系結構

圖1. Tomcat整體類加載體系結構

Tomcat本身也是一個java項目,因此其也需要被JDK的類加載機制加載,也就必然存在引導類加載器、擴展類加載器和應用(系統)類加載器。Tomcat自身定義的類加載器主要由圖中下半部分組成,Common ClassLoader作為Catalina ClassLoader和Shared ClassLoader的parent,而Shared ClassLoader又可能存在多個children類加載器WebApp ClassLoader,一個WebApp ClassLoader實際上就對應一個Web應用,那Web應用就有可能存在Jsp頁面,這些Jsp頁面最終會轉成class類被加載,因此也需要一個Jsp的類加載器,就是圖中的JasperLoder
需要注意的是,在代碼層面Catalina ClassLoader、Shared ClassLoader、Common ClassLoader對應的實體類實際上都是URLClassLoader或者SecureClassLoader,一般我們只是根據加載內容的不同和加載父子順序的關系,在邏輯上劃分為這三個類加載器;而WebApp ClassLoader和JasperLoader都是存在對應的類加載器類的
下面我們從源碼設計的角度驗證圖中類加載器的設計

源碼分析Tomcat類加載機制

Tomcat的啟動入口在Bootstrap.class中

?

圖2. Tomcat啟動入口

其中初始化類加載器的流程在bootstrap.init();中,如下“代碼清單1

public void init()throws Exception{// Set Catalina pathsetCatalinaHome();setCatalinaBase();// (1) 初始化 classLoaderinitClassLoaders();Thread.currentThread().setContextClassLoader(catalinaLoader);SecurityClassLoad.securityClassLoad(catalinaLoader);// Load our startup class and call its process() methodif (log.isDebugEnabled())log.debug("Loading startup class");//加載 org.apache.catalina.startup.Catalina classClass<?> startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");// (2) 實例化 Catalina 實例Object startupInstance = startupClass.newInstance();// Set the shared extensions class loaderif (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;}

(1)處注釋的代碼主要進行類加載的初始化以及形成類加載器的關系初始化,繼續跟進

圖3. initClassLoaders()方法

這里紅線處的代碼實際上創建了三個ClassLoader對象,其名稱和Tomcat類加載關系圖中的類加載器高度一致,那么我們猜測createClassLoader(String,ClassLoader)方法可能就是創建Tomcat自定義類加載器的方法之一,繼續往下看 “ 代碼清單2

private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {// (1) 根據名稱查找特定的配置String value = CatalinaProperties.getProperty(name + ".loader");if ((value == null) || (value.equals("")))return parent;value = replace(value);List<Repository> repositories = new ArrayList<Repository>();StringTokenizer tokenizer = new StringTokenizer(value, ",");while (tokenizer.hasMoreElements()) {String repository = tokenizer.nextToken().trim();if (repository.length() == 0) {continue;}// Check for a JAR URL repositorytry {@SuppressWarnings("unused")URL url = new URL(repository);repositories.add(new Repository(repository, RepositoryType.URL));continue;} catch (MalformedURLException e) {// Ignore}// Local repositoryif (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));}}// (2) 類加載器工廠創建特定類加載器return ClassLoaderFactory.createClassLoader(repositories, parent);}

代碼清單中(1)處注釋是根據上圖中傳遞的“名稱”加上后綴.loader去某個配置文件加載文件,為了突出重點,這里直接給出結論,其加載的內容為/org/apache/catalina/startup/catalina.properties,比如要加載 common.loader對應的value,其在文件中的值為${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,也就是說Common ClassLoader要加載的路徑是這些,是Tomcat運行要使用的公共組件,比如servlet-api.jar、catalina.jar等;而我們發現當要加載server.loader和shared.loader時,其key在配置文件中的value為空,也就是說,默認情況下Catalina ClassLoader和Shared ClassLoader(Tomcat整體類加載體系結構圖中紅色虛線內)都不存在,只有Common ClassLoader
方法中的第二個參數表示創建類加載器的父類加載器是哪個,再看initClassLoaders()方法圖中代碼,在創建catalinaLoader和sharedLoader時,父類加載器傳入的實際上就是commonLoader,以此可以驗證圖1中Catalina ClassLoader、Shared ClassLoader和Common ClassLoader的父子關系。而common ClassLoader的父類加載器參數傳遞的為null,為什么null就會導致該類加載器的父類加載器為System ClassLoader呢?我們需要進入代碼清單2中看注釋(2)處標識的代碼 代碼清單3

public static ClassLoader createClassLoader(List<Repository> repositories,final ClassLoader parent)throws Exception {if (log.isDebugEnabled())log.debug("Creating new class loader");// Construct the "class path" for this class loaderSet<URL> set = new LinkedHashSet<URL>();// 加載指定路徑下的資源對象if (repositories != null) {for (Repository repository : repositories) {if (repository.getType() == RepositoryType.URL) {URL url = buildClassLoaderUrl(repository.getLocation());if (log.isDebugEnabled())log.debug(" Including URL " + url);set.add(url);} else if (repository.getType() == RepositoryType.DIR) {File directory = new File(repository.getLocation());directory = directory.getCanonicalFile();if (!validateFile(directory, RepositoryType.DIR)) {continue;}URL url = buildClassLoaderUrl(directory);if (log.isDebugEnabled())log.debug(" Including directory " + url);set.add(url);} else if (repository.getType() == RepositoryType.JAR) {File file=new File(repository.getLocation());file = file.getCanonicalFile();if (!validateFile(file, RepositoryType.JAR)) {continue;}URL url = buildClassLoaderUrl(file);if (log.isDebugEnabled())log.debug(" Including jar file " + url);set.add(url);} else if (repository.getType() == RepositoryType.GLOB) {File directory=new File(repository.getLocation());directory = directory.getCanonicalFile();if (!validateFile(directory, RepositoryType.GLOB)) {continue;}if (log.isDebugEnabled())log.debug(" Including directory glob "+ directory.getAbsolutePath());String filenames[] = directory.list();if (filenames == null) {continue;}for (int j = 0; j < filenames.length; j++) {String filename = filenames[j].toLowerCase(Locale.ENGLISH);if (!filename.endsWith(".jar"))continue;File file = new File(directory, filenames[j]);file = file.getCanonicalFile();if (!validateFile(file, RepositoryType.JAR)) {continue;}if (log.isDebugEnabled())log.debug(" Including glob jar file "+ file.getAbsolutePath());URL url = buildClassLoaderUrl(file);set.add(url);}}}}// Construct the class loader itselffinal URL[] array = set.toArray(new URL[set.size()]);if (log.isDebugEnabled())for (int i = 0; i < array.length; i++) {log.debug(" location " + i + " is " + array[i]);}// 返回創建的類加載器return AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() {@Overridepublic URLClassLoader run() {if (parent == null)return new URLClassLoader(array);elsereturn new URLClassLoader(array, parent);}});}

大塊的if中的代碼實際上是對資源進行轉化加載的過程,而return部分才是返回類加載器的部分,代碼根據是否有parent調用了URLClassLoader不同的構造器,Common ClassLoader調用的是沒有parent的構造器

圖4. Common ClassLoader的parent創建的底層關鍵代碼

按紅線所畫Common ClassLoader的parent實際上是JDK中sun.misc.Launcher.class類的loader成員變量,而在上一篇文章中已經知道該loader的值就是應用類加載器(系統類加載器)System ClassLoader。至此Tomcat中類加載機制和JDK的類加載機制也建立上了聯系
現在Tomcat的類加載機制已完成了一大半,剩下用于加載每個web應用的類加載器WebApp ClassLoader的分析,這個時候需要重新回到代碼清單1中看注釋(2)以下的部分,其主要做的事情是通過反射創建了org.apache.catalina.startup.Catalina類的實例,然后調用了簽名為void setParentClassLoader(ClassLoader parentClassLoader)的方法,并傳入了Shared ClassLoader,上面我們說過默認情況下Shared ClassLoader就是Common ClassLoader,因此其傳入的參數實際上是Common ClassLoader
我們思考既然有保存parent的方法,必定使用時會調用獲得parent方法,那么我們需要查看Catalina類中ClassLoader getParentClassLoader()方法的調用棧(層級關系比較復雜,要緊跟主線不要迷失),最終定位到StandardContext中的synchronized void startInternal() throws LifecycleException方法(其中涉及到Tomcat的各種組件關系,生命周期管理等內容,將在下次分析Tomcat組件文章中詳細介紹),下面是只保留核心邏輯的startInternal()方法 代碼清單4

protected synchronized void startInternal() throws LifecycleException {// 其他邏輯略......// Add missing components as necessaryif (webappResources == null) { // (1) Required by Loaderif (log.isDebugEnabled())log.debug("Configuring default Resources");try {String docBase = getDocBase();if (docBase == null) {setResources(new EmptyDirContext());} else if (docBase.endsWith(".war")&& !(new File(getBasePath())).isDirectory()) {setResources(new WARDirContext());} else {setResources(new FileDirContext());}} catch (IllegalArgumentException e) {log.error(sm.getString("standardContext.resourcesInit"), e);ok = false;}}if (ok) {if (!resourcesStart()) {throw new LifecycleException("Error in resourceStart()");}}// (1) 為每一個web應用創建一個WebappLoaderif (getLoader() == null) {WebappLoader webappLoader = new WebappLoader(getParentClassLoader());webappLoader.setDelegate(getDelegate());setLoader(webappLoader);}// 其他邏輯略......try {if (ok) {// (2) 調用WebappLoader的start// Start our subordinate components, if anyif ((loader != null) && (loader instanceof Lifecycle))((Lifecycle) loader).start();}// 其他邏輯省略......} finally {// Unbinding threadunbindThread(oldCCL);}}

(1)處注釋下的代碼邏輯就是為每一個web應用創建一個類加載器,該類加載器的父類加載器就是通過getParentClassLoader()得到的Shared ClassLoader(Common ClassLoader),(2)處代碼調用了WebappLoader的start方法,繼續跟進

protected void startInternal() throws LifecycleException {// 其他邏輯省略.....try {//創建類加載器關鍵方法classLoader = createClassLoader();classLoader.setResources(container.getResources());classLoader.setDelegate(this.delegate);classLoader.setSearchExternalFirst(searchExternalFirst);if (container instanceof StandardContext) {classLoader.setAntiJARLocking(((StandardContext) container).getAntiJARLocking());classLoader.setClearReferencesRmiTargets(((StandardContext) container).getClearReferencesRmiTargets());classLoader.setClearReferencesStatic(((StandardContext) container).getClearReferencesStatic());classLoader.setClearReferencesStopThreads(((StandardContext) container).getClearReferencesStopThreads());classLoader.setClearReferencesStopTimerThreads(((StandardContext) container).getClearReferencesStopTimerThreads());classLoader.setClearReferencesHttpClientKeepAliveThread(((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());}// 其他邏輯省略.....}

由于Tomcat的設計,WebappLoader的start方法實際上調用的是父類的模板,而模板中的startinternal方法由各個子類具體實現,其中最關鍵的方法為createClassLoader()

圖5. WebappLoader中createClassLoader方法

上圖中的loadClass成員變量的值為org.apache.catalina.loader.WebappClassLoader,所以,實際上該類為每一個web應用創建了一個WebappClassLoader的實例,該實例的parent就是Shared ClassLoader或者Common ClassLoader,至此WebApp ClassLoader在圖1中的位置也得以驗證。
從理論上分析來看,由于類加載的“雙親委派”機制,一個類加載器只能加載本加載器指定的目錄以及使用有“繼承”關系的父類加載器加載過的類,而Tomcat為每一個Web應用創建了一個WebappClassLoader,不同的WebappClassLoader是同級關系,不會存在交叉訪問的問題,從而達到web應用相互隔離的目的。
那Tomcat是否沒有"破壞"雙親委派機制呢?我們通過查看WebappClassLoader及其父類WebappClassLoaderBase的loadClass()和findClass()分析一下Tomcat加載web應用相關類的策略

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLockInternal(name)) {if (log.isDebugEnabled())log.debug("loadClass(" + name + ", " + resolve + ")");Class<?> clazz = null;// Log access to stopped classloaderif (!started) {try {throw new IllegalStateException();} catch (IllegalStateException e) {log.info(sm.getString("webappClassLoader.stopped", name), e);}}// (1) // Check our previously loaded local class cacheclazz = findLoadedClass0(name);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Returning class from cache");if (resolve)resolveClass(clazz);return (clazz);}// (2)// Check our previously loaded class cacheclazz = findLoadedClass(name);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Returning class from cache");if (resolve)resolveClass(clazz);return (clazz);}// (3)// Try loading the class with the system class loader, to prevent// the webapp from overriding J2SE classestry {clazz = j2seClassLoader.loadClass(name);if (clazz != null) {if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {// Ignore}// Permission to access this class when using a SecurityManagerif (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;if (name.endsWith("BeanInfo")) {// BZ 57906: suppress logging for calls from// java.beans.Introspector.findExplicitBeanInfo()log.debug(error, se);} else {log.info(error, se);}throw new ClassNotFoundException(error, se);}}}// (4)boolean delegateLoad = delegate || filter(name);// (5)// Delegate to our parent if requestedif (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}}// (6)// Search local repositoriesif (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}// Delegate to parent unconditionallyif (!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);}

我們首先定位到WebappClassLoaderBase的loadClass方法,(1)處首先看name對應的類是否存在緩存中,緩存是一個ConcurrentHashMap<String, ResourceEntry>的集合,如果沒有緩存執行(2)處邏輯,從JVM中查找是否曾今加載過該類,(3)中的代碼保證自定義類不會覆蓋java基礎類庫中的類,(4)的邏輯就是是否進行雙親委派的分叉口,其中delegate默認為false,那么就要看filter(String)方法,該方法的內部實際上將待加載類的全路徑名稱和一個成員變量protected static final String[] packageTriggers中的類名進行比較,如果待加載的類名和packageTriggers數組中的內容前綴匹配,則需要委派父類加載,即執行(5)處代碼,否則執行(6),調用重寫的findClass(String)方法加載該類

public Class<?> findClass(String name) throws ClassNotFoundException {// 其他代碼略去.....// Ask our superclass to locate this class, if possible// (throws ClassNotFoundException if it is not found)Class<?> clazz = null;try {if (log.isTraceEnabled())log.trace(" findClassInternal(" + name + ")");// (1)if (hasExternalRepositories && searchExternalFirst) {try {clazz = super.findClass(name);} catch(ClassNotFoundException cnfe) {// Ignore - will search internal repositories next} catch(AccessControlException ace) {log.warn("WebappClassLoaderBase.findClassInternal(" + name+ ") security exception: " + ace.getMessage(), ace);throw new ClassNotFoundException(name, ace);} catch (RuntimeException e) {if (log.isTraceEnabled())log.trace(" -->RuntimeException Rethrown", e);throw e;}}// (2)if ((clazz == null)) {try {clazz = findClassInternal(name);} catch(ClassNotFoundException cnfe) {if (!hasExternalRepositories || searchExternalFirst) {throw cnfe;}} catch(AccessControlException ace) {log.warn("WebappClassLoaderBase.findClassInternal(" + name+ ") security exception: " + ace.getMessage(), ace);throw new ClassNotFoundException(name, ace);} catch (RuntimeException e) {if (log.isTraceEnabled())log.trace(" -->RuntimeException Rethrown", e);throw e;}}//其他代碼略去........return (clazz);}

(1)處由于hasExternalRepositories和searchExternalFirst默認為false,因此執行(2)處邏輯,調用findClassInternal(String)方法

圖6. WebappClassLoader類的findClassInternal方法

其主要的思想是根據待加載類的全路徑讀取該類的二進制數據,進而進行類的預定義、class source的解析等,將該類加載到JVM中
綜上所述,我認為Tomcat的類加載機制不能算完全“正統”的雙親委派,WebappClassLoader內部重寫了loadClass和findClass方法,實現了繞過“雙親委派”直接加載web應用內部的資源,當然可以通過在Context.xml文件中加上<Loader delegate = "true">開啟正統的“雙親委派”加載機制

?

總結

以上是生活随笔為你收集整理的违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 亚洲精品视频中文字幕 | 成人77777 | av第一福利大全导航 | 日本不卡在线播放 | 欧美一级成人 | 免费观看av | 国产99视频在线 | 国产成人自拍视频在线观看 | 能免费看av的网站 | 国产精品久久久久久精 | wwwxxx日本免费| 91精品视频观看 | 韩国三级hd中文字幕的背景音乐 | 男人操女人的视频 | 精品国产成人av | 高清视频在线播放 | 色视频网站在线观看 | 亚洲欧美另类在线视频 | 青青自拍视频 | 国产精品视频在线免费观看 | 在线观看黄色动漫 | 欧美zzz物交| 色午夜av| 天天插天天搞 | xxxx96| 香蕉久久一区二区三区 | 国产乱码一区二区三区 | 性www| 大屁股一区二区三区 | 国产三级网站 | 国产精品91久久 | 久久久久久亚洲av毛片大全 | 欧美精品一区二区三区在线播放 | 制服一区| 国产毛片久久久久久国产毛片 | 久久艳片www.17c.com | 亚洲女同二女同志 | 四虎影院黄色 | 国产丝袜在线播放 | 日本91在线 | 色屁屁草草影院ccyycom | 少妇高潮一区二区三区四区 | 在线亚洲网站 | 日本一区二区在线 | 北条麻妃一区二区三区免费 | 国产精品99精品久久免费 | 91视频论坛| 一道本一区二区 | 91高清无打码 | 欧美日韩成人在线播放 | 长篇h版少妇沉沦交换 | 日日爱夜夜操 | 亚洲一区二区视频网站 | 日韩国产综合 | 日韩色图在线观看 | 亚洲va中文字幕 | 成人涩涩网站 | 日本老妇高潮乱hd | 久久亚洲精品视频 | 亚洲乱熟女一区二区三区小说 | 中文在线а√天堂 | 天堂网在线资源 | 天堂av片| 欧美视频不卡 | sm调教羞耻姿势图片 | 97在线播放免费观看 | 末发成年娇小性xxxxx | 国产片免费 | 精品国产大片大片大片 | 欧美视频xxxx | 国产美女精品 | 特级黄色录像 | 伊人快播| 偷拍欧美亚洲 | 亚洲一级免费视频 | 日韩欧美一级大片 | 日韩免费视频一区 | 欧美wwwwww | 日韩欧美精品免费 | 波多野结衣中文一区 | aaa一级黄色片 | 久久9999久久免费精品国产 | 亚洲区av| 黄污视频在线观看 | www成年人 | 91视频国产精品 | 国产黄色片免费看 | 97视频免费看 | 手机看片在线观看 | 嫩草影院黄色 | 中文天堂网 | 亚洲精品一区二区三区四区乱码 | 人妻精品久久久久中文字幕69 | 中文字幕33页 | 人人爽爽人人 | 成人福利网站在线观看 | 久久中文字幕人妻 | 催眠调教后宫乱淫校园 | 精品无码av一区二区三区不卡 |