İş parçacığının bağlam sınıfı yükleyicisi ile normal sınıf yükleyicisi arasındaki fark


242

Bir iş parçacığının bağlam sınıfı yükleyicisi ile normal bir sınıf yükleyicisi arasındaki fark nedir?

Yani, farklı sınıf yükleyici nesneleri varsa Thread.currentThread().getContextClassLoader()ve getClass().getClassLoader()döndürürse, hangisi kullanılır?

Yanıtlar:


151

Her sınıf, diğer sınıfları yüklemek için kendi sınıf yükleyicisini kullanır. Yani eğer ClassA.classreferanslar ClassB.classsonra ClassBbir classloader ait üzerinde sınıf olması gerekir ClassA, ya da ebeveynlerden.

İş parçacığı bağlamı sınıf yükleyicisi, geçerli iş parçacığının geçerli sınıf yükleyicisidir. Bir sınıftaki bir nesne oluşturulabilir ClassLoaderCve daha sonra ait olduğu bir iş parçacığına geçirilebilir ClassLoaderD. Bu durumda, nesne Thread.currentThread().getContextClassLoader()kendi sınıf yükleyicisinde bulunmayan kaynakları yüklemek istiyorsa doğrudan kullanması gerekir .


1
Neden bunun yükleyicinin (veya yükleyicisinin ebeveynlerinin) ClassBsınıf yolunda olması gerektiğini söylüyorsunuz ? Yükleyicisinin sınıf yolunda olmasa bile başarıyla yüklenebilmesi için geçersiz kılınması mümkün değil mi? ClassAClassAClassAloadClass()ClassBClassB
Pacerier

8
Gerçekten de, tüm sınıf yükleyicilerin bir sınıf yolu yoktur. "ClassB ClassA sınıf yükleyicisinin sınıf yolunda olması gerekir" yazdığında, "ClassB ClassA sınıf yükleyicisi tarafından yüklenebilir gerekir" demek istedim. Zamanın% 90'ı aynı anlama geliyor. Ancak, URL tabanlı bir sınıf yükleyici kullanmıyorsanız, yalnızca ikinci durum geçerlidir.
David Roussel

" ClassA.classReferanslar ClassB.class" demek ne anlama geliyor ?
jameshfisher

1
ClassA, ClassB için bir içe aktarma deyimi olduğunda veya ClassA'da ClassB türünde yerel değişkeni olan bir yöntem varsa. Bu, önceden yüklenmemişse ClassB'nin yüklenmesini tetikler.
David Roussel

Sanırım sorunum bu konu ile bağlantılı. Benim çözümüm hakkında ne düşünüyorsun? Bunun iyi bir desen olmadığını biliyorum, ancak nasıl düzeltileceğim konusunda başka bir fikrim yok: stackoverflow.com/questions/29238493/…
Marcin Erbel

109

Bu orijinal soruyu cevaplamıyor, ancak soru herhangi bir ContextClassLoadersorgu için yüksek dereceli ve bağlantılı olduğundan, bağlam sınıfı yükleyicinin ne zaman kullanılması gerektiği ile ilgili soruyu cevaplamanın önemli olduğunu düşünüyorum. Kısa cevap: asla bağlam sınıfı yükleyiciyi kullanmayın ! Ancak getClass().getClassLoader(), ClassLoaderparametresi eksik bir yöntemi çağırmanız gerektiğinde bunu ayarlayın .

Bir sınıftan kod başka bir sınıf yüklemeyi istediğinde, kullanılacak doğru sınıf yükleyici, arayan sınıfı ile aynı sınıf yükleyicisidir (yani getClass().getClassLoader()). Bu, işlerin% 99.9'unun çalışma biçimidir, çünkü JVM, yeni bir sınıf örneği ilk kez oluşturduğunuzda, statik bir yöntemi çağırdığınızda veya statik bir alana eriştiğinizde budur.

Yansıma kullanarak bir sınıf oluşturmak istediğinizde (örneğin, yapılandırılmış adlandırılmış bir sınıfın serileştirilmesi veya yüklenmesi gibi), yansımayı yapan kitaplık , uygulamadanClassLoader bir parametre olarak alarak her zaman uygulamaya hangi sınıf yükleyicinin kullanılacağını sormalıdır . (İnşa edilmesi gereken tüm sınıfları bilen) uygulama bunu geçmelidir getClass().getClassLoader().

