เพื่อให้เห็นภาพรวมที่ชัดเจนก่อนจะไปลงมือเขียนสคริปต์ เราต้องเข้าใจ “กลไกการทำงาน” ของ 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
- Scan Annotations: มองหาคลาสที่ติด
@Component,@Service,@RestController - Read XML Config: อ่านไฟล์ XML ( เช่น
spring-jdbc-xxx.xmlที่คุณระบุในlaunch.json) - 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ระบบจะไม่รู้ว่าคุณต้องการธนาคารไหน
- ตัวอย่าง: มี Interface
- การชนกันระหว่าง “โลกเก่า ( XML )” และ “โลกใหม่ ( Annotation )”
นี่คือสาเหตุที่พบบ่อยที่สุดในโปรเจกต์ระดับ Enterprise- ปัญหา: ใน Java โค้ดประกาศ
@Service("myLog")ไว้แล้ว แต่ในไฟล์ XML เก่าดันมี<bean id="myLog" class="...">อีกตัวหนึ่ง เมื่อ Spring โหลดทั้งสองอย่างเข้ามาพร้อมกันจึงเกิดการแย่งชิง ID นั้นๆ
- ปัญหา: ใน Java โค้ดประกาศ
วิธีการ “ตรวจจับ” ( Identification Techniques )
ก่อนจะแก้ เราต้องรู้วิธี “บีบวง” ให้แคบลง
- Strict Mode ( Fail-Fast ): การตั้งค่า
spring.main.allow-bean-definition-overriding=falseคือการบอกระบบว่า “ถ้าเจอของซ้ำ ให้หยุดทำงานทันที” วิธีนี้ดีที่สุดในการ Debug เพราะ Log จะบอกชื่อไฟล์ทั้ง 2 จุดที่ชนกันอย่างละเอียด - Context Analysis: ใช้การสแกนหาคำหลัก ( Keywords ) ในซอร์สโค้ด:
- หาคลาสที่ชื่อซ้ำกันในต่าง Package
- หา ID ที่ซ้ำกันในไฟล์
.xml - หาการประกาศ
@Beanที่คืนค่า Type เดียวกัน
วิธีการ “แก้ไข” ( Resolution Strategies )
เมื่อเจอจุดที่ชนกันแล้ว เรามีกลยุทธ์การแก้ 4 ระดับ
| ระดับ | วิธีการ | คำอธิบาย |
| 1. Explicit Naming | @Service("uniqueName") | ระบุชื่อ Bean ให้ชัดเจน ไม่พึ่งพาระบบตั้งชื่ออัตโนมัติ |
| 2. Primary Marking | @Primary | บอกว่าถ้ามีซ้ำ ให้เลือกตัวนี้เป็นตัวหลัก ( Default ) |
| 3. Specific Wiring | @Qualifier("name") | ตอนเรียกใช้ ให้ระบุไปเลยว่าจะเอาตัวชื่อไหน |
| 4. Exclusion | exclude | สั่งให้ 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 เข้าใจทิศทางที่ถูกต้องครับ
อ่านเพิ่มเติม