Java, Classpath, Classloading => หลายเวอร์ชันของ jar / project เดียวกัน


119

ฉันรู้ว่านี่อาจเป็นคำถามโง่ ๆ สำหรับนักเขียนโค้ดที่มีประสบการณ์ แต่ฉันมีไลบรารี (ไคลเอนต์ http) ที่เฟรมเวิร์ก / ไหอื่น ๆ ที่ใช้ในโครงการของฉันต้องการ แต่ทั้งหมดนั้นต้องการเวอร์ชันหลักที่แตกต่างกันเช่น:

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

classloader ฉลาดพอที่จะแยกพวกมันออกหรือไม่? เป็นไปได้มากที่สุด? Classloader จัดการกับสิ่งนี้อย่างไรในกรณีที่ Class เหมือนกันในทั้งสามขวด โหลดอันไหนและเพราะอะไร

Classloader รับเพียงหนึ่งโถหรือผสมคลาสโดยพลการ? ตัวอย่างเช่นถ้าคลาสถูกโหลดจาก Version-1.jar คลาสอื่น ๆ ทั้งหมดที่โหลดจาก classloader เดียวกันทั้งหมดจะไปอยู่ใน jar เดียวกันหรือไม่

คุณจัดการกับปัญหานี้อย่างไร?

มีเคล็ดลับในการ "รวม" ไหลงใน "required.jar" เพื่อให้เห็นเป็น "หนึ่งหน่วย / แพ็กเกจ" Classloaderหรือเชื่อมโยงอย่างใด

คำตอบ:


58

ปัญหาที่เกี่ยวข้องกับ Classloader เป็นเรื่องที่ค่อนข้างซับซ้อน ไม่ว่าในกรณีใดคุณควรคำนึงถึงข้อเท็จจริงบางประการ:

  • Classloaders ในแอปพลิเคชันมักจะเป็นมากกว่าหนึ่งเดียว ตัวโหลดคลาส bootstrap มอบหมายให้เหมาะสม เมื่อคุณสร้างอินสแตนซ์คลาสใหม่จะมีการเรียกตัวโหลดคลาสที่เฉพาะเจาะจงมากขึ้น หากไม่พบการอ้างอิงถึงคลาสที่คุณพยายามโหลดมันจะมอบหมายให้กับพาเรนต์และอื่น ๆ จนกว่าคุณจะไปที่ตัวโหลดคลาส bootstrap หากไม่พบการอ้างอิงถึงคลาสที่คุณพยายามโหลดคุณจะได้รับ ClassNotFoundException

  • หากคุณมีสองคลาสที่มีชื่อไบนารีเดียวกันสามารถค้นหาได้โดย classloader เดียวกันและคุณต้องการทราบว่าคลาสใดที่คุณกำลังโหลดคุณสามารถตรวจสอบวิธีที่ classloader ที่ระบุพยายามแก้ไขชื่อคลาสเท่านั้น

  • ตามข้อกำหนดภาษา java ไม่มีข้อ จำกัด ด้านความเป็นเอกลักษณ์สำหรับชื่อไบนารีคลาส แต่เท่าที่ฉันเห็นมันควรจะไม่ซ้ำกันสำหรับแต่ละ classloader

ฉันสามารถหาวิธีโหลดคลาสสองคลาสที่มีชื่อไบนารีเดียวกันได้และเกี่ยวข้องกับการโหลดคลาส (และการอ้างอิงทั้งหมด) โดยคลาสโหลดเดอร์สองคลาสที่แตกต่างกันซึ่งจะลบล้างพฤติกรรมเริ่มต้น ตัวอย่างคร่าวๆ:

    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);

ฉันมักจะพบว่าการปรับแต่ง classloader เป็นงานที่ยุ่งยาก ฉันอยากจะแนะนำให้หลีกเลี่ยงการอ้างอิงที่เข้ากันไม่ได้หลายอย่างถ้าเป็นไปได้


14
ตัวโหลดคลาส bootstrap มอบหมายให้เหมาะสม เมื่อคุณสร้างอินสแตนซ์คลาสใหม่จะมีการเรียกตัวโหลดคลาสที่เฉพาะเจาะจงมากขึ้น หากไม่พบการอ้างอิงถึงคลาสที่คุณพยายามโหลดมันจะมอบสิทธิ์ให้กับพาเรนต์ได้ โปรดอดทนกับฉัน แต่ขึ้นอยู่กับนโยบาย classloader ซึ่งเป็นค่าเริ่มต้น Parent First กล่าวอีกนัยหนึ่งคลาสย่อยจะขอให้พาเรนต์โหลดคลาสก่อนและจะโหลดก็ต่อเมื่อลำดับชั้นทั้งหมดไม่สามารถโหลดได้ไม่ใช่ ??
deckingraj

