IDEA現場離線環境問題總結
因為現場Java開發的離線環境,經常會導致引入jar等各種環境問題,現將離線開發過程中遇到的環境問題進行總結。
問題1:IDEA控制台報錯,如下所示:
Process terminated
答案分析:一般情況下,該種情況是有maven的配置問題導致的。比如配置文件settings.xml中有錯誤(例如標簽不匹配,缺少開始標簽或結束標簽、縮進或者空格有問題),或者idea中的maven配置有問題,比如對應的setting或repository文件配置不正確,或者在設置離線模式的情況下設定的repository文件夾是空的等情況。
此外,如果maven配置正常,只是設定為離線模式了,但是控制台依然報該錯誤,可能的原因是項目代碼還未引入maven項目,此時的操作是取消離線模式,然后在外網機環境更新對應的依賴即可解決。
問題2:IDEA環境Maven引入報錯,如下圖1所示:

圖1
答案分析:這種報錯比較明顯就是倉庫中缺陷對應的包所導致。在此處要引入傳遞性依賴的概念。
講述傳遞性依賴的概念之前要先說明下依賴范圍。
Maven在編譯項目主代碼的時候需要使用一套classpath,假如編譯項目主代碼的時候需要用到spring-core包,該文件以依賴的方式被引入到classpath中。其次,Maven在編譯和執行測試的時候會使用另外一套classpath。測試程序Junit也以依賴的方式引入到測試使用的classpath中,不同的是這里的依賴范圍是test。最后,實際運行Maven項目的時候,又會使用一套classpath,但測試程序Junit就不需要。
依賴范圍就是用來控制依賴與這三種classpath(編譯classpath、測試classpath、運行classpath)的關系,Maven有以下幾種依賴范圍:
compile:編譯依賴范圍。如果沒有指定,就會默認使用該依賴范圍。使用此依賴范圍的Maven依賴,對於編譯、測試、運行三種classpath都有效。典型的例子是spring-core,在編譯、依賴和運行的時候都需要使用該依賴。
test:測試依賴范圍。使用此依賴范圍的Maven依賴,只對於測試classpath有效。典型的例子是Junit,它只在編譯測試代碼及運行測試的時候才需要。
provided:已提供依賴范圍。使用此依賴范圍的Maven依賴,對於編譯和測試classpath有效,但在運行時無效。典型的例子是servlet-api,編譯和測試項目的時候需要該依賴,但在運行項目的時候,由於容器已經提供,就不需要Maven重復地引入一遍。
runtime:運行時依賴范圍。使用此依賴范圍的Maven依賴,對於測試和運行classpath有效,但在編譯主代碼時無效。典型的例子是JDBC驅動實現,項目主代碼的編譯只需要JDK提供的JDBC接口,只有在執行測試或者運行項目的時候才需要實現上述接口的具體JDBC驅動。
system:系統依賴范圍。該依賴與三種classpath的關系,和provided依賴范圍完全一致。但是,使用system范圍的依賴時必須通過systemPath元素顯示地指定依賴文件的路徑。由於此類以來不是通過Maven倉庫解析的,而且往往與本機系統綁定,可能造成構建的不可移植,因此應該謹慎使用。systemPath元素可以引用環境變量,如:
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>javax.sql</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
表1 依賴范圍描述
| 依賴范圍(Scope) | 對於編譯classpath有效 | 對於測試classpath有效 | 對於運行時classpath有效 | 例子 |
|---|---|---|---|---|
| compile | Y | Y | Y | spring-core |
| test | - | Y | - | JUint |
| provided | Y | Y | - | servlet-api |
| runtime | - | Y | Y | JDBC驅動實現 |
| system | Y | Y | - | 本地的,Maven倉庫之外的類庫文件 |
考慮一個基於Spring Framework的項目,如果不使用Maven,那么在項目中就需要手動下載相關依賴。由於Spring Framework又會依賴於其他開源類庫,因此實際中往往會下載一個很大的如 spring-framework-2.5.6-with-dependencies.zip的包,這里包含了所有Spring Framework的jar包,以及所有它依賴的其他jar包。這么往往就引入了很多不必要的依賴。另一種做法是只下載spring-framework-2.5.6.zip 這樣一個包,這里不包含其他相關依賴,到實際使用的時候,再根據出錯信息,或者查詢相關文檔,加入需要的其他依賴。很顯然,這也是一件非常麻煩的事情。
Maven的傳遞性依賴機制可以很好地解決這一問題。假如一個項目有一個org.springframework:spring-core:2.5.6的依賴,而實際上spring-core也有自己的依賴,我們可以直接訪問位於中央倉庫的該構件的POM:https://repo1.maven.org/maven2/org/springframework/spring-core/2.5.6/spring-core-2.5.6.pom。該文件包含了一個commons-logging依賴,見代碼清單1。
代碼清單1 spring-core的commons-logging依賴
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
該依賴沒有聲明依賴范圍,那么其依賴范圍就是默認的compile。例如項目TestProgram有一個compile范圍的spring-core依賴,spring-core有一個compile范圍的commons-logging依賴,那么commons-logging就會成為項目TestProgram的compile范圍依賴,commons-logging是項目TestProgram的一個傳遞性依賴,如下圖2所示:

