SPRING BOOT SERIES - LẮP RÁP
Circular Dependency: Spring Cho Mượn Bean Chưa Lắp Xong Thế Nào?
BEAN LIFECYCLE - XANH = TẦNG PHÓNG TOEarly singleton exposure chỉ có thể nối một số vòng setter/field. Constructor cycle không có instance để cho mượn.
Constructor A -> B -> A
A chưa instantiate xong khi B đòi A; không có object reference nào để expose sớm.
Cùng một vòng thiết kếBa cache chỉ là cơ chế hoàn tất một số graph. Boot khóa circular references mặc định để vòng lộ ra lúc startup.
Setter/field A -> B -> A
Nếu cho phép circular reference, A có thể instantiate trước rồi được expose qua factory cho B.
Mức 1 - tạo singleton
AbstractAutowireCapableBeanFactory.doCreateBean()
Instantiate trước, populate và initialize sau
createBeanInstance()Constructor/factory method tạo raw instance.earlySingletonExposureChỉ true cho singleton đang creation vàallowCircularReferences.addSingletonFactory(...)Đặt factory có thể gọigetEarlyBeanReference().populateBean()Dependency injection diễn ra sau khi raw instance đã tồn tại.initializeBean()Aware callbacks, init methods và post-processors có thể tạo proxy.- Reconcile early referenceSpring kiểm tra object cho mượn có khớp exposed object cuối cùng hay không.
Mức 2 - ba tầng singleton cache
DefaultSingletonBeanRegistry.getSingleton()
Chỉ tra early reference khi bean đang creation
singletonObjectsTầng 1: singleton hoàn tất, lookup thông thường trả từ đây.earlySingletonObjectsTầng 2: early reference đã materialize và được cache.singletonFactoriesTầng 3: factory tạo early reference, có cơ hội trả proxy sớm.singletonFactory.getObject()Promotion factory -> early object xảy ra khi dependency thật sự đòi bean.addSingleton()Khi bean hoàn tất, đặt vào tầng 1 và xóa hai entry tạm.
Mức 3 - guardrail và lối ra
allowCircularReferences
Chạy được không đồng nghĩa graph đã an toàn
- Boot mặc định
falseTừ Boot 2.6, vòng bị từ chối trừ khi mở cờ rõ ràng. - Constructor cycle vẫn thất bạiChưa có raw instance để đặt factory trước khi resolve constructor argument.
@Lazyở injection pointInject một proxy trì hoãn lookup, không xóa dependency cycle trong thiết kế.- Raw/proxy mismatch có thể bị từ chốiBeanPostProcessor tạo wrapper muộn có thể làm dependent bean giữ raw object sai.
- Tách trách nhiệm CCho A và B cùng phụ thuộc abstraction/thành phần thứ ba, biến vòng thành graph có hướng rõ.
Phép kiểm: tạo riêng một vòng constructor và một vòng setter, breakpoint tại doCreateBean(), addSingletonFactory() và getSingleton(beanName, true). Quan sát bean name đi qua ba map và runtime class của early/final reference.
Gỡ vòng ở thiết kế
- Vẽ graph dependency có hướngTìm trách nhiệm hoặc port chung đang bị kẹp giữa A và B.
- Tách orchestration thành bean thứ baGiữ service lõi một chiều và test độc lập.
- Dùng event/callback khi thật sự bất đồng bộKhông đổi một lời gọi trực tiếp thành event chỉ để che vòng.
Khi KHÔNG được kết luận
- Field injection chữa circular dependencyNó chỉ cho cơ chế early exposure cơ hội nối vòng.
- Bật cờ nghĩa là Spring bảo đảm proxy đúngRaw injection và wrapping muộn vẫn có guardrail riêng.
@Lazyluôn là fix thiết kếNó trì hoãn lookup; coupling hai chiều vẫn tồn tại.