Tôi vẫn còn tương đối mới với Java, vì vậy hãy chịu với tôi.Jar địa ngục: làm thế nào để sử dụng một bộ nạp lớp để thay thế một phiên bản thư viện jar với nhau tại thời gian chạy
Vấn đề của tôi là ứng dụng Java của tôi phụ thuộc vào hai thư viện. Hãy gọi họ là Thư viện 1 và Thư viện 2. Cả hai thư viện chia sẻ một sự phụ thuộc lẫn nhau về Thư viện 3. Tuy nhiên:
- Thư viện 1 yêu cầu chính xác phiên bản 1 của Thư viện 3.
- Thư viện 2 đòi hỏi chính xác phiên bản 2 của Thư viện 3.
Đây chính xác là định nghĩa của JAR hell (hoặc ít nhất một biến thể của nó). Như đã nêu trong liên kết, tôi không thể tải cả hai phiên bản của thư viện thứ ba trong cùng một trình nạp lớp. Vì vậy, tôi đã cố gắng tìm ra nếu tôi có thể tạo một trình nạp lớp mới trong ứng dụng để giải quyết vấn đề này. Tôi đã xem xét URLClassLoader, nhưng tôi đã không thể tìm ra.
Đây là cấu trúc ứng dụng ví dụ minh họa sự cố. Lớp Main (Main.java) của ứng dụng cố gắng để nhanh chóng cả library1 và Library2 và chạy một số phương pháp quy định tại các thư viện:
Main.java (phiên bản gốc, trước khi bất kỳ nỗ lực tại một giải pháp):
public class Main {
public static void main(String[] args) {
Library1 lib1 = new Library1();
lib1.foo();
Library2 lib2 = new Library2();
lib2.bar();
}
}
library1 và Library2 cả chia sẻ một sự phụ thuộc lẫn nhau về Library3, nhưng library1 đòi hỏi chính xác phiên bản 1, và Library2 đòi hỏi chính xác phiên bản 2. trong ví dụ này, cả hai thư viện chỉ in các phiên bản của Library3 rằng họ nhìn thấy:
Lib rary1.java:
public class Library1 {
public void foo() {
Library3 lib3 = new Library3();
lib3.printVersion(); // Should print "This is version 1."
}
}
Library2.java:
public class Library2 {
public void foo() {
Library3 lib3 = new Library3();
lib3.printVersion(); // Should print "This is version 2." if the correct version of Library3 is loaded.
}
}
Và sau đó, tất nhiên, có rất nhiều phiên bản của Library3. Tất cả họ làm là in số phiên bản của họ:
Phiên bản 1 của Library3 (theo yêu cầu của library1):
public class Library3 {
public void printVersion() {
System.out.println("This is version 1.");
}
}
Phiên bản 2 của Library3 (theo yêu cầu của Library2):
public class Library3 {
public void printVersion() {
System.out.println("This is version 2.");
}
}
Khi tôi khởi chạy ứng dụng, đường dẫn lớp chứa Library1 (lib1.jar), Library2 (lib2.jar) và phiên bản 1 của Thư viện 3 (lib3-v1/lib3.jar). Điều này làm việc tốt cho Library1, nhưng nó sẽ không hoạt động đối với Library2.
Điều tôi bằng cách nào đó cần làm là thay thế phiên bản Library3 xuất hiện trên đường dẫn lớp trước khi khởi tạo Library2. Tôi đã có ấn tượng rằng URLClassLoader có thể được sử dụng cho điều này, vì vậy, đây là những gì tôi đã thử:
Main.java (phiên bản mới, bao gồm nỗ lực của tôi tại một giải pháp):
import java.net.*;
import java.io.*;
public class Main {
public static void main(String[] args)
throws MalformedURLException, ClassNotFoundException,
IllegalAccessException, InstantiationException,
FileNotFoundException
{
Library1 lib1 = new Library1();
lib1.foo(); // This causes "This is version 1." to print.
// Original code:
// Library2 lib2 = new Library2();
// lib2.bar();
// However, we need to replace Library 3 version 1, which is
// on the classpath, with Library 3 version 2 before attempting
// to instantiate Library2.
// Create a new classloader that has the version 2 jar
// of Library 3 in its list of jars.
URL lib2_url = new URL("file:lib2/lib2.jar"); verifyValidPath(lib2_url);
URL lib3_v2_url = new URL("file:lib3-v2/lib3.jar"); verifyValidPath(lib3_v2_url);
URL[] urls = new URL[] {lib2_url, lib3_v2_url};
URLClassLoader c = new URLClassLoader(urls);
// Try to instantiate Library2 with the new classloader
Class<?> cls = Class.forName("Library2", true, c);
Library2 lib2 = (Library2) cls.newInstance();
// If it worked, this should print "This is version 2."
// However, it still prints that it's version 1. Why?
lib2.bar();
}
public static void verifyValidPath(URL url) throws FileNotFoundException {
File filePath = new File(url.getFile());
if (!filePath.exists()) {
throw new FileNotFoundException(filePath.getPath());
}
}
}
Khi tôi chạy này, lib1.foo()
nguyên nhân "Đây là phiên bản 1." Được in. Vì đó là phiên bản Library3 nằm trên classpath khi ứng dụng bắt đầu, điều này được mong đợi.
Tuy nhiên, tôi đã mong đợi lib2.bar()
để in "Đây là phiên bản 2.", phản ánh phiên bản mới của Library3 đã được tải nhưng vẫn in "Đây là phiên bản 1."
Tại sao việc sử dụng trình nạp lớp mới có phiên bản bình phải được tải vẫn dẫn đến phiên bản jar cũ đang được sử dụng? Tôi có làm điều gì sai? Hay tôi không hiểu khái niệm đằng sau các trình nạp lớp? Làm thế nào tôi có thể chuyển đổi các phiên bản jar của Library3 một cách chính xác khi chạy?
Tôi sẽ đánh giá cao bất kỳ trợ giúp nào về vấn đề này.
bản sao có thể có của http://stackoverflow.com/questions/6105124/java-classpath-classloading-multiple-versions-of-the-same-jar-project – abalogh
người đã phát minh ra thuật ngữ * có thể * trùng lặp trên SO? nó có nghĩa là gì? – irreputable
@svkk FYI JDK8 sẽ có [Project Jigsaw] (http://openjdk.java.net/projects/jigsaw/doc/draft-java-module-system-requirements-12) nhằm giải quyết vấn đề Jar-hell . – Bringer128