圖2 傳遞性依賴
有了傳遞性依賴機制,在使用Spring Framework的時候就不用去考慮它依賴了什么,也不用擔心引入多余的依賴。Maven會解析各個直接依賴的POM,將那些必要的間接依賴,以傳遞性依賴的形式引入到當前的項目中。
依賴范圍不僅可以控制依賴與三種classpath的關系,還對傳遞性依賴產生影響。上面的例子中,項目TestProgram對於spring-core的依賴范圍是compile,spring-core對於commons-logging的依賴范圍是compile,那么項目TestProgram對於commons-logging這一傳遞性依賴的范圍也就是compile。假設A依賴於B,B依賴於C,我們即認為A對於B是第一直接依賴,B對於C是第二直接依賴,A對於C是傳遞性依賴。第一直接依賴的范圍和第二直接依賴的范圍決定了傳遞性依賴的范圍,如表2所示,最左邊一行表示第一直接依賴范圍,最上面一行表示第二直接依賴范圍,中間的交叉單元格則表示傳遞性依賴范圍。
表2 依賴范圍影響傳遞性依賴
| compile | test | provided | runtime | |
|---|---|---|---|---|
| compile | compile | ------ | ------ | compile |
| test | test | ------ | ------ | test |
| provided | provided | ------ | provided | provided |
| runtime | runtime | ------ | ------ | runtime |
為了能更好地幫助讀者理解表1,再舉個例子。項目TestProgram有一個com.icegreen:greenmail:1.3.1b的直接依賴,我們說這是第一直接依賴,其依賴范圍是test;而greenmail又有一個javax.mail:mail:1.4的直接依賴,我們說這是第二直接依賴,其依賴范圍是compile。顯然javax.mail:mail:1.4是項目TestProgram的傳遞性依賴,對照表1可以知道,當第一直接依賴范圍為test,第二直接依賴范圍是compile的時候,傳遞性依賴的范圍是test,因此javax.mail:mail:1.4是項目TestProgram的一個范圍是test的傳遞性依賴。
仔細觀察一下表2,可以發現這樣的規律:當第二直接依賴范圍的范圍是compile的時候,傳遞性依賴的范圍與第一直接依賴的范圍一致;當第二直接依賴的范圍是test的時候,依賴不會得以傳遞;當第二直接依賴的范圍是provided的時候,值傳遞第一直接依賴范圍也為provided的依賴,且傳遞性依賴的范圍同樣為provided;當第二直接依賴的范圍是runtime的時候,傳遞性依賴的范圍與第一直接依賴的范圍一致,但compile例外,此時傳遞性依賴的范圍為runtime。
在傳遞性依賴中導致依賴不同版本的jar包,如圖1中spring-cloud-starter中有2.2.4.RELEASE和2.2.1.RELEASE兩個版本被間接依賴。
建議在現場開發的時候,先在外網機環境上更新好對應的maven庫。外網及環境maven依賴沒有報錯的情況下,將IDEA環境設定為Work offline模式,執行maven的clean、compile和package命令(如下圖3),如能成功執行則表明外網機的maven庫jar包更新完整。否則需要關閉work offline模型,並多次執行Reload All Maven Projects(如下圖3左上角)命令,保證maven更新了完整的jar包,因為外網機環境可以直接連接倉庫導致本地Maven庫可能並未保存需要的jar包。

圖3: maven命令操作界面標識
問題3:IDEA下載maven依賴撥錯: Could not transfer artifact org.springframework.boot:spring-boot-maven-plugins*
答案分析:報這個錯的原因是證書校驗出現了問題,解決方法如下:
File->settings->Build->Build Tools->Maven->Runner。修改VM Option換上如下的參數(這樣的設置可以跳過證書檢查,接下來maven clean,compile,再install就可以解決該問題了)
-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true
問題4:IDEA 錯誤: 找不到或無法加載主類
答案分析:可能的原因:1) 未能成功編譯。操作:Build->Rebuild Project。一般情況下重新編譯后即可正常啟動。
2) 緩存問題。操作:File->Invalidate Caches/Restart 。選擇Invalidate and REstart 或者選擇Invalidate,然后清除掉緩存,接着Rebuild Project。
問題5:1) 明明maven庫里有對應的jar包,但項目代碼在卻找不到。
2) 項目有以下在編譯時提示類似以下報錯:
Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact dom4j:dom4j:pom:1.6.1 has not been downloaded from it before.
答案分析:解決方法,直接刪除本地倉庫中對應jar包所屬文件夾的以.lastUpdated和.repositories為后綴的文件(沒有以.lastUpdated為后綴的,僅刪除以.repositories為后綴的文件即可)。在實際操作中要刪除的該類文件可能會比較多,這點需要一定的耐心,本人測試過將外網機同一項目環境中運行正常的本地倉庫拷貝到內網機中,有時依然需要這樣操作。
原因分析:出現.lastUpdated文件一般情況下是由於網速慢、斷網等原因導致jar包下載不下來或不完整導致出現該文件的,.repositories文件的作用相當於本地Maven倉庫緩存了jar/pom的情況下修改了maven配置文件(settings.xml)后依然會去遠程倉庫獲取。將其刪除后就不會去這類文件中找遠端倉庫地址進行更新,而是直接引用了現有jar包,該問題也就解決了。
