Java, Classpath, Classloading => Nhiều phiên bản của cùng một jar / dự án


117

Tôi biết đây có thể là một câu hỏi ngớ ngẩn cho các lập trình viên có kinh nghiệm. Nhưng tôi có một thư viện (một máy khách http) mà một số khung / lọ khác được sử dụng trong dự án của tôi yêu cầu. Nhưng tất cả chúng đều yêu cầu các phiên bản chính khác nhau như:

httpclient-v1.jar => Required by cralwer.jar
httpclient-v2.jar => Required by restapi.jar
httpclient-v3.jar => required by foobar.jar

Trình nạp lớp có đủ thông minh để ngăn cách chúng bằng cách nào đó không? Hầu như không? Classloader xử lý việc này như thế nào, trong trường hợp một Class giống nhau trong cả ba lọ. Cái nào được tải và tại sao?

Classloader chỉ nhận chính xác một jar hay nó trộn các lớp tùy ý? Vì vậy, ví dụ nếu một lớp được tải từ Phiên bản-1.jar, tất cả các lớp khác được tải từ cùng một trình nạp lớp đều sẽ đi vào cùng một jar?

Làm thế nào để bạn xử lý vấn đề này?

Có một số mẹo để bằng cách nào đó "kết hợp" các lọ vào "required.jar" để cái được xem là "một đơn vị / gói" bởi Classloader, hoặc bằng cách nào đó được liên kết?

Câu trả lời:


56

Các vấn đề liên quan đến classloader là một vấn đề khá phức tạp. Trong mọi trường hợp, bạn nên ghi nhớ một số sự thật:

  • Trình nạp lớp trong một ứng dụng thường nhiều hơn một. Trình nạp lớp bootstrap ủy nhiệm cho thích hợp. Khi bạn khởi tạo một lớp mới, trình nạp lớp cụ thể hơn được gọi. Nếu nó không tìm thấy một tham chiếu đến lớp mà bạn đang cố tải, thì nó sẽ ủy quyền cho lớp mẹ của nó, v.v., cho đến khi bạn đến trình nạp lớp bootstrap. Nếu không ai trong số họ tìm thấy một tham chiếu đến lớp mà bạn đang cố tải, bạn sẽ nhận được ClassNotFoundException.

  • Nếu bạn có hai lớp có cùng tên nhị phân, có thể tìm kiếm bởi cùng một trình nạp lớp và bạn muốn biết một trong số chúng đang tải, bạn chỉ có thể kiểm tra cách trình tải lớp cụ thể cố gắng giải quyết tên lớp.

  • Theo đặc tả ngôn ngữ java, không có ràng buộc duy nhất cho tên nhị phân lớp, nhưng theo như tôi có thể thấy, nó phải là duy nhất cho mỗi trình nạp lớp.

Tôi có thể tìm ra một cách để tải hai lớp có cùng tên nhị phân và nó liên quan đến việc chúng được tải (và tất cả các phụ thuộc của chúng) bởi hai trình nạp lớp khác nhau ghi đè hành vi mặc định. Một ví dụ sơ bộ:

    ClassLoader loaderA = new MyClassLoader(libPathOne);
    ClassLoader loaderB = new MyClassLoader(libPathTwo);
    Object1 obj1 = loaderA.loadClass("first.class.binary.name", true)
    Object2 obj2 = loaderB.loadClass("second.class.binary.name", true);

Tôi luôn luôn thấy việc tùy biến classloader là một nhiệm vụ khó khăn. Tôi muốn đề nghị tránh nhiều phụ thuộc không tương thích nếu có thể.


13
Trình nạp lớp bootstrap ủy nhiệm cho thích hợp. Khi bạn khởi tạo một lớp mới, trình nạp lớp cụ thể hơn được gọi. Nếu nó không tìm thấy một tham chiếu đến lớp mà bạn đang cố tải, nó sẽ ủy quyền cho cha mẹ của nó. Xin hãy đồng ý với tôi, nhưng nó phụ thuộc vào chính sách trình nạp lớp theo mặc định là Parent First. Nói cách khác, lớp con trước tiên sẽ yêu cầu lớp cha của nó tải lớp và sẽ chỉ tải nếu toàn bộ hệ thống phân cấp không tải được, không ??
Deckingraj

