2012-12-18 37 views
5

Tình huống trong tầm tay không đơn giản như tiêu đề dường như chỉ ra.Làm cách nào để kiểm soát ClassLoader nào sẽ tải lớp học?

Java 1.6_17 chạy qua JWS.

Tôi có một lớp, giả sử MyClass và một trong các biến thành viên thể hiện của nó là một Loại từ thư viện bên thứ ba không đúng nơi khởi tạo lớp nó cố gắng tải một số lớp riêng của nó bằng Class.forName(String). Trong một trong những trường hợp này, nó sẽ tự động gọi: Class.forName("foo/Bar"). Tên lớp này không tuân theo JLS cho tên nhị phân và cuối cùng dẫn đến một số java.lang.NoClassDefFoundError: foo/Bar.

Chúng tôi có một tùy chỉnh ClassLoader mà tôi đã thêm phương pháp khử trùng vào ClassLoader.findClass(String)ClassLoader.loadClass(String) để khắc phục sự cố này.

tôi có thể gọi những thứ như: myCustomClassLoader.findClass("foo/Bar")

Mà sau đó tải các lớp mà không cần bất kỳ vấn đề. Nhưng ngay cả khi tôi tải lớp trước, tôi vẫn nhận được ngoại lệ sau. Điều này là do trong quá trình khởi tạo MyClass đề cập đến Bar - của chúng kết thúc bằng cách gọi số Class.forName("foo/Bar") trong một khối tĩnh ở đâu đó. Điều này thực sự sẽ là OK nếu ClassLoader nó đã cố gắng sử dụng là bộ nạp lớp tùy chỉnh của tôi. Nhưng không phải vậy. Đó là com.sun.jnlp.JNLPClassLoader mà không làm vệ sinh như vậy, do đó vấn đề của tôi.

Tôi đã đảm bảo rằng Thread.currentThread().getContextClassLoader() được đặt thành trình tải lớp tùy chỉnh của tôi. Nhưng điều này (như bạn biết) không có hiệu lực. Tôi thậm chí thiết lập nó như là điều đầu tiên tôi làm trong main() do một số công cụ tôi đọc và vẫn còn, MyClass.class.getClassLoader() - là JNLPClassLoader. Nếu tôi có thể ép buộc nó không phải là JNLPClassLoader và sử dụng của tôi thay vào đó, vấn đề được giải quyết.

Làm cách nào để kiểm soát ClassLoader nào được sử dụng để tải lớp học thông qua lời gọi Class.forName tĩnh ("foo/Bar") của họ trong quá trình khởi tạo lớp? Tôi tin rằng nếu tôi có thể buộc MyClass.class.getClassLoader() trả lại trình tải lớp tùy chỉnh của tôi, thì sự cố của tôi sẽ được giải quyết.

Tôi đang mở cho các tùy chọn khác nếu có ai đó có ý tưởng.

TL; DR: Giúp tôi buộc tất cả các cuộc gọi Class.forName(String) trong thư viện của bên thứ ba được tham chiếu bởi MyClass - để sử dụng trình nạp lớp mà tôi chọn.

+3

* "từ thư viện bên thứ ba sai lệch" * Chiến lược tốt nhất cuối cùng là thay thế API đó. –

+1

@AndrewThompson Nó có thể đến đó. Tôi hy vọng nó không. Vì vậy, nếu bất cứ ai có bất kỳ ý tưởng - Tôi là tất cả tai! –

+2

Tôi đã làm một cái gì đó tương tự bằng cách tạo một lớp bootstrap và sử dụng một URLClassLoader để tải tất cả các thư viện bên ngoài của chúng ta từ đó. Họ không ở trong classpath khi lớp boostrap khởi chạy, do đó, trình nạp lớp duy nhất mà họ thấy là lớp tùy chỉnh. Không phải là một câu trả lời bởi vì tôi không có ý tưởng nếu nó sẽ làm việc với JWS. Chúng tôi đã làm nó để tải thư viện plugin vào thời gian chạy. – Thomas

Trả lời

1

Tôi nghĩ mọi người đã cố gắng hết sức để trả lời sự cố. Tuy nhiên, nó chỉ ra rằng tôi misdiagnosed vấn đề.

Tôi đã có một đồng nghiệp đảm nhận vấn đề và yêu cầu anh ta lấy một JDK với cờ gỡ lỗi để chúng tôi có thể gỡ lỗi JNLPClassLoader để xem điều gì đang xảy ra khi tôi đã thử tất cả các đề xuất ở đây + một số.

Chúng tôi đã nhận được OpenJDK vì biên dịch lại JDK từ đầu là một cơn ác mộng hoàn toàn (chúng tôi đã thử). Sau khi OpenJDK làm việc với sản phẩm của chúng tôi và gỡ lỗi thông qua các JNLPClassLoader - nó chỉ ra rằng nó vẫn còn sử dụng một REALLY cũ .jnlp từ tháng trước đó có đường dẫn tài nguyên sai và do đó tại sao nó không thể tìm thấy lớp.

Chúng tôi đã nhầm lẫn lý do tại sao nó vẫn sử dụng tệp .jnlp cổ xưa mặc dù chúng tôi đã triển khai lại máy chủ đúng nhiều lần với đúng .jnlp và rất nhiều thay đổi mã được phản ánh trong ứng dụng khách của chúng tôi khi chạy.

Vâng, hóa ra là trên máy khách, Java lưu trữ tệp .jnlp. Ngay cả khi ứng dụng của bạn thay đổi và nó tải xuống ứng dụng của bạn, nó vẫn sẽ không tải xuống lại .jnlp mới vì bất kỳ lý do gì. Vì vậy, nó sẽ sử dụng tất cả các mã mới, nhưng tìm kiếm các đường dẫn tài nguyên/lớp bằng cách sử dụng bộ nhớ đệm .jnlp.

Nếu bạn chạy: javaws -uninstall Trên máy khách sau đó sẽ xóa bộ đệm .jnlp và lần sau nó sẽ sử dụng đúng tệp .jnlp.

Thực sự buồn vì đây là vấn đề. Hy vọng rằng, điều này giúp tiết kiệm một số giờ bất tận khác của sự thất vọng như nó đã gây ra cho chúng tôi.

4

Điều này nhắc tôi về một bài báo tôi đã đọc 10 năm trước về các sắp xếp tải lớp trong Java. Nó vẫn ở đó on JavaWorld.

Bài viết sẽ không trả lời câu hỏi của bạn trực tiếp, nhưng nó có thể giúp bạn hiểu được vấn đề của mình. Bạn cần phải tải MyClass để tải thông qua trình nạp lớp tùy chỉnh của mình và vượt qua hành vi tải lớp mặc định, đây là lần đầu tiên ủy quyền tải lớp cho trình nạp lớp cha và chỉ cố tải một lớp nếu không thành công.

phép MyClass để nạp bởi một classloader khác hơn của bạn sẽ lưu giữ một mối quan hệ từ lớp được thuyết minh để classloader đó (qua getClassLoader) và gây Java để sử dụng classloader khác để cố gắng khám phá bất kỳ lớp tham chiếu found at compile time, bỏ qua hiệu quả của bạn trình nạp lớp tùy chỉnh nhờ hệ thống phân cấp trình nạp lớp và mô hình ủy nhiệm. Nếu MyClass thay vào đó là được xác định là bởi trình tải lớp của bạn, bạn sẽ có cơ hội thứ hai.

Nghe có vẻ giống như một công việc cho một cái gì đó như URLClassLoader, ghi đè loadClass và vượt qua mô hình ủy quyền cho các lớp học nằm trong các tệp JAR của bạn. Có thể bạn sẽ muốn sử dụng một phương pháp tiếp cận bootstrap (như đề xuất của Thomas trong một bình luận ở trên) để buộc một lớp nhập điểm duy nhất được nạp thông qua trình nạp lớp tùy chỉnh của bạn, kéo tất cả những người khác với nó.

Cũng có thông tin là this other JavaWorld article bởi cùng một anh chàng, cảnh báo bạn về các lời nhắc của Class.forName. Điều đó cũng có thể dẫn đến việc sắp xếp xếp lớp của bạn.

Tôi hy vọng điều này sẽ giúp và chứng minh thông tin. Trong mọi trường hợp, nó nghe có vẻ như một giải pháp khó mà dễ phá vỡ khi mã của bạn phát triển.

+0

+1 Thông tin tuyệt vời. Tôi sẽ tiêu hóa vào ngày mai. Nếu điều này kéo ra, tôi sẽ đánh dấu nó là câu trả lời. Cảm ơn! –

+0

Nghiên cứu của bạn đã kết thúc ở đâu? Bất kỳ nghỉ nào? –

2

Nếu bạn hết ý tưởng với tự vá chính mình, bạn có thể xem xét tự viết lại bytecode thư viện - chỉ thay thế hằng số "foo/bar" bằng giá trị chính xác và sau đó bạn không cần tùy chỉnh nạp thêm lớp nữa!

Bạn có thể thực hiện việc này vào lúc chạy hoặc trước.

+0

+1 Ý tưởng tuyệt vời.Tuy nhiên như tôi đã lưu ý trong câu hỏi của tôi (và cố gắng làm cho rõ ràng nhưng thất bại - xin lỗi!) Nó đang làm tải lớp năng động. Tôi lấy mã nguồn đã và grepped cho "foo/bar" và tất cả những gì đã đưa ra là nhập khẩu (và lớp def chính nó). Vì vậy, tên lớp không phải là tĩnh. Vấn đề này đã xảy ra khoảng một năm trước với thư viện chính xác này. Nó đang sử dụng trình nạp lớp tùy chỉnh của chúng tôi, và sau khi gỡ lỗi thông qua nó, tôi thấy nó đang gọi 'Class.forName()' với chuỗi động và vì vậy tôi đã sửa đổi trình nạp lớp tùy chỉnh của chúng tôi để khử trùng tên lớp và khắc phục sự cố cho đến khi điều này xảy ra. –

Các vấn đề liên quan