Sınıf yükleyici almanın başka bir yolu yanlıştır. Gibi bir kütüphane kullanımları kesmek durumunda Thread.getContextClassLoader(), sun.misc.VM.latestUserDefinedLoader()ya sun.reflect.Reflection.getCallerClass()da API eksikliğinden kaynaklanan bir hatadır. Temel olarak, Thread.getContextClassLoader()sadece ObjectInputStreamAPI'yi tasarlayan kişi ClassLoaderparametre olarak kabul etmeyi unuttuğu ve bu hata Java topluluğunu bugüne kadar perdelediği için var.

Bununla birlikte, birçok JDK sınıfı, bazı sınıf yükleyicilerin kullanmasını tahmin etmek için birkaç hack'ten birini kullanır. Bazıları ContextClassLoader(paylaşılan bir iş parçacığı havuzunda farklı uygulamalar çalıştırdığınızda veya ayrıldığınızda ContextClassLoader nullbaşarısız olur), bazıları yığını (sınıfın doğrudan arayanı bir kütüphane olduğunda başarısız olur) kullanır, bazıları sistem sınıfı yükleyiciyi kullanır (sadece sınıftaki sınıfları kullanmak için belgelendiği sürece iyidir CLASSPATH) veya bootstrap sınıf yükleyicisi ve bazıları yukarıdaki tekniklerin (yalnızca işleri daha karmaşık hale getirir) öngörülemeyen bir kombinasyonunu kullanır. Bu, dişlerin çok fazla ağlamasına ve gıcırdamasına neden oldu.

Böyle bir API kullanırken, önce sınıf yükleyiciyi parametre olarak kabul eden yöntemin aşırı yüklenmesini bulmaya çalışın . Mantıklı bir yöntem yoksa, ContextClassLoaderAPI çağrısından önce ayarlamayı (ve daha sonra sıfırlamayı) deneyin :

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
Evet, soruyu soran herkese işaret ettiğim cevap bu.
Marko Topolnik

6
Bu cevap, sınıfları yüklemek için sınıf yükleyiciyi (yansıma ya da benzeri yollarla örneklemek için) kullanmaya odaklanırken, diğer bir amacı (ve aslında, kişisel olarak kullandığım tek amaç) kaynakları yüklemek. Aynı ilke geçerli mi veya arayan sınıfı yükleyicisinden ziyade, bağlam sınıfı yükleyicisinden kaynakları almak istediğiniz durumlar var mı?
Egor Hans

90

Javaworld.com'da farkı açıklayan bir makale var => Hangi ClassLoader kullanmalısınız

(1)

İş parçacığı bağlamı sınıf yükleyicileri, sınıf yükleme yetkilendirme şemasının etrafında bir arka kapı sağlar.

