หมายเหตุ: คำต่อไปนี้เช่น "บริดจ์", "การแปลง" และ "การผูก" นั้นเป็นแนวคิดเดียวกัน
log4j-over-SLF4J และ SLF4J-LOG4J12 เป็นแพ็คเกจขวดสองขวดที่เกี่ยวข้องกับระบบบันทึก Java เมื่อพวกเขาปรากฏตัวภายใต้ classpath ในเวลาเดียวกันพวกเขาอาจทำให้เกิดข้อยกเว้นสแต็กล้น ข้อมูลข้อยกเว้นมีดังนี้ (ข้อความที่ตัดตอนมาจากเอกสารเว็บไซต์ทางการ SLF4J ที่ตรวจพบทั้ง log4j-over-slf4j.jar และ slf4j-log4j12.jar บนเส้นทางชั้นเรียน
ข้อยกเว้นในเธรด "Main" java.lang.stackoverflowerror
ที่ java.util.hashtable.containskey (hashtable.java:306)
ที่ org.apache.log4j.log4jloggerfactory.getLogger (log4jloggerfactory.java:36)
ที่ org.apache.log4j.logmanager.getlogger (logmanager.java:39)
ที่ org.slf4j.impl.log4jloggerfactory.getLogger (log4jloggerfactory.java:73)
ที่ org.slf4j.loggerfactory.getLogger (loggerfactory.java:249)
ที่ org.apache.log4j.category. <init> (category.java:53)
ที่ org.apache.log4j.logger .. <init> (logger.java:35)
ที่ org.apache.log4j.log4jloggerfactory.getLogger (log4jloggerfactory.java:39)
ที่ org.apache.log4j.logmanager.getlogger (logmanager.java:39)
ที่ org.slf4j.impl.log4jloggerfactory.getLogger (log4jloggerfactory.java:73)
ที่ org.slf4j.loggerfactory.getLogger (loggerfactory.java:249)
ที่ org.apache.log4j.category .. <init> (category.java:53)
ที่ org.apache.log4j.logger .. <init> (logger.java:35)
ที่ org.apache.log4j.log4jloggerfactory.getLogger (log4jloggerfactory.java:39)
ที่ org.apache.log4j.logmanager.getlogger (logmanager.java:39)
บรรทัดที่ตามมาละเว้น ...
ระบบการบันทึกที่มีอยู่
ก่อนที่จะวิเคราะห์เหตุผลเฉพาะสำหรับข้อยกเว้นนี้จำเป็นต้องเข้าใจระบบการบันทึก Java ที่มีอยู่อย่างรวดเร็ว รูปต่อไปนี้เป็นไดอะแกรมของระบบบันทึก Java ที่มีอยู่:
ภาพด้านบนไม่แม่นยำมาก แต่สามารถแสดงโครงสร้างหลักของระบบบันทึก Java ที่มีอยู่ได้อย่างชัดเจน ระบบการบันทึก Java สามารถแบ่งออกเป็นสามส่วน: อินเทอร์เฟซซุปเปอร์ซุ้มบริดจ์และการใช้งาน Log Framework
กรอบการบันทึก Java มีหลายประเภท สิ่งที่ง่ายที่สุดคือ java.util.logging ของ Java และคลาสสิกที่สุดคือ log4j ต่อมามีการเข้าสู่ระบบที่มีประสิทธิภาพที่ดีกว่า log4j ปรากฏขึ้นดังนั้นเฟรมเวิร์กบันทึกอื่น ๆ จึงไม่ได้ใช้กันมาก เป็นไปได้อย่างแน่นอนสำหรับแอปพลิเคชันที่จะใช้ APIs ของเฟรมเวิร์กบันทึกเฉพาะเหล่านี้โดยตรงเพื่อตอบสนองความต้องการเอาต์พุตบันทึก แต่เนื่องจาก APIs ระหว่างแต่ละเฟรมเวิร์กล็อกมักจะเข้ากันไม่ได้
ตัวเลือกที่สมเหตุสมผลมากกว่าการใช้ Log Framework API โดยตรงคือการใช้อินเตอร์เฟสด้านหน้าของ Log อินเทอร์เฟซ Log Facade จัดเตรียมชุดของ API ที่เป็นอิสระจากเฟรมเวิร์กบันทึกเฉพาะ แอปพลิเคชันสามารถแยกออกจากเฟรมเวิร์กบันทึกเฉพาะโดยใช้ API อิสระเหล่านี้ซึ่งคล้ายกับ JDBC อินเทอร์เฟซซุปเปอร์ที่เร็วที่สุดคือการบันทึกคอมมอนส์ แต่ส่วนที่ได้รับความนิยมมากที่สุดในปัจจุบันคือ SLF4J
อินเทอร์เฟซ Log Facade เองมักจะไม่มีความสามารถในการบันทึกจริง มันยังคงต้องเรียก Framework API ที่เฉพาะเจาะจงที่ด้านล่างนั่นคือจริง ๆ แล้วมันจำเป็นต้องใช้ร่วมกับเฟรมเวิร์กบันทึกเฉพาะ เนื่องจากมีเฟรมเวิร์กบันทึกเฉพาะจำนวนมากและส่วนใหญ่เข้ากันไม่ได้ซึ่งกันและกันหากอินเทอร์เฟซซุ้มล็อกถูกรวมเข้ากับเฟรมเวิร์กบันทึกใด ๆ จึงอาจต้องใช้บริดจ์ที่สอดคล้องกันเช่นเดียวกับการรวมกันระหว่าง JDBC และฐานข้อมูลต่าง ๆ ที่แตกต่างกันต้องใช้ไดรเวอร์ JDBC ที่สอดคล้องกัน
ควรสังเกตว่าดังที่ได้กล่าวไว้ก่อนหน้านี้ภาพด้านบนไม่ถูกต้องนี่เป็นเพียงส่วนหลักและสถานการณ์จริงไม่ได้เป็นบรรทัดทางเดียวทางเดียวของ "อินเตอร์เฟสด้านหน้าของบันทึก-> บริดจ์-> เฟรมเวิร์กบันทึก" ในความเป็นจริงบางครั้งสะพานอิสระไม่จำเป็นและมันไม่ได้เป็นเพียงสะพานที่แปลง API ของซองซองเป็น Log Framework API เฉพาะ นอกจากนี้ยังมีสะพานที่แปลง Log Framework API เป็น API Log Facade
ในการกล่าวอย่างตรงไปตรงมาว่า "สะพาน" ที่เรียกว่าไม่มีอะไรมากไปกว่าการใช้ APIs แบบหลอกชุดหนึ่ง การใช้งานนี้ไม่ได้ดำเนินการฟังก์ชั่นที่ประกาศโดย API โดยตรง แต่เรียก API อื่น ๆ ด้วยฟังก์ชั่นที่คล้ายกัน สิ่งนี้จะเสร็จสิ้นการเปลี่ยนจาก "ชุดของ APIs" เป็น "API อื่น ๆ " หากมีสะพานสองสะพาน A-to-b.jar และ b-to-a.jar คุณสามารถจินตนาการได้ว่าจะเกิดอะไรขึ้นเมื่อแอปพลิเคชันเริ่มเรียก API ของ A หรือ B นี่คือหลักการพื้นฐานของการยกเว้นสแต็กล้นที่แนะนำครั้งแรก
การส่งต่อ SLF4J การส่งต่อ
ข้างต้นเป็นเพียงคำอธิบายทั่วไปของระบบการบันทึก Java ที่มีอยู่และเป็นไปไม่ได้ที่จะอธิบายปัญหาในรายละเอียด เราจำเป็นต้องเข้าใจสถานการณ์การเชื่อมโยงระหว่าง SLF4J และกรอบบันทึกเฉพาะเพิ่มเติม
สะพาน SLF4J ไปยังเฟรมบันทึกเฉพาะ
ภาพต่อไปนี้มาจากเอกสารเว็บไซต์อย่างเป็นทางการของ SLF4J ที่มีผลผูกพันกับกรอบการบันทึกตามเวลาการปรับใช้:
คุณจะเห็นว่ามีโซลูชั่นมากมายสำหรับ SLF4J เพื่อรวมเข้ากับเฟรมเวิร์กบันทึกเฉพาะ แน่นอนชั้นบนสุด (เลเยอร์แอปพลิเคชันสีเขียว) ของแต่ละโซลูชันจะรวมเป็นหนึ่งเดียว พวกเขาเรียก API โดยตรงโดย SLF4J (เลเยอร์ API นามธรรมสีฟ้าอ่อน) และพึ่งพา SLF4J-API.JAR จากนั้นหากคุณทำ SLF4J API ลงมันจะฟรีมากและคุณสามารถใช้เฟรมเวิร์กการบันทึกที่เฉพาะเจาะจงได้เกือบทั้งหมด โปรดทราบว่าชั้นที่สองในรูปเป็นสีฟ้าอ่อน เมื่อมองไปที่ตำนานในมุมซ้ายล่างเราจะเห็นได้ว่าสิ่งนี้แสดงถึง API ที่เป็นนามธรรมซึ่งหมายความว่าพวกเขาไม่ได้ใช้งานที่เป็นรูปธรรม หากเลเยอร์ล่างไม่ได้รวมเข้ากับการใช้งาน Log Framework เฉพาะใด ๆ เช่นโซลูชันแรกทางด้านซ้ายบันทึกไม่สามารถส่งออกได้ ( ไม่แน่ใจว่าอาจเป็นเอาต์พุตไปยังเอาต์พุตมาตรฐานโดยค่าเริ่มต้น )
เลเยอร์ที่สามในภาพนั้นไม่เป็นระเบียบเท่าเลเยอร์แรกและชั้นที่สองเนื่องจากกรอบบันทึกเฉพาะได้เริ่มมีส่วนร่วมที่นี่
ก่อนอื่นมาดูบล็อกทะเลสาบสีน้ำเงินสองแห่งที่อยู่ตรงกลางของชั้นสามซึ่งเป็นชั้นอะแดปเตอร์นั่นคือสะพาน สะพาน SLF4J-log4j12.jar ทางด้านซ้ายสามารถบอกได้ว่ามันเป็นสะพานจาก SLF4J ถึง log4j ตามชื่อ ในทำนองเดียวกัน SLF4J-JDK14.JAR ทางด้านขวาคือสะพานที่ใช้โดย SLF4J ถึงบันทึกพื้นเมือง Java เลเยอร์ถัดไปของพวกเขาคือการใช้งาน Log Framework ที่สอดคล้องกัน รหัสการใช้งานของ log4j คือ log4j.jar และรหัสการใช้งานกรกฎาคมรวมอยู่ในรันไทม์ JVM และไม่จำเป็นต้องใช้แพ็คเกจ JAR แยกต่างหาก
มาดูบล็อกสีน้ำเงินเข้มสามบล็อกที่เหลืออยู่บนชั้นสาม ทั้งสามของพวกเขายังใช้การใช้งานกรอบการบันทึกเฉพาะ แต่พวกเขาไม่ต้องการสะพานเพราะพวกเขาใช้ SLF4J API โดยตรง ไม่จำเป็นต้องพูด, slf4j-simple.jar และ slf4j-nop.jar คุณสามารถบอกได้ว่าหนึ่งคือการใช้งานง่าย ๆ ของ SLF4J และอีกอันคือการใช้งาน SLF4J ที่ว่างเปล่าซึ่งไม่ได้มีประโยชน์มากในเวลาปกติ เหตุผลที่ Logback ยังใช้ SLF4J API นั้นเป็นเพราะการเข้าสู่ระบบและ SLF4J นั้นมาจากบุคคลเดียวกันซึ่งเป็นผู้เขียน Log4J ด้วย
แพ็คเกจขวดสีเทาทั้งหมดในชั้นที่สามมีกล่องสีแดงซึ่งหมายความว่าพวกเขาทั้งหมดใช้ SLF4J API โดยตรง อย่างไรก็ตามการใช้งานของ SLF4J API โดยสะพานทะเลสาบบลูไม่ได้บันทึกการส่งออกโดยตรง แต่เรียก API ของเฟรมเวิร์กบันทึกอื่น ๆ แทน
Framework Log Framework อื่น ๆ โทรกลับไปที่ SLF4J
หากมีเพียงสะพานข้างต้นจาก SFL4J ถึงเฟรมเวิร์กบันทึกอื่น ๆ อาจไม่มีปัญหาใด ๆ แต่ในความเป็นจริงมีสะพานอีกประเภทหนึ่งซึ่งมีฟังก์ชั่นตรงข้ามกับข้างต้นพวกเขาแปลง API ของกรอบบันทึกอื่น ๆ เป็น SLF4J API ภาพต่อไปนี้มาจากเว็บไซต์อย่างเป็นทางการของเว็บไซต์ SLF4J เอกสารสะพาน APIs มรดก:
รูปด้านบนแสดงทั้งสามกรณีที่สามารถโทรกลับไปยัง SLF4J ได้อย่างปลอดภัยจาก APIs Log Framework อื่น ๆ
การใช้กรณีแรกที่มุมซ้ายบนเป็นตัวอย่างเมื่อ SLF4J พื้นฐานถูกเชื่อมต่อกับเฟรมเวิร์ก logback, APIs Framework Framework ที่อนุญาตให้ชั้นบนเชื่อมโยงกลับไปยัง SLF4J รวมถึง log4j และ Jul แม้ว่า JCL ไม่ใช่การใช้งานเฉพาะของเฟรมเวิร์กการบันทึก แต่ API ของมันก็ยังสามารถเรียกกลับไปที่ SLF4J ได้ ในการใช้การแปลงวิธีการคือการแทนที่ขวดเฟรมบันทึกต้นฉบับด้วยขวดสะพานเฉพาะที่ระบุไว้ในรูป ควรสังเกตว่า LOGBACK API ไปยัง SLF4J API ไม่รวมถึงการแปลง LOGBACK API เนื่องจาก LOGBACK นั้นเป็นการใช้งาน SLF4J API เดิม
หลังจากอ่านสามสถานการณ์คุณจะพบว่า APIs เกือบทั้งหมดของเฟรมเวิร์กบันทึกอื่น ๆ รวมถึง JCL API สามารถเปลี่ยนเส้นทางกลับไปยัง SLF4J ได้ตามต้องการ แต่มีข้อ จำกัด เพียงอย่างเดียวที่เฟรมเวิร์กบันทึกที่โทรกลับไปยัง SLF4J ไม่สามารถเหมือนกันกับเฟรมเวิร์กบันทึกที่ SLF4J กำลังเชื่อมโยงอยู่ในปัจจุบัน ข้อ จำกัด นี้คือการป้องกันไม่ให้ A-to-b.jar และ b-to-a.jar ปรากฏตัวใน classpath ในเวลาเดียวกันส่งผลให้ A และ B เรียกกันและกันอย่างต่อเนื่องและในที่สุดก็สแต็กล้น ในปัจจุบันข้อ จำกัด นี้ไม่ได้รับการรับประกันด้วยเทคโนโลยี แต่โดยนักพัฒนาเองเท่านั้น นี่คือเหตุผลที่เว็บไซต์อย่างเป็นทางการของ SLF4J เน้นว่าวิธีการที่สมเหตุสมผลทั้งหมดเป็นเพียงในสามสถานการณ์ในภาพด้านบน
เมื่อมาถึงจุดนี้หลักการของข้อยกเว้นที่แสดงในตอนแรกนั้นชัดเจน นอกจากนี้จากรูปด้านบนเราจะเห็นว่าอาจมีชุดค่าผสมคล้ายกับข้อยกเว้นไม่เพียง แต่ log4j-over-SLF4J และ SLF4J-LOG4J12 เว็บไซต์อย่างเป็นทางการของ SLF4J ยังชี้ให้เห็นคู่อื่น: JCL-Over-SLF4J.JAR และ SLF4J-JCL.JAR
ตัวอย่างรหัส
การวิเคราะห์ก่อนหน้านี้เป็นทฤษฎี แม้ว่า log4j-over-SLF4J และ SLF4J-LOG4J12 จะใช้ในเวลาเดียวกันในรหัสจริงข้อยกเว้นอาจไม่จำเป็นต้องเกิดขึ้น รหัสต่อไปนี้เรียก API ของ SLF4J เพื่อส่งออกบันทึกและ SLF4J จะเชื่อมโยงไปยัง log4j:
การทดสอบแพ็คเกจ; คลาสสาธารณะ HelloWorld {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {org.apache.log4j.basicconfigurator.configure (); org.slf4j.logger logger = org.slf4j.loggerfortory.getloggerกำหนดค่า classPath เพื่อกำหนดค่าแพ็คเกจ JAR (โปรดทราบว่า log4j เป็นก่อน log4j-over-SLF4J):
ในกรณีนี้การเรียกใช้โปรแกรมทดสอบสามารถส่งออกบันทึกได้ตามปกติและจะไม่มีข้อยกเว้นสแต็กล้น แต่ถ้าคุณปรับคำสั่ง jar บน classpath เป็น:
การรันโปรแกรมทดสอบอีกครั้งจะทำให้เกิดข้อยกเว้นสแต็กล้นคล้ายกับสแต็คเริ่มต้นล้นในบทความนี้ คุณสามารถเห็นการทำซ้ำเป็นระยะที่ชัดเจน:
การวิเคราะห์แผนภูมิลำดับ
ภาพด้านบนเป็นแผนภาพคอลัมน์โปรแกรมการโทรอย่างละเอียดของสแต็คล้น เริ่มต้นจากการโทร 1 โทร 1.1, 1.1.1 ... ในที่สุดเมื่อถึง 1.1.1.1.1 (การโทรครั้งสุดท้ายในรูป) พบว่ามันเหมือนกับ 1 ดังนั้นกระบวนการที่ตามมาจะถูกทำซ้ำอย่างสมบูรณ์
ควรสังเกตว่าฟิวส์เริ่มต้นไม่เพียง แต่ loggerFactory.getLogger () ที่แสดงในรูป มีการโทรโดยตรงอื่น ๆ อีกหลายครั้งในแอปพลิเคชันที่สามารถเรียกใช้สแต็กล้น ตัวอย่างเช่นในรหัสตัวอย่างก่อนหน้านี้คำสั่งแรก org.apache.log4j.basicconfigurator.configure () เป็นจริงจริง ๆ แล้วคำสั่งแรก org.apache.log4j.basicconfigurator.configure ()
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น