5
ไม่ - โดยทั่วไปแล้ว classloader จะมอบหมายให้กับพาเรนต์ก่อนที่จะค้นหาคลาสนั้นเอง ดูคลาส javadoc สำหรับ Classloader
Joe Kearney

1
ฉันคิดว่าแมวตัวผู้ทำในลักษณะที่อธิบายไว้ที่นี่ แต่การมอบหมายแบบ "ธรรมดา" คือการถามผู้ปกครองก่อน
rogerdpack

@deckingraj: หลังจาก googling ฉันพบสิ่งนี้จาก oracle docs: "ในการออกแบบการมอบหมายคลาสตัวโหลดจะมอบหมายการโหลดคลาสให้กับพาเรนต์ก่อนที่จะพยายามโหลดคลาสเอง [... ] หากตัวโหลดคลาสพาเรนต์ไม่สามารถโหลดคลาสได้ ตัวโหลดคลาสพยายามโหลดคลาสตัวเองผลคือคลาสโหลดเดอร์มีหน้าที่โหลดเฉพาะคลาสที่ไม่พร้อมใช้งานสำหรับพาเรนต์ " ฉันจะตรวจสอบเพิ่มเติม หากสิ่งนี้จะปรากฏเป็นการใช้งานเริ่มต้นฉันจะอัปเดตการตอบสนองตามนั้น ( docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html )
Luca Putzu

20

แต่ละ classload เลือกหนึ่งคลาส โดยปกติจะพบครั้งแรก

OSGiมีจุดมุ่งหมายเพื่อแก้ปัญหาของโถเดียวกันหลายเวอร์ชัน EquinoxและApache Felixเป็นการนำโอเพนซอร์สมาใช้งานทั่วไปสำหรับ OSGi


6

Classloader จะโหลดคลาสจาก jar ที่อยู่ใน classpath ก่อน โดยปกติไลบรารีเวอร์ชันที่เข้ากันไม่ได้จะมีความแตกต่างในแพ็กเกจ แต่ในกรณีที่ไม่น่าจะเข้ากันไม่ได้จริง ๆ และไม่สามารถแทนที่ด้วย jarjar ได้


6

Classloaders โหลดคลาสตามต้องการ ซึ่งหมายความว่าคลาสที่แอปพลิเคชันของคุณต้องการก่อนและไลบรารีที่เกี่ยวข้องจะถูกโหลดก่อนคลาสอื่น ๆ โดยทั่วไปแล้วการร้องขอเพื่อโหลดคลาสที่ขึ้นต่อกันจะออกในระหว่างขั้นตอนการโหลดและการเชื่อมโยงของคลาสที่ขึ้นอยู่

คุณมักจะพบLinkageErrorว่ามีการระบุคำจำกัดความคลาสที่ซ้ำกันสำหรับ classloaders โดยทั่วไปจะไม่พยายามระบุว่าควรโหลดคลาสใดก่อน (หากมีคลาสที่มีชื่อเดียวกันสองคลาสขึ้นไปอยู่ในคลาสพา ธ ของตัวโหลด) บางครั้ง classloader จะโหลดคลาสแรกที่เกิดขึ้นใน classpath และละเว้นคลาสที่ซ้ำกัน แต่ขึ้นอยู่กับการใช้งานตัวโหลด

แนวทางปฏิบัติที่แนะนำเพื่อแก้ไขข้อผิดพลาดประเภทนี้คือการใช้ classloader แยกต่างหากสำหรับไลบรารีแต่ละชุดที่มีการอ้างอิงที่ขัดแย้งกัน ด้วยวิธีนี้หาก classloader พยายามโหลดคลาสจากไลบรารีคลาสที่ขึ้นต่อกันจะถูกโหลดโดย classloader เดียวกันที่ไม่มีการเข้าถึงไลบรารีและการอ้างอิงอื่น ๆ


1

คุณสามารถใช้URLClassLoaderfor require เพื่อโหลดคลาสจาก jar เวอร์ชัน diff-2:

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();
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.