Jetty 類載入問題處理

前幾日使用 Jetty (9.2)部署公司一個 web 項目,這個項目原本部署在 Tomcat server上,一切正常,可是部署到 Jetty 后,啟動報錯.關鍵錯誤信息為"java.lang.NoClassDefFoundError: Could not initialize class org.apache.tomcat.jdbc.pool.DataSource"


項目使用了 Tomcat jdbc connection pool 當中有兩個 jar 包 tomcat-jdbc.jar 和 tomcat-juli.jar, 后者是前者的 maven 依賴,后者也是 tomcat 的日志抽象層,tomcat server自帶這個 jar 在 tomcat_home 的 bin 文件夾下.依據異常信息推斷錯誤是因為載入和初始化org.apache.tomcat.jdbc.pool.DataSource導致的,為了更easy分析問題,我創建了一個簡單的 web 項目僅僅依賴于這兩個 jar 包.部署到同一個 Jetty server中,報錯"java.util.ServiceConfigurationError: org.apache.juli.logging.Log: Provider org.eclipse.jetty.apache.jsp.JuliLog not a subtype"


查看源碼org.eclipse.jetty.apache.jsp.JuliLog非常明白是org.apache.juli.logging.Log的子類,但為什么會報這種錯誤呢,結合之前的java.lang.NoClassDefFoundErrorjava.util.ServiceConfigurationError能夠確定問題是因為類載入引起的,依據對類載入的了解,同一個類被不同的類載入器實例載入得到的 Class 對象是不同的.所以我判斷可能是因為 Jetty server使用了不同的類載入器實例載入了兩個累,導致繼承關系不存在了.查看java.util.ServiceConfigurationError相關的API文檔發現,這個 Error 是在 ServiceLoader載入 Service Provider 時發生的.查看 ServiceLoader 的源碼發現這樣一段

        private S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn = nextName;nextName = null;Class<?

> c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype");//我遇到的錯誤信息剛好是這里. } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }

為了驗證我的猜測,參考這段代碼我寫了個小程序來測試.主要代碼例如以下

