Eclipse / OSGI, Java 11, JAXB และ Classloader

Nov 23 2020

ฉันมีโมดูล Java สองโมดูล A และ B A จัดเตรียมโมเดลหลักที่มีคำอธิบายประกอบ JAXB และคลาสตัวช่วยสำหรับการสร้างการทำสิ่งต่างๆของ JAXB (การสร้างบริบทการมาร์แชลการยกเลิกการแช่ง ฯลฯ ) B จัดเตรียมคลาสเพิ่มเติมที่รวมอยู่ในโมเดลผ่านทาง @ XmlAnyElement (lax = true) และต้องถูกเพิ่มในบริบท JAXB

สิ่งนี้ใช้ได้ดีใน Java ธรรมดา - classloader ของ B จะเห็นคลาสที่เกี่ยวข้องทั้งหมดและสามารถสร้างอินสแตนซ์บริบท JAXB ด้วย:

JAXBContext.newInstance(RootFromA.class, RootFromB.class)

ตอนนี้ฉันกำลังลองแบบเดียวกันกับ OSGI (B เป็นปลั๊กอิน Eclipse และ A คือไลบรารีหลักที่จะใช้โดยโมดูลบรรทัดคำสั่ง Java ธรรมดา C) หลังจากลองผิดลองถูกมากมายฉันได้รับ A และ B เพื่อดูทั้ง JAXB API และการใช้งานผ่านการนำเข้าแพ็คเกจ OSGI ปัญหาคือการเรียก newInstance ข้างต้นดูเหมือนว่าจะใช้ classloader ของ JAXB API ไม่ใช่หนึ่งใน RootFromA และไม่ใช่หนึ่งใน RootFromB ด้วยเหตุนี้จึงไม่เห็นการใช้งาน JAXB และบ่นว่าไม่พบคลาส ContextFactory

ฉันได้จัดการเพื่อแก้ไขปัญหานี้โดยเรียก newInstance เวอร์ชันอื่น:

JAXBContext.newInstance(
  RootFromA.class.getPackageName()
    + ":" + RootFromB.class.getPackageName(),
  RootFromB.class.getClassLoader())

ฉันไม่ชอบสิ่งนี้ด้วยเหตุผลสองประการ:

  1. รหัส "ไคลเอนต์" ของฉัน (B ซึ่งเป็นไคลเอนต์ของตัวช่วย JAXB ใน A) ต้องจัดหา classloader ที่เหมาะสมด้วยตนเอง
  2. ฉันต้องจัดเตรียมไฟล์ jaxb.index ในแพ็กเกจที่อ้างอิงทั้งหมดที่แสดงรายการคลาสบริบทแม้ว่าโค้ดของฉันจะรับรู้อย่างสมบูรณ์และได้รับชื่อแพ็กเกจจากคลาส

อาจไม่มีวิธีใด ๆ (1) เนื่องจากมีเพียง B เท่านั้นที่รู้ชุดคลาสทั้งหมดและสามารถตัดสินใจได้ว่าใครจะสามารถดูคลาสโหลดเดอร์ทั้งหมดได้ ฉันกังวลว่าฉันอาจประสบปัญหามากขึ้นเมื่อฉันเพิ่มโมดูลส่วนขยาย C และ D ที่เกี่ยวกับ B ผ่านจุดขยาย Eclipse และจัดเตรียมคลาสเพิ่มเติมสำหรับบริบท JAXB - classloader ของ B จะสามารถมองเห็นสิ่งเหล่านี้ได้หรือไม่

แต่ฉันจะหาวิธีกำจัดไฟล์ดัชนีคงที่ที่จำเป็นสำหรับ (2) จริงๆ ชุดคลาสบริบททั้งหมดเป็นแบบไดนามิกและตัดสินใจใน Java ธรรมดาโดย ServiceLoader และใน Eclipse โดยจุดขยาย ในทั้งสองกรณีฉันสามารถเข้าถึงชุดคลาสทั้งหมดที่ควรเป็นของบริบทได้โดยตรงดังนั้นจึงต้องพิจารณาว่าจะต้องเพิ่มไฟล์ jaxb.index ลงในแต่ละแพ็กเกจซ้ำซ้อนด้วยตนเองดังนั้นจึงเป็นแหล่งที่มาของข้อผิดพลาดที่อาจเกิดขึ้นและไม่จำเป็น

ฉันพลาดอะไรไปรึเปล่า? มีวิธีที่ "ดีกว่า" ในการทำเช่นนี้หรือไม่? และเหตุใดจึงไม่มีเมธอด newInstance ที่ใช้ชุดของคลาสบริบทและ classloader?

คำตอบ

MarianSchedenig Nov 26 2020 at 01:55

ดูเหมือนว่าฉันพบวิธีแก้ปัญหาแล้ว ปัญหาของฉันคือต้องจัดการกับ classloaders เพื่อวัตถุประสงค์สองอย่างที่แตกต่างกัน:

  1. classloader ที่ JAXB ใช้เพื่อสร้างอินสแตนซ์บริบท
  2. classloaders ของคลาสโมเดลต่างๆของฉัน

ตามที่อธิบายไว้ข้างต้น (1) สามารถระบุเป็นพารามิเตอร์ใน JAXBContext.newInstance () ได้ แต่เฉพาะเมื่อระบุโมเดลเป็นชื่อแพ็กเกจแทนที่จะเป็นคลาสโมเดลแต่ละคลาส ซึ่งหมายความว่า JAXB ต้องค้นหาคลาสด้วยตัวเองและเป็น classloader ตัวเดียวที่สามารถใช้ทำเช่นนั้นได้คือ (1) ซึ่งไม่สามารถมองเห็นคลาสโมเดลทั้งหมดได้หากมีการกระจายไปตามกลุ่มต่างๆ

เธรดอื่น ( เหตุใด JAXB ไม่พบ jaxb.index ของฉันเมื่อทำงานใน Apache Felix ) สอนฉันว่า JAXB เริ่มต้นโดยใช้คลาสตัวโหลดบริบทเธรดเพื่อค้นหาการนำบริบทไปใช้ การตั้งค่าให้กับ classloader ที่มองเห็นการนำไปใช้งาน (เช่นของบันเดิลของฉันเอง) นั้นเพียงพอที่จะทำให้ JAXB พบการใช้งานของตัวเองซึ่งทำให้ฉันสามารถเรียก newInstance () เวอร์ชันที่ต้องการได้ด้วยอาร์เรย์ของคลาส และเนื่องจากคลาสเหล่านี้ได้รับการโหลดแล้ว JAXB จึงสามารถใช้งานได้ตามที่เป็นอยู่และไม่ต้องสนใจเกี่ยวกับตัวโหลดคลาสต่างๆ

ในระยะสั้น:

Class[] myClasses = getModelClasses(); // gather all model classes, which may have different classloaders
Thread thread = Thread.currentThread();
ClassLoader originalClassLoader = thread.getContextClassLoader();
thread.setContextClassLoader(getClass().getClassLoader()); // my own bundle's classloader
JAXBContext context = JAXBContext.newInstance(myClasses);
thread.setContextClassLoader(originalClassLoader); // reset context classloader

เนื้อหาการจัดการตัวโหลดบริบทสามารถรวมอยู่ใน AutoCloseable ทำให้ฉันสามารถรวมอินสแตนซ์ JAXBContext ไว้ในบล็อก try-with-resources ได้