Örneğin JNDI'yı ele alalım: bağırsakları rt.jar'daki bootstrap sınıfları tarafından uygulanır (J2SE 1.3'ten başlayarak), ancak bu çekirdek JNDI sınıfları bağımsız satıcılar tarafından uygulanan ve potansiyel olarak uygulamanın -classpath'ine dağıtılan JNDI sağlayıcılarını yükleyebilir. Bu senaryo, alt sınıf yükleyicilerinden birine (örneğin sisteme) göre bir sınıf yüklemek için bir üst sınıf yükleyiciyi (bu durumda ilkel olanı) çağırır. Normal J2SE yetkilendirmesi çalışmaz ve çözüm, temel JNDI sınıflarının iş parçacığı bağlam yükleyicilerini kullanmasını sağlamak, böylece uygun delegasyonun tersi yönde sınıf yükleyici hiyerarşisinde etkili bir şekilde "tünel oluşturmak "tır.

(2) aynı kaynaktan:

Bu karışıklık muhtemelen bir süre Java ile kalacaktır. Herhangi bir türden dinamik kaynak yüklemesi ile herhangi bir J2SE API'sini alın ve hangi yükleme stratejisini kullandığını tahmin etmeye çalışın. İşte bir örnekleme:

  • JNDI bağlam sınıf yükleyicilerini kullanır
  • Class.getResource () ve Class.forName () geçerli classloader'ı kullanır
  • JAXP bağlam sınıf yükleyicileri kullanır (J2SE 1.4'ten itibaren)
  • java.util.ResourceBundle, arayanın geçerli sınıf yükleyicisini kullanır
  • Java.protocol.handler.pkgs sistem özelliği ile belirtilen URL protokol işleyicileri yalnızca önyükleme ve sistem sınıf yükleyicilerinde aranır
  • Java Serialization API'sı varsayılan olarak arayanın geçerli sınıf yükleyicisini kullanır

Geçici çözümün temel JNDI sınıflarının iş parçacığı bağlam yükleyicilerini kullanmasını sağlamak olduğu önerildiğinden, bu durumda bunun nasıl yardımcı olduğunu anlamadım. . Bu üst sınıf yükleyiciyi iş parçacığının bağlam sınıfı yükleyicisine ayarlasak bile bunları üst öğe kullanarak nasıl yükleyebiliriz.
Sunny Gupta

6
@SAM, önerilen geçici çözüm aslında sonunda söylediklerinizin tam tersidir. bootstrapBağlam sınıfı yükleyicisi olarak ayarlanan üst sınıf yükleyicisi değil, ayarlandığı alt systemsınıf yolu yükleyicisi Thread. JNDISınıflar sonra kullandığınızdan emin yapıyoruz Thread.currentThread().getContextClassLoader()üzerinde sınıf mevcut Jndi uygulama sınıfları yüklemek için.
Ravi Thapliyal

"Normal J2SE delegasyonu çalışmıyor", neden çalışmadığını bilebilir miyim? Bootstrap ClassLoader yalnızca rt.jar'dan sınıf yükleyebildiğinden ve uygulamayı uygulamanın -classpath öğesinden yükleyemediğinden? Sağ?
YuFeng Shen

37

@David Roussel yanıtına ek olarak, sınıflar birden çok sınıf yükleyicisi tarafından yüklenebilir.

Sınıf yükleyicinin nasıl çalıştığını anlayalım.

Jamaika'daki Javin Paul Blogundan:

resim açıklamasını buraya girin

resim açıklamasını buraya girin

ClassLoader üç prensibi izler.

Yetkilendirme ilkesi

Java gerektiğinde bir sınıf yüklenir. Abc.class adlı uygulamaya özel bir sınıfınız olduğunu varsayalım, bu sınıfı yüklemenin ilk isteği, Primordial veya Bootstrap sınıfı yükleyiciye daha fazla delege veren üst Extension ClassLoader'a temsil edecek Application ClassLoader'a gelecektir.

  • Bootstrap ClassLoader , standart JDK sınıfı dosyaları rt.jar'dan yüklemekle sorumludur ve Java'daki tüm sınıf yükleyicilerin üst öğesidir. Bootstrap sınıfı yükleyicinin ebeveynleri yok.

  • Extension ClassLoader sınıf yükleme isteğini üst öğesine, Bootstrap'e devreder ve başarısız olursa, sınıf formu jre / lib / ext dizinine veya java.ext.dirs system özelliğinin işaret ettiği başka bir dizine yükler

  • Sistem veya Uygulama sınıfı yükleyici ve CLASSPATH ortam değişkeni, -classpath veya -cp komut satırı seçeneği, JAR içindeki Manifest dosyasının Class-Path özniteliğinden uygulamaya özgü sınıfların yüklenmesinden sorumludur.

  • Uygulama sınıfı yükleyici , Extension ClassLoader öğesinin alt öğesidir ve sun.misc.Launcher$AppClassLoadersınıf tarafından uygulanır .

NOT: Çoğunlukla C dilinde yerel dilde uygulanan Bootstrap sınıfı yükleyici hariç , tüm Java sınıfı yükleyiciler kullanılarak uygulanır java.lang.ClassLoader.

Görünürlük İlkesi

Görünürlük ilkesine göre, Child ClassLoader , Parent ClassLoader tarafından yüklenen sınıfı görebilir , ancak tam tersi doğru değildir.

Benzersizlik İlkesi

Bu prensibe göre, Ebeveyn tarafından yüklenen bir sınıf, Child ClassLoader tarafından tekrar yüklenmemelidir

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.