Sự khác biệt giữa trình nạp lớp ngữ cảnh của trình xử lý và trình nạp lớp thông thường


242

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()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:


151

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.classtài liệu tham khảo ClassB.classthì ClassBcần phải nằm trên đường dẫn lớp của trình nạp lớp ClassAhoặ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 ClassLoaderCvà 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ó.


1
Tại sao bạn nói rằng ClassBphải nằm trên đường dẫn của ClassAtrình nạp (hoặc ClassAcha mẹ của trình tải)? Không phải ClassAtrình tải của nó có thể ghi đè loadClass(), sao cho nó có thể tải thành công ClassBngay cả khi ClassBkhông có trên đường dẫn của nó?
Pacerier

8
Thật vậy, không phải tất cả các trình nạp lớp đều có một đường dẫn lớp. Khi được viết "ClassB cần phải nằm trên đường dẫn lớp của trình nạp lớp của ClassA", tôi có nghĩa là "ClassB cần được tải bởi trình nạp lớp của ClassA". 90% thời gian chúng có nghĩa giống nhau. Nhưng nếu bạn không sử dụng trình nạp lớp dựa trên URL, thì chỉ có trường hợp thứ hai là đúng.
David Roussel

" ClassA.classTham khảo ClassB.class" nghĩa là gì?
jameshfisher

1
Khi ClassA có một câu lệnh nhập cho ClassB hoặc nếu có một phương thức trong ClassA có một biến cục bộ của loại ClassB. Điều đó sẽ kích hoạt ClassB được tải, nếu nó chưa được tải.
David Roussel

Tôi nghĩ vấn đề của tôi được kết nối với chủ đề này. Bạn nghĩ gì về sự làm phiền của tôi? Tôi biết rằng đó không phải là một mô hình tốt, nhưng tôi không có ý tưởng nào khác để khắc phục nó: stackoverflow.com/questions/29238493/
Kẻ

109

Đ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ỳ ContextClassLoadertruy 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 ClassLoadertham 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 ClassLoadertham 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ế ObjectInputStreamAPI đều quên chấp nhận ClassLoaderlà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 ContextClassLoadertrướ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);
}

5
Vâng, đây là câu trả lời tôi chỉ ra cho bất cứ ai đặt câu hỏi.
Marko Topolnik

6
Câu trả lời này tập trung vào việc sử dụng trình nạp lớp để tải các lớp (để khởi tạo chúng thông qua phản xạ hoặc tương tự), trong khi mục đích khác nó được sử dụng cho (và trên thực tế, mục đích duy nhất tôi từng sử dụng cho mục đích cá nhân) là tải tài nguyên. Có áp dụng nguyên tắc tương tự không, hoặc có những tình huống mà bạn muốn tìm nạp tài nguyên thông qua trình nạp lớp ngữ cảnh thay vì trình nạp lớp người gọi?
Egor Hans

90

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

Vì có ý kiến ​​cho rằng 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, tôi không hiểu cách này giúp trong trường hợp này. Chúng tôi muốn tải các lớp của nhà cung cấp triển khai bằng trình nạp lớp cha nhưng chúng không hiển thị với trình nạp lớp cha . Vậy làm thế nào chúng ta có thể tải chúng bằng cách sử dụng cha, ngay cả khi chúng ta đặt trình nạp lớp cha mẹ này trong trình nạp lớp ngữ cảnh của luồng.
Nắng Gupta

6
@SAM, cách giải quyết được đề xuất thực sự hoàn toàn ngược lại với những gì bạn đang nói ở cuối. Nó không phải là bootstraptrình nạp lớp cha được đặt làm trình nạp lớp ngữ cảnh mà là systemtrình nạp lớp đường dẫn lớp con mà nó Threadđang được thiết lập. Các JNDIlớ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.
Ravi Thapliyal

"Phái đoàn J2SE bình thường không hoạt động", tôi có thể biết tại sao nó không hoạt động không? Bởi vì Bootstrap ClassLoader chỉ có thể tải lớp từ rt.jar và không thể tải lớp từ classpath của ứng dụng? Đúng?
YuFeng Shen

37

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:

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

ClassLoader tuân theo ba nguyên tắc.

Nguyên tắc đại biểu

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$AppClassLoaderlớ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.

Nguyên tắc hiển thị

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.

Nguyên tắc duy nhất

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

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.