摘要
坊間傳聞java web開發人員寫了那麼多代碼,但是其實一半代碼都在處理NPE。總是在加班,卻大部分時間都在處理包衝突,類加載不了的bug。這些問題總是讓新老程式設計師都很抓狂,有很多的工具可以輔助我們解決這些問題(maven helper插件,arthas等)但是有沒有一些原則可以遵循,在源頭上避免這些問題的發生呢。
問題
經常遇到的問題有ClassNotFoundException通過Class.forName()或者loadClass()方法加載類時,當classpath中又找不到這個類,就會拋這個錯誤。
這個錯誤一般比較好排查,編譯程序時就拋出來了。然後引入對應的jar包,或者刷新classpath就可以解決NoClassDefFoundError類在編譯的時候存在,但是運行的時候不存在。
NoSuchMethodError找不到對應的方法,運行時才會拋錯,這個錯誤在日常開發經常遇到,線上諸多bug都是來源於此。
發生的原因就是多個包依賴了不一樣版本的另外一個包,比如A,B都依賴了C包,A依賴C1,B依賴C2,工程中加載了C1,但是C1中某個類缺少了C2版本的這個類的某個方法,這時候運行時,B依賴的C2方法被調用到了,就會報這個錯。
既然這些問題這麼頭疼,且難以排查,還容易造成線上故障,那平時在開發過程中如何避免這些問題。
做好二方包的管理
重中之中就是做好二方包的管理,第三方包都是公開發布出來的,相對還是比較靠譜的,更多的是二方包的問題。二方包發布的頻率比較高,開發人員的水平又參差不齊,是需要嚴格把控管理的。
線上禁止snapshot包snapshot包是萬惡之源,內容隨時會被人修改,所以線上環境,是絕對不能允許snapshot包存在,否則就是一顆定時炸彈。向下兼容版本號是3位: 主版本號.次版本號.修訂號做了不兼容的更新要升級主版本號。在二方包管理時,儘量不要接口層面的方法,重命名POJO類欄位等不兼容的改動,因為二方包的發布頻次高,版本比較多,維護的代價比較高。很多包發布了幾年,還是0.xx.xx的版本。有不兼容的改動,比如需要搭配版本時候,維護的成本都會讓人奔潰。儘量少的依賴發布出來的二方包儘量少的依賴,二次依賴。對於發布出去的包小心檢查,不要引入不必要的依賴。比如有些業務repo,需要發布rpc-client給其他服務。對外的client與內部代碼通過不同module管理,那麼這時候就不要共用一份parent pom.xml。進行版本管理,因為內部工程使用的和外部不一樣。不同領域的client也可以拆分出來,單獨進行jar包版本管理。changelog發布出來的版本必須要有changelog,做了什麼改動。必須依賴什麼版本的二方包也需要寫清楚。優雅"copy"開源包對於外部開源包,有些需要包裝一下在公司裡面用。切記做好以下兩點記錄下開源版本上做了什麼更改,否則後續人維護的就是一坨屎,不要使用同一個包名,類名。後續別人用開源版本實現時,就會出現不同jar包有相同同一個Class,這時候就不是包衝突解決了,就會出現不確定的加載順序,因為同一個類只會被jvm加載一次,但是加載的是哪個jar的類,就和包依賴的路徑,文件名的順序等有關。出現不同的機器加載的類不一樣,十分難排查。使用包的原則
上面說完了發布包的原則,那麼使用包的原則呢。
1.了解maven仲裁機制
maven仲裁機制就是maven的依賴機制,按順序分別是以下三點
優先按照依賴管理若無版本聲明,則按照「短路徑優先」的原則(Maven2.0)進行仲裁,即選擇依賴樹中路徑最短的版本若路徑長度一致,則按照「第一聲明優先」的原則進行仲裁,即選擇POM中最先聲明的版本2.統一基礎包管理
因為間接依賴有很多的不確定性,所以需要將基礎版本的包統一使用原則1
<dependencyManagement>
來管理,這樣這些包的版本就不會被你無意間引入的某個二方包版本給覆蓋掉。常見的基礎包有中間件的包,日誌,util,序列化等包。
幾個bad case
case 1: 那是我剛來公司的時候,第一個功能上線就翻跟頭了,本地windows環境,測試linux環境都通過測試了。但是上預發的時候出了問題。排查了通宵也沒搞定,差點發布延期。最後問題根源就是「開源包」亂使用的原因。項目中依賴了公司改造的某個mongo client jar包,將類copy過來了,使用了相同的package,但是以不同的jar deploy。我來了以後,在這個工程中使用了開源的mongo client,然後中招了。
case2: A包是一個業務基礎包,非公共基礎包。然後B,C業務依賴A。A包有些設計不合理,一個新來的大刀闊斧的各種重構了,升了一個版本,發布了一個release包出去,完美。然後其他業務方就炸了。因為B使用了新版本,C沒有使用新版本。一個工程中引用了B,C包後,包衝突,就會發生運行時錯誤,NoSuchMethodError 或者NoClassDefFoundError。這時候業務方既不能升A包的版本,也不能降A的版本,可不就炸了嗎。