public class Main {public static void main(String[] args) throws ClassNotFoundException {final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();final CustomClassLoader customClassLoader= new CustomClassLoader("target/classes", "自己定義載入器", systemClassLoader);//第一個參數的路徑是類的編譯路徑//使用系統類載入器載入 Child//由于 Child 依賴 Parent 所以系統類載入器會自己主動載入 Parent,這個行為與 jetty 的 WebAppClassCloader 相同Class<?> child = Class.forName("Child", true, systemClassLoader);//由于之前載入過,不會反復載入直接返回 Parent 類實例Class<?> parent = Class.forName("Child", true, systemClassLoader);//使用自己定義載入器載入 Child//自己定義載入器會優先嘗試自己載入,失敗后使用父載入器Class<?

> customChild = Class.forName("Child", true, customClassLoader); Class<?

> customParent = Class.forName("Parent", true, customClassLoader);//相同不會反復載入 //測試 //相同使用系統載入器載入的兩個類,繼承關系正常 System.out.println("parent.isAssignableFrom(child) = " + parent.isAssignableFrom(child));//true //使用自己定義載入器載入的 Child 卻不是系統類載入器載入的 Parent 的子類 System.out.println("parent.isAssignableFrom(customChild) = " + parent.isAssignableFrom(customChild));//false //相同使用自己定義載入器載入的兩個類,集成關系正常 System.out.println("customParent.isAssignableFrom(customChild) = " + customParent.isAssignableFrom(customChild));//true } }


這段代碼中 Child 是 Parent 的子類.這個簡單的測試驗證了我的猜測.查看文檔發現 Jettty 在9.2版本號中的 jsp 引擎使用的是 tomcat 的.在 Jetty 的 lib 里面能夠發現例如以下jar

org.eclipse.jetty.apache-jsp-9.2.3.v20140905.jar
org.eclipse.jetty.orbit.org.eclipse.jdt.core-3.8.2.v20130121.jar
org.mortbay.jasper.apache-el-8.0.9.M3.jar
org.mortbay.jasper.apache-jsp-8.0.9.M3.jar

org.mortbay開頭的兩個 jar 里面是 apache 的 jsp 實現類當中包括org.apache.juli.logging這個包,org.eclipse.jetty.apache-jsp-9.2.3.v20140905.jar這個 jar 中提供了 logging 的詳細實現,終于通過 ServiceLoader 載入.問題就出在這里,由于我的項目中有 tomcat-juli.jar 當中也包括org.apache.juli.logging這個包.

查看了一下 Jetty 的文檔中有關類載入的內容,發現 Jetty 對每一個部署的 web 應用使用單獨的WebAppClassLoader實例進行類載入.通常實現自己定義類載入器的時候會優先托付給父載入器(一般為系統類載入器,能夠通過 ClassLoader.getSystemClassLoader() 得到),然后再嘗試自己載入類.但 Jetty 的這個 WebAppClassLoader 正相反,除了對于系統類和server類(什么是系統類和server類能夠查看文檔),會優先嘗試自己載入,然后才托付父載入器.

依據這個行為基本能夠確認了,server載入 jsp 引擎是會使用自己的類載入器載入server lib 中上述的類(org.apache.juli.logging.Log及事實上現org.eclipse.jetty.apache.jsp.JuliLog),應用部署時會使用WebAppClassLoader載入應用 lib 中的org.apache.juli.logging.Log.載入過程是這種, WebAppClassLoader實例載入org.apache.tomcat.jdbc.pool.DataSource,其依賴org.apache.juli.logging.Log,優先嘗試自己載入,所以會從應用的 lib 中載入到這個類,而嘗試載入事實上現的時候發現應用 lib 中沒有,再托付給父類載入器,也就是 Jetty server的載入器,成功載入到org.eclipse.jetty.apache.jsp.JuliLog,這樣就是使用兩個不同的載入器實例載入了子類和父類,依據之前的測試結果,兩個類之間的繼承關系是不成立的.所以導致發生錯誤.


清楚了問題的解決辦法,怎么解決呢?

方案一,在 maven 配置中將 tomcat-juli 的依賴 scope 改為 provided,Jetty server已經提供了. 這樣在WebAppClassLoader 嘗試自己載入org.eclipse.jetty.apache.jsp.JuliLog時會失敗,進而托付父類載入器,這樣org.apache.juli.logging.Log及事實上現org.eclipse.jetty.apache.jsp.JuliLog兩個類就是同一個載入器載入了.

方案二,更改 WebAppClassLoader 的父類載入器的優先級,使其優先使用父類載入器.詳細配置方式能夠參考文檔.目標是調用?setParentLoaderPriority(true)


使用這兩個方案,相同能夠解決原始項目中的問題,可是為什么測試用的簡單 web 項目和原始項目的錯誤信息不同呢?

回到原始項目的錯誤信息發現,"java.lang.NoClassDefFoundError: Could not initialize class org.apache.tomcat.jdbc.pool.DataSource"這個錯誤是因為在載入類的時候無法初始化,那么看org.apache.tomcat.jdbc.pool.DataSource中,在類載入后要做的初始化操作有什么,通過查看源代碼發現private static final Log log = LogFactory.getLog(DataSource.class);僅僅有這句代碼是須要在類載入后進行的初始化,跟蹤這個語句發現終于會進入上文中提到的nextService方法,所以根源錯誤依舊是上面描寫敘述的.

至此問題得到比較圓滿的解釋和解決.


總結:

本文涉及到的知識點

1.Java虛擬機的類載入機制

2.JavaServiceProvider 載入機制

3.Java 類的初始化過程

4.Jetty server的配置方式


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/281743.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/281743.shtml
英文地址,請注明出處:http://en.pswp.cn/news/281743.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

2.3 萬 Star,Nginx 可視化配置工具

你好&#xff0c;這里是 Dotnet 工具箱&#xff0c;定期分享 Dotnet 有趣&#xff0c;實用的工具或組件&#xff0c;希望對您有用&#xff01;對于前后端開發工程師來說&#xff0c; Nginx 是必須掌握的工具&#xff0c;因為它不僅僅是一個 Web Server&#xff0c;還包含了其他…

城市智慧停車系統方案的產品設計體系介紹

最近幾年隨著大數據技術快速發展與應用&#xff0c;智慧城市隨即被正式提出。而且&#xff0c;我們也可以深刻感受到“智慧”正在慢慢改變我們的生活方式和城市。要讓城市變智慧的地方太多太多&#xff0c;當前我們接觸做多的可能就是外出停車&#xff0c;比如很多商場的停車系…

vue.js:利用vue.js做一個抽獎小游戲

MVVM模式是什么&#xff1a;MModel(模型)&#xff0c;VView&#xff08;視圖&#xff09;,VM ViewModel(簡寫成MVVM) . 代碼如下&#xff1a; 運行代碼結果&#xff1a; 1.你沒有中獎&#xff1a; 2.恭喜你&#xff0c;你中獎了&#xff1a; 轉載于:https://www.cnblogs.com/ya…

滾動加載數據 php,無刷新動態加載數據 滾動條加載適合評論等頁面

滾屏加載更多數據,適合評論等頁面本例的數據庫很簡單&#xff0c;一看就明了復制代碼 代碼如下:$querymysql_query("select * from content order by id desc limit 0,10");while ($rowmysql_fetch_array($query)) {?>js文件復制代碼 代碼如下:$(function(){var …

Java之品優購課程講義_day20(5)

資源過濾與變量替換 修改 pom.xml &#xff0c;在 build 節點中添加如下配置 <filters><filter>src/main/resources/filters/db_${env}.properties</filter></filters><resources><resource><directory>src/main/resources</dir…

國際主流固件接口組織UEFI全面支持LoongArch,龍架構已完成上游TianoCore EDK2代碼合并...

2022年9月初&#xff0c;UEFI官方組織在發布的UEFI specification V2.10規范中全面支持了LoongArch64架構以及部分LoongArch32架構。近期&#xff0c;龍芯團隊又完成了LoongArch基礎代碼與UEFI上游TianoCore EDK2的合并&#xff0c;LoongArch進入TianoCore EDK2主分支&#xff…

Invalidate和postInvalidate

為什么80%的碼農都做不了架構師&#xff1f;>>> Android提供了Invalidate方法實現界面刷新&#xff0c;但是Invalidate不能直接在線程中調用&#xff0c;因為他是違背了單線程模型&#xff1a;android UI操作并不是線程安全的&#xff0c;并且這些操作必須在UI線程…

java比c好逆向,吐槽一下java的效率。。。比起C差的真的好遠。。。

該樓層疑似違規已被系統折疊 隱藏此樓查看此樓刷計算機編程題目&#xff0c;USACO某道題&#xff0c;因為最近想用java&#xff0c;就寫了一個&#xff0c;各種查錯優化之后總算通過了&#xff1a;TASK: camelotLANG: JAVACompiling...Compile: OKExecuting...Test 1: TEST OK …

.Net輕松處理億級數據--clickhouse及可視化界面安裝介紹

前言我是在17年就聽說過Clickhouse,那時還未接觸過億數據的運算&#xff0c;那時我在的小公司對于千萬數據的解決方案還停留在分庫分表&#xff0c;最好的也是使用mycat做的集群。這些解決方案都比較復雜&#xff0c;畢竟通常來說那些需要大量存儲的數據基本都是像日志&#xf…

[USACO 4.2] 完美的牛欄

★★☆ 輸入文件&#xff1a;stall4.in 輸出文件&#xff1a;stall4.out 簡單對比 時間限制&#xff1a;1 s 內存限制&#xff1a;128 MB USACO/stall4(譯by Felicia Crazy)描述 農夫約翰上個星期剛剛建好了他的新牛棚&#xff0c;他使用了最新的擠奶技術。不幸的是&am…

003Java語言環境搭建

JRE,JDK JRE(Java Runtime Environment java運行環境)&#xff1a;包括java虛擬機和java程序所需要的核心類庫&#xff0c; 如果要運行一個開發好的java程序&#xff0c;計算機中只需要安裝一個JRE JDK&#xff08;Java Development Kit Java開發工具包&#xff09; JDK是提供給…

php 編寫mysql,自己寫的MySQL類

自己寫的MySQL類---------- php debug ----------Serverlocalhost;DataBasemysql;UserIDroot;PassWord123456resource(5) of type (mysql result)Output completed (1 sec consumed) - Normal Terminationclass DBCLS{//debug 調試開關var $debug true;//debuginfo 錯誤信息&a…

NET CORE讀取Excel.xlsx單元格內的圖片,并關聯當前業務ID推送圖片到指定服務器...

NET CORE讀取Excel.xlsx單元格圖片的場景&#xff0c;一般是批量導入業務數據&#xff0c;例如&#xff1a;藥品的圖片&#xff0c;醫師資格證&#xff0c;商品上架、商家營業資質、水果信息、用戶頭像等等這里我截個圖&#xff0c;圖文并茂更好理解特別聲明&#xff1a;粘貼圖…

CSS或HTML如何實現文字下面加點?

就像word里文字加著重號一樣&#xff0c;在字的下面加一個點&#xff0c;用CSS怎么做&#xff1f;注意&#xff0c;我說的是下面加點&#xff0c;不是文字加粗或傾斜&#xff0c;請不要回答<strong>或<em>之類的。 把要著重加點的文字用<span></span>…

數據庫常見錯誤

錯誤&#xff1a; You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 12123123123.0123.0) at line 1 解決辦法&#xff1a; 檢查對應到您的MySQL服務器版本附近使用正確的語法手冊 數…

RocketMQ 5.0 大手筆,擁抱云原生,支持流處理,高可用架構升級!

大家好&#xff0c;我是君哥。RocketMQ 5.0 已經發布一段時間了&#xff0c;今天來分享一下 RocketMQ 5.0 有哪些新特性。1 架構變化RocketMQ 5.0 架構上的變化主要是為了更好的走向云原生。RocketMQ 4.x 架構如下&#xff1a;Broker 向 Name Server 注冊 Topic 路由信息&#…

php驗證碼顯示亂碼,如何解決php驗證碼亂碼問題

php驗證碼亂碼的解決辦法&#xff1a;1、修改訪問驗證碼生成方法函數的路徑&#xff1b;2、修改文件編碼&#xff0c;并去掉BOM頭&#xff1b;3、檢查驗證碼生成方法&#xff1b;4、修改服務環境。具體問題&#xff1a;php驗證碼輸出全是亂碼...<?php session_start();head…

中國HBase技術社區第五屆MeetUp ——HBase技術解析及應用實踐(深圳站)

HBase—Hadoop Database是一個分布式的、面向列的開源數據庫&#xff0c;該技術來源于 Fay Chang 所撰寫的Google論文“Bigtable&#xff1a;一個結構化數據的分布式存儲系統”。HBase的特點是高可靠性、高性能、面向列、可伸縮的分布式存儲系統&#xff0c;如今HBase已經廣泛應…

如何查找Power BI本地報表服務器產品密鑰

Power BI 報表服務器產品密鑰&#xff0c;以便在生產環境中安裝服務器。 已下載 Power BI 報表服務器&#xff0c;并已購買 SQL Server Enterprise 軟件保障協議。 或者&#xff0c;已購買 Power BI Premium。 希望在生產環境中安裝服務器&#xff0c;但需要產品密鑰才能進行安…

【.NET番外篇】Rust環境搭建+基礎開發入門+Rust與.NET6、C++的基礎運算性能比較

前言&#xff1a;突然想打算把Rust作為將來自己主要的副編程語言。當然&#xff0c;主語言還是C#&#xff0c;畢竟.NET平臺這么強大&#xff0c;寫起來就是爽。緣起&#xff1a;之前打算一些新的產品或者新的要開發的東西&#xff0c;由于沒有歷史包袱&#xff0c;就想重新選型…