5
Không - thường là một trình nạp lớp ủy quyền cho cha mẹ của nó trước khi tìm kiếm chính lớp đó. Xem lớp javadoc cho Classloader.
Joe Kearney

1
Tôi nghĩ tomcat thực hiện theo cách được mô tả ở đây, nhưng phái đoàn "thông thường" là hỏi phụ huynh trước
rogerdpack

@deckingraj: sau khi một số googling tôi thấy điều này từ tài liệu oracle: "Trong thiết kế đoàn, một đại biểu lớp loader classloading đến mẹ của nó trước . cố gắng tải một lớp riêng của mình [...] Nếu bộ nạp lớp cha mẹ không thể nạp một lớp, trình nạp lớp cố gắng tự tải lớp. Thực tế, trình nạp lớp chịu trách nhiệm chỉ tải các lớp không có sẵn cho cha mẹ ". Tôi sẽ điều tra thêm. Nếu điều này sẽ xuất hiện như là thực hiện mặc định, tôi sẽ cập nhật phản hồi cho phù hợp. ( docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html )
Luca Putzu

20

Mỗi lớp tải chọn chính xác một lớp. Thường là cái đầu tiên được tìm thấy.

OSGi nhằm mục đích giải quyết vấn đề của nhiều phiên bản của cùng một jar. EquinoxApache Felix là các triển khai nguồn mở phổ biến cho OSGi.


6

Trình nạp lớp sẽ tải các lớp từ tệp jar xảy ra trong đường dẫn lớp trước. Thông thường, các phiên bản thư viện không tương thích sẽ có sự khác biệt trong các gói, nhưng trong trường hợp không chắc là chúng thực sự không tương thích và không thể thay thế bằng một - hãy thử jarjar.


6

Classloaders tải lớp theo yêu cầu. Điều này có nghĩa là lớp được yêu cầu trước bởi ứng dụng của bạn và các thư viện liên quan sẽ được tải trước các lớp khác; yêu cầu tải các lớp phụ thuộc thường được đưa ra trong quá trình tải và liên kết của một lớp phụ thuộc.

Bạn có thể gặp phải LinkageErrorkhi nói rằng các định nghĩa lớp trùng lặp đã gặp phải đối với các trình nạp lớp thường không cố gắng xác định lớp nào sẽ được tải trước (nếu có hai hoặc nhiều lớp cùng tên có trong đường dẫn lớp của trình nạp). Đôi khi, trình nạp lớp sẽ tải lớp đầu tiên xảy ra trong đường dẫn lớp và bỏ qua các lớp trùng lặp, nhưng điều này phụ thuộc vào việc thực hiện trình nạp.

Cách thực hành được đề xuất để giải quyết các loại lỗi như vậy là sử dụng một trình nạp lớp riêng cho từng bộ thư viện có phụ thuộc xung đột. Theo cách đó, nếu một trình nạp lớp cố gắng tải các lớp từ thư viện, các lớp phụ thuộc sẽ được tải bởi cùng một trình nạp lớp không có quyền truy cập vào các thư viện và các phụ thuộc khác.


1

Bạn có thể sử dụng URLClassLoaderyêu cầu để tải các lớp từ phiên bản diff-2 của lọ:

URLClassLoader loader1 = new URLClassLoader(new URL[] {new File("httpclient-v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
URLClassLoader loader2 = new URLClassLoader(new URL[] {new File("httpclient-v2.jar").toURL()}, Thread.currentThread().getContextClassLoader());

Class<?> c1 = loader1.loadClass("com.abc.Hello");

Class<?> c2 = loader2.loadClass("com.abc.Hello");

BaseInterface i1 = (BaseInterface) c1.newInstance();

BaseInterface i2 = (BaseInterface) c2.newInstance();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.