เพื่อให้เห็นภาพรวมที่ชัดเจนก่อนจะไปลงมือเขียนสคริปต์ เราต้องเข้าใจ “กลไกการทำงาน” ของ Spring Bean และ “ลำดับการเกิดความขัดแย้ง” ในระบบที่มีความซับซ้อนสูง นี่คือบทความเจาะลึกทฤษฎี Bean Collision ใน Spring Framework ครับ
📖 เจาะลึกทฤษฎี: กลไกการจัดการ Bean และสาเหตุการเกิดข้อผิดพลาดซ้ำซ้อน
ในระบบ Spring Boot, ApplicationContext เปรียบเสมือน “ตู้เก็บของอัจฉริยะ” ที่เก็บ Object ไว้ให้ส่วนต่าง ๆ ของโปรแกรมเรียกใช้งาน แต่ตู้ใบนี้มีกฎเหล็กอยู่ว่า “ในหนึ่งประเภท หรือหนึ่งชื่อ ควรจะมีของเพียงชิ้นเดียวเท่านั้น”
ลำดับการสร้าง Bean
เมื่อเรา Start Application, Spring จะทำสิ่งที่เรียกว่า Component Scanning และ Configuration Parsing
- Scan Annotations: มองหาคลาสที่ติด
@Component,@Service,@RestController - Read XML Config: อ่านไฟล์ XML
- Process Java Config: อ่าน Method ที่ติด
@Beanในคลาส@Configuration
ทำไมถึงเกิดการ “ชนกัน”
ความผิดพลาดมักเกิดจาก 3 สถานการณ์หลัก ดังนี้
- การชนกันด้วย “ชื่อ”
ตามปกติ Spring จะตั้งชื่อ Bean จากชื่อคลาสโดยเปลี่ยนตัวแรกเป็นตัวเล็ก- ปัญหา: หากคุณมี
com.packageA.Utilityและcom.packageB.Utilityอยู่ในโปรเจกต์เดียวกัน Spring จะพยายามสร้าง Bean ชื่อutilityทั้งคู่ ทำให้ระบบระเบิดทันที
- ปัญหา: หากคุณมี
- การชนกันด้วย “ประเภท”
เกิดเมื่อคุณมี Interface หนึ่งอัน แต่มี Class ที่ Implement มันมากกว่าหนึ่งตัว- ตัวอย่าง: มี Interface
PaymentGatewayและมีคลาสKBankTypeกับSCBTypeที่ติด@Serviceทั้งคู่ - ปัญหา: เมื่อคุณ
@Autowired PaymentGatewayระบบจะไม่รู้ว่าคุณต้องการธนาคารไหน
- ตัวอย่าง: มี Interface
- การชนกันระหว่าง “โลกเก่า ” และ “โลกใหม่ “
นี่คือสาเหตุที่พบบ่อยที่สุดในโปรเจกต์ระดับ Enterprise- ปัญหา: ใน Java โค้ดประกาศ
@Service ("myLog")ไว้แล้ว แต่ในไฟล์ XML เก่าดันมี<bean id="myLog" class="...">อีกตัวหนึ่ง เมื่อ Spring โหลดทั้งสองอย่างเข้ามาพร้อมกันจึงเกิดการแย่งชิง ID นั้น ๆ
- ปัญหา: ใน Java โค้ดประกาศ
วิธีการ “ตรวจจับ”
ก่อนจะแก้ เราต้องรู้วิธี “บีบวง” ให้แคบลง
- Strict Mode : การตั้งค่า
spring.main.allow-bean-definition-overriding=falseคือการบอกระบบว่า “ถ้าเจอของซ้ำ ให้หยุดทำงานทันที” วิธีนี้ดีที่สุดในการ Debug เพราะ Log จะบอกชื่อไฟล์ทั้ง 2 จุดที่ชนกันอย่างละเอียด - Context Analysis: ใช้การสแกนหาคำหลัก ในซอร์สโค้ด:
- หาคลาสที่ชื่อซ้ำกันในต่าง Package
- หา ID ที่ซ้ำกันในไฟล์
.xml - หาการประกาศ
@Beanที่คืนค่า Type เดียวกัน
วิธีการ “แก้ไข”
เมื่อเจอจุดที่ชนกันแล้ว เรามีกลยุทธ์การแก้ 4 ระดับ
| ระดับ | วิธีการ | คำอธิบาย |
| 1. Explicit Naming | @Service ("uniqueName") | ระบุชื่อ Bean ให้ชัดเจน ไม่พึ่งพาระบบตั้งชื่ออัตโนมัติ |
| 2. Primary Marking | @Primary | บอกว่าถ้ามีซ้ำ ให้เลือกตัวนี้เป็นตัวหลัก |
| 3. Specific Wiring | @Qualifier ("name") | ตอนเรียกใช้ ให้ระบุไปเลยว่าจะเอาตัวชื่อไหน |
| 4. Exclusion | exclude | สั่งให้ Spring ไม่ต้องสแกนใน Package หรือไฟล์ XML ที่ไม่ได้ใช้งาน |
ข้อควรระวัง
ถ้าโปรเจกต์ของคุณมีการใช้ launch.json เพื่อโหลด XML หลายตัวพร้อมกัน
- ความเสี่ยง: การเปลี่ยนชื่อ Bean ใน Java อาจทำให้ XML ที่อ้างอิงถึงชื่อนั้นพัง
- การป้องกัน: “Snapshot & Backup” จึงสำคัญมาก เพราะการแก้ Bean Collision ในระบบ Multi-module อาจมี Side-effect ข้าม Module ได้
- การแก้ Bean ซ้ำ ไม่ใช่แค่การลบตัวใดตัวหนึ่งออก แต่คือการ “จัดระเบียบ ID และ Type” ให้ Spring Container เข้าใจทิศทางที่ถูกต้องครับ
อ่านเพิ่มเติม