Sự khác biệt giữa trình nạp lớp ngữ cảnh của luồng và trình nạp lớp bình thường là gì?
Đó là, nếu Thread.currentThread().getContextClassLoader()
và getClass().getClassLoader()
trả về các đối tượng trình nạp lớp khác nhau, cái nào sẽ được sử dụng?
Sự khác biệt giữa trình nạp lớp ngữ cảnh của luồng và trình nạp lớp bình thường là gì?
Đó là, nếu Thread.currentThread().getContextClassLoader()
và getClass().getClassLoader()
trả về các đối tượng trình nạp lớp khác nhau, cái nào sẽ được sử dụng?
Câu trả lời:
Mỗi lớp sẽ sử dụng trình nạp lớp riêng để tải các lớp khác. Vì vậy, nếu các ClassA.class
tài liệu tham khảo ClassB.class
thì ClassB
cần phải nằm trên đường dẫn lớp của trình nạp lớp ClassA
hoặc cha mẹ của nó.
Trình nạp lớp ngữ cảnh luồng là trình nạp lớp hiện tại cho luồng hiện tại. Một đối tượng có thể được tạo từ một lớp trong ClassLoaderC
và sau đó được chuyển đến một luồng thuộc sở hữu của ClassLoaderD
. Trong trường hợp này, đối tượng cần sử dụng Thread.currentThread().getContextClassLoader()
trực tiếp nếu nó muốn tải các tài nguyên không có sẵn trên trình nạp lớp riêng của nó.
ClassA.class
Tham khảo ClassB.class
" nghĩa là gì?
Điều này không trả lời câu hỏi ban đầu, nhưng vì câu hỏi được xếp hạng và liên kết cao cho bất kỳ ContextClassLoader
truy vấn nào , tôi nghĩ điều quan trọng là phải trả lời câu hỏi liên quan khi nào nên sử dụng trình tải lớp ngữ cảnh. Câu trả lời ngắn gọn: không bao giờ sử dụng trình nạp lớp ngữ cảnh ! Nhưng đặt nó thành getClass().getClassLoader()
khi bạn phải gọi một phương thức thiếu ClassLoader
tham số.
Khi mã từ một lớp yêu cầu tải một lớp khác, trình nạp lớp đúng sẽ sử dụng là trình nạp lớp giống như lớp người gọi (tức là getClass().getClassLoader()
). Đây là cách mọi thứ hoạt động 99,9% thời gian bởi vì đây là điều mà JVM tự thực hiện lần đầu tiên khi bạn xây dựng một thể hiện của một lớp mới, gọi một phương thức tĩnh hoặc truy cập vào một trường tĩnh.
Khi bạn muốn tạo một lớp bằng cách sử dụng sự phản chiếu (chẳng hạn như khi giải tuần tự hóa hoặc tải một lớp có tên có thể định cấu hình), thư viện luôn phản ánh phải luôn hỏi ứng dụng sử dụng trình nạp lớp nào, bằng cách nhận ClassLoader
tham số từ ứng dụng. Ứng dụng (biết tất cả các lớp cần xây dựng) sẽ vượt qua nó getClass().getClassLoader()
.
Bất kỳ cách nào khác để có được một trình nạp lớp là không chính xác. Nếu một thư viện sử dụng hacks như Thread.getContextClassLoader()
, sun.misc.VM.latestUserDefinedLoader()
hoặc sun.reflect.Reflection.getCallerClass()
nó là một lỗi gây ra bởi sự thiếu hụt trong API. Về cơ bản, Thread.getContextClassLoader()
chỉ tồn tại bởi vì bất kỳ ai thiết kế ObjectInputStream
API đều quên chấp nhận ClassLoader
làm tham số và lỗi này đã ám ảnh cộng đồng Java cho đến ngày nay.
Điều đó nói rằng, nhiều lớp JDK sử dụng một trong một số hack để đoán một số trình nạp lớp để sử dụng. Một số sử dụng ContextClassLoader
(không thành công khi bạn chạy các ứng dụng khác nhau trên nhóm luồng chung hoặc khi bạn rời khỏi ContextClassLoader null
), một số sử dụng ngăn xếp (không thành công khi trình gọi trực tiếp của lớp là thư viện), một số sử dụng trình tải lớp hệ thống (điều này là tốt, miễn là nó được ghi nhận là chỉ sử dụng các lớp trong CLASSPATH
) hoặc trình nạp lớp bootstrap, và một số sử dụng sự kết hợp không thể đoán trước của các kỹ thuật trên (chỉ làm cho mọi thứ trở nên khó hiểu hơn). Điều này đã dẫn đến nhiều khóc lóc và nghiến răng.
Khi sử dụng API như vậy, trước tiên, hãy thử tìm sự quá tải của phương thức chấp nhận trình nạp lớp làm tham số . Nếu không có phương pháp hợp lý, thì hãy thử đặt ContextClassLoader
trước lệnh gọi API (và đặt lại sau đó):
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
// call some API that uses reflection without taking ClassLoader param
} finally {
Thread.currentThread().setContextClassLoader(originalClassLoader);
}
Có một bài viết trên javaworld.com giải thích sự khác biệt => Bạn nên sử dụng ClassLoader nào
(1)
Trình tải lớp ngữ cảnh luồng cung cấp một cửa sau xung quanh lược đồ phân lớp tải.
Lấy JNDI làm ví dụ: ruột của nó được triển khai bởi các lớp bootstrap trong rt.jar (bắt đầu với J2SE 1.3), nhưng các lớp JNDI cốt lõi này có thể tải các nhà cung cấp JNDI được triển khai bởi các nhà cung cấp độc lập và có khả năng được triển khai trong đường dẫn lớp của ứng dụng. Kịch bản này yêu cầu một trình nạp lớp cha (trường hợp nguyên thủy trong trường hợp này) để tải một lớp hiển thị cho một trong các trình nạp lớp con của nó (ví dụ như hệ thống). Phân quyền J2SE bình thường không hoạt động và cách giải quyết là làm cho các lớp JNDI lõi sử dụng các trình tải ngữ cảnh luồng, do đó "đường hầm" hiệu quả thông qua hệ thống phân cấp của trình nạp lớp theo hướng ngược lại với phân cấp thích hợp.
(2) từ cùng một nguồn:
Sự nhầm lẫn này có thể sẽ ở lại với Java một thời gian. Sử dụng bất kỳ API J2SE nào với tải tài nguyên động dưới mọi hình thức và thử đoán xem nó sử dụng chiến lược tải nào. Đây là một mẫu:
- JNDI sử dụng trình nạp lớp ngữ cảnh
- Class.getResource () và Class.forName () sử dụng trình nạp lớp hiện tại
- JAXP sử dụng các trình nạp lớp ngữ cảnh (kể từ J2SE 1.4)
- java.util.ResourceBundle sử dụng trình nạp lớp hiện tại của trình gọi
- Trình xử lý giao thức URL được chỉ định qua java.protatio.handler.pkgs thuộc tính hệ thống chỉ được tra cứu trong trình khởi động lớp và hệ thống trình nạp lớp hệ thống
- API tuần tự hóa Java sử dụng trình nạp lớp hiện tại của trình gọi theo mặc định
bootstrap
trình nạp lớp cha được đặt làm trình nạp lớp ngữ cảnh mà là system
trình nạp lớp đường dẫn lớp con mà nó Thread
đang được thiết lập. Các JNDI
lớp sau đó được đảm bảo sử dụng Thread.currentThread().getContextClassLoader()
để tải các lớp triển khai JNDI có sẵn trên đường dẫn lớp.
Thêm vào câu trả lời @David Roussel, các lớp có thể được tải bởi nhiều trình nạp lớp.
Hãy hiểu cách trình tải lớp hoạt động.
Từ blog javin paul trong javarevisited:
ClassLoader
tuân theo ba nguyên tắc.
Một lớp được tải bằng Java, khi cần. Giả sử bạn có một lớp dành riêng cho ứng dụng có tên Abc. Class, yêu cầu đầu tiên về việc tải lớp này sẽ đến Application ClassLoader, nó sẽ ủy quyền cho Trình mở rộng ClassLoader cha của nó, đại biểu tiếp theo cho trình tải lớp Primordial hoặc Bootstrap
Bootstrap ClassLoader chịu trách nhiệm tải các tệp lớp JDK tiêu chuẩn từ rt.jar và nó là cha mẹ của tất cả các trình nạp lớp trong Java. Trình tải lớp Bootstrap không có cha mẹ.
Phần mở rộng ClassLoader ủy quyền yêu cầu tải lớp cho cha mẹ của nó, Bootstrap và nếu không thành công, tải thư mục jre / lib / ext của lớp hoặc bất kỳ thư mục nào khác được chỉ ra bởi thuộc tính hệ thống java.ext.dirs
Trình tải lớp hệ thống hoặc ứng dụng và nó chịu trách nhiệm tải các lớp cụ thể của ứng dụng từ biến môi trường CLASSPATH, tùy chọn dòng lệnh-classpath hoặc -cp, thuộc tính Class-Path của tệp Manifest bên trong JAR.
Trình tải lớp ứng dụng là một phần tử con của Trình mở rộng lớp mở rộng và được triển khai bởi sun.misc.Launcher$AppClassLoader
lớp.
LƯU Ý: Ngoại trừ trình nạp lớp Bootstrap , được triển khai bằng ngôn ngữ bản địa chủ yếu bằng C, tất cả các trình nạp lớp Java đều được triển khai bằng cách sử dụng java.lang.ClassLoader
.
Theo nguyên tắc hiển thị, Child ClassLoader có thể thấy lớp được tải bởi Parent ClassLoader nhưng ngược lại là không đúng.
Theo nguyên tắc này, một lớp được tải bởi Parent không nên được tải bởi Child ClassLoader nữa
ClassB
phải nằm trên đường dẫn củaClassA
trình nạp (hoặcClassA
cha mẹ của trình tải)? Không phảiClassA
trình tải của nó có thể ghi đèloadClass()
, sao cho nó có thể tải thành côngClassB
ngay cả khiClassB
không có trên đường dẫn của nó?