![](/img/trans.png)
[英]NoClassDefFoundError in maven project with local dependency
[英]Re-installing maven dependency project causes NoClassDefFoundError in already running application
假設我有一個非常簡單的 maven 項目ProjA
,它本身沒有依賴項。 這個項目ProjA
有類X
和Y
如下:
package proja;
public class X {
static {
System.out.println("X loaded");
}
public void something() {
System.out.println("X hello world");
}
}
package proja;
public class Y {
static {
System.out.println("Y loaded");
}
public void something() {
System.out.println("Y hello world");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tomac</groupId>
<artifactId>ProjA</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>
接下來我有第二個 maven 項目ProjB
,它有項目ProjA
作為依賴項。
我項目ProjB
我有一個 class Run
如下:
package projb;
import proja.X;
import proja.Y;
import java.util.Scanner;
public class Run {
public void run() {
Scanner scanner = new Scanner(System.in);
while (true) {
String msg = scanner.nextLine();
switch (msg) {
case "x":
new X().something();
break;
case "y":
new Y().something();
break;
}
}
}
public static void main(String[] args) {
new Run().run();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tomac</groupId>
<artifactId>ProjB</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>ProjA</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>
我使用mvn install
安裝項目ProjA
,然后使用mvn compile
編譯項目ProjB
現在,我從 class 運行主要方法Run
使用:
mvn exec:java -Dexec.mainClass="projb.Run"
然后我鍵入x
<ENTER> 並得到 output:
X loaded
X hello world
之后我輸入y
<ENTER> 並得到 output:
Y loaded
Y hello world
現在,考慮特定的操作順序:
開始 class Run
(加載 class Run
並等待Scanner.nextLine()
)
鍵入x
<ENTER>(加載 class X
並輸出X loaded
X hello world
)
現在,當Run
運行時,在 class Y
中編輯一些東西,例如something()
方法的主體: System.out.println("Y hello world new");
使用mvn install
重新安裝項目ProjA
(這會導致將 class Y
打包編譯到目標 jar 並將打包的 jar 安裝到 local.m2 存儲庫中)
Go 回到正在運行的應用程序並鍵入y
<ENTER>
現在加載 class Y
會導致:
堆棧跟蹤:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:293)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NoClassDefFoundError: proja/Y
at projb.Run.run(Run.java:18)
at projb.Run.main(Run.java:25)
... 6 more
Caused by: java.lang.ClassNotFoundException: proja.Y
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 8 more
請注意,此類加載錯誤只有在依賴項目中一些尚未卸載的 class 被更改、部署,然后來自依賴項目的 class(已經至少有一個 class 從依賴項目加載)嘗試加載這個新更改的 class 時才會重現。
項目和 class 結構只是作為概念從更大的系統中提取出來的,這個系統有更多的類和main()
方法。 其中許多在不同的 JVM 中並行運行在同一台機器上。
問題:如何防止這種情況發生?
請注意,我不需要在運行時重新加載任何類型的動態 class 。
我知道以不兼容的方式進行更改(例如:在方法something(String str)
中添加參數)無論如何都會中斷。
一種解決方法是在更改和部署項目ProjA
中的某些內容時重新啟動項目ProjB
中的所有內容。 但是有些進程在啟動時的初始操作成本相對較高,因此這不是一種選擇。
另一種解決方法是以某種方式強制(使用例如Reflections Library )class 在項目ProjB
的每個進程啟動時從項目ProjA
加載所有類。 但這對我來說太過分了,可能會導致很多不必要的 class 負載並可能導致OutOfMemoryException
。
另一種選擇是將所有項目合並到一個大項目中,但是這樣就失去了將不同的東西分成不同項目的所有意義。
我怎樣才能更好地組織我的 develop->build->run/restart 流程,以便在啟動某些進程時以及在將來的某個時間加載類,以便那些加載的類定義等於在時間之前構建的代碼庫的時間點這個進程的啟動?
添加ProjA
和ProjB
的pom文件
出現此問題是因為exec-maven-plugin
使用 Maven類路徑,即聲明的依賴項來執行Java main。
在當前VM中執行提供的java類,並將封閉項目的依賴項作為類路徑。
這些依賴項在本地Maven存儲庫中有物理jar, .m2
,它確實可以隨着時間的推移而改變(通過在相關項目上install
的並行調用),並在SNAPSHOT
依賴項的情況下重寫(為了遵守約定,但你可以也重寫發布的版本,雖然強烈不建議)。
您可以通過運行dependency:build-classpath
來檢查它。
mvn dependency:build-classpath -Dmdep.outputFile=classpath.txt -DincludeScope=runtime
將classpath.txt
文件寫入exec:java
run使用的類路徑(注意該范圍為runtime
, 默認為exec:java
run)。 classpath.txt
文件中的classpath.txt
將有效地指向位於m2
根目錄下的jar文件。
因此,重寫到Maven緩存會影響指向它的類作為類路徑,因為Java會在第一次引用時加載該類。
一種更強大且可重現性更友好的方法是在發布中生成一個超級jar並有效地凍結所需的依賴項(您的程序類路徑)並將它們包裝到一個提供程序和類路徑的jar中。
因此,在保持項目的現有分離的同時,不再有並行/外部干預可能影響正在運行的應用程序。
另一種方法是通過以下versions:lock-snapshots
以前生成的依賴項目的SNAPSHOT
versions:lock-snapshots
:
搜索POM所有
-SNAPSHOT
版本,並與當前的時間戳版本替換它們-SNAPSHOT
,如-20090327.172306-4
因此,再次將您的項目與任何並發/外部干預隔離開來。 雖然在發布/分發項目時,更推薦使用超級jar方法。
此外,鎖定快照僅在通過Maven存儲庫可用時才起作用, 而不是在本地存儲庫安裝上工作:
嘗試將解鎖的快照依賴項版本解析為構建中使用的鎖定時間戳版本。 例如,
1.0-SNAPSHOT
等未鎖定的快照版本可以解析為1.0-20090128.202731-1
。 如果時間戳快照不可用,則版本將保持不變。 如果依賴項僅在本地存儲庫中可用而在遠程快照存儲庫中不可用,則會出現這種情況。
因此,在您的情況下,很可能不是一個選項。
要清除本地依賴項並重新安裝,您還可以使用 maven:
mvn dependency:purge-local-repository
根據文檔:
默認行為是先解析整個依賴樹,然后從本地倉庫中刪除內容,然后從遠程倉庫中重新解析依賴。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.