ป้ายกำกับ: Parsing

Java: error จาก bean ซ้ำJava: error จาก bean ซ้ำ

เพื่อให้เห็นภาพรวมที่ชัดเจนก่อนจะไปลงมือเขียนสคริปต์ เราต้องเข้าใจ “กลไกการทำงาน” ของ Spring Bean และ “ลำดับการเกิดความขัดแย้ง” ในระบบที่มีความซับซ้อนสูง ( Enterprise System ) นี่คือบทความเจาะลึกทฤษฎี Bean Collision ( การชนกันของ Bean ) ใน Spring Framework ครับ


📖 เจาะลึกทฤษฎี: กลไกการจัดการ Bean และสาเหตุการเกิดข้อผิดพลาดซ้ำซ้อน

ในระบบ Spring Boot, ApplicationContext เปรียบเสมือน “ตู้เก็บของอัจฉริยะ” ที่เก็บ Object ( ซึ่งเราเรียกว่า Bean ) ไว้ให้ส่วนต่าง ๆ ของโปรแกรมเรียกใช้งาน แต่ตู้ใบนี้มีกฎเหล็กอยู่ว่า “ในหนึ่งประเภท ( Type ) หรือหนึ่งชื่อ ( ID ) ควรจะมีของเพียงชิ้นเดียวเท่านั้น”

ลำดับการสร้าง Bean ( Bean Lifecycle & Registration )

เมื่อเรา Start Application, Spring จะทำสิ่งที่เรียกว่า Component Scanning และ Configuration Parsing

  1. Scan Annotations: มองหาคลาสที่ติด @Component, @Service, @RestController
  2. Read XML Config: อ่านไฟล์ XML ( เช่น spring-jdbc-xxx.xml ที่คุณระบุใน launch.json )
  3. Process Java Config: อ่าน Method ที่ติด @Bean ในคลาส @Configuration

ทำไมถึงเกิดการ “ชนกัน” ( Conflict Scenarios )

ความผิดพลาดมักเกิดจาก 3 สถานการณ์หลัก ดังนี้

  • การชนกันด้วย “ชื่อ” ( Bean Name Collision )
    ตามปกติ Spring จะตั้งชื่อ Bean จากชื่อคลาสโดยเปลี่ยนตัวแรกเป็นตัวเล็ก ( เช่น คลาส UserService จะได้ชื่อ Bean ว่า userService )
    • ปัญหา: หากคุณมี com.packageA.Utility และ com.packageB.Utility อยู่ในโปรเจกต์เดียวกัน Spring จะพยายามสร้าง Bean ชื่อ utility ทั้งคู่ ทำให้ระบบระเบิดทันที
  • การชนกันด้วย “ประเภท” ( Dependency Type Ambiguity )
    เกิดเมื่อคุณมี Interface หนึ่งอัน แต่มี Class ที่ Implement มันมากกว่าหนึ่งตัว
    • ตัวอย่าง: มี Interface PaymentGateway และมีคลาส KBankType กับ SCBType ที่ติด @Service ทั้งคู่
    • ปัญหา: เมื่อคุณ @Autowired PaymentGateway ระบบจะไม่รู้ว่าคุณต้องการธนาคารไหน
  • การชนกันระหว่าง “โลกเก่า ( XML )” และ “โลกใหม่ ( Annotation )”
    นี่คือสาเหตุที่พบบ่อยที่สุดในโปรเจกต์ระดับ Enterprise
    • ปัญหา: ใน Java โค้ดประกาศ @Service("myLog") ไว้แล้ว แต่ในไฟล์ XML เก่าดันมี <bean id="myLog" class="..."> อีกตัวหนึ่ง เมื่อ Spring โหลดทั้งสองอย่างเข้ามาพร้อมกันจึงเกิดการแย่งชิง ID นั้นๆ

วิธีการ “ตรวจจับ” ( Identification Techniques )

ก่อนจะแก้ เราต้องรู้วิธี “บีบวง” ให้แคบลง

  1. Strict Mode ( Fail-Fast ): การตั้งค่า spring.main.allow-bean-definition-overriding=false คือการบอกระบบว่า “ถ้าเจอของซ้ำ ให้หยุดทำงานทันที” วิธีนี้ดีที่สุดในการ Debug เพราะ Log จะบอกชื่อไฟล์ทั้ง 2 จุดที่ชนกันอย่างละเอียด
  2. Context Analysis: ใช้การสแกนหาคำหลัก ( Keywords ) ในซอร์สโค้ด:
  3. หาคลาสที่ชื่อซ้ำกันในต่าง Package
  4. หา ID ที่ซ้ำกันในไฟล์ .xml
  5. หาการประกาศ @Bean ที่คืนค่า Type เดียวกัน

วิธีการ “แก้ไข” ( Resolution Strategies )

เมื่อเจอจุดที่ชนกันแล้ว เรามีกลยุทธ์การแก้ 4 ระดับ

ระดับวิธีการคำอธิบาย
1. Explicit Naming@Service("uniqueName")ระบุชื่อ Bean ให้ชัดเจน ไม่พึ่งพาระบบตั้งชื่ออัตโนมัติ
2. Primary Marking@Primaryบอกว่าถ้ามีซ้ำ ให้เลือกตัวนี้เป็นตัวหลัก ( Default )
3. Specific Wiring@Qualifier("name")ตอนเรียกใช้ ให้ระบุไปเลยว่าจะเอาตัวชื่อไหน
4. Exclusionexcludeสั่งให้ Spring ไม่ต้องสแกนใน Package หรือไฟล์ XML ที่ไม่ได้ใช้งาน

ข้อควรระวัง

ถ้าโปรเจกต์ของคุณมีการใช้ launch.json เพื่อโหลด XML หลายตัวพร้อมกัน

  • ความเสี่ยง: การเปลี่ยนชื่อ Bean ใน Java อาจทำให้ XML ที่อ้างอิงถึงชื่อนั้นพัง ( Runtime Error )
  • การป้องกัน: “Snapshot & Backup” จึงสำคัญมาก เพราะการแก้ Bean Collision ในระบบ Multi-module อาจมี Side-effect ข้าม Module ได้
  • การแก้ Bean ซ้ำ ไม่ใช่แค่การลบตัวใดตัวหนึ่งออก แต่คือการ “จัดระเบียบ ID และ Type” ให้ Spring Container เข้าใจทิศทางที่ถูกต้องครับ

อ่านเพิ่มเติม