SPRING SERIES - LỖI IM LẶNG
AOP Proxy, @Configuration Và Lời Gọi Nội Bộ
* đánh dấu điểm bài viết khai thác.
Spring AOP - proxy tách target
Advice của @Transactional bị bỏ qua qua this.save()
this đang trỏ vào object nào? Câu trả lời được chốt bởi thời điểm Spring can thiệp.Full @Configuration - class được enhance
@Bean vẫn theo scope khi gọi qua this.dataSource()
Hai Thời Điểm Can Thiệp
Đường non-lazy singleton thông thường
invokeBeanFactoryPostProcessors()Chạy BFPP trước khi phần lớn singleton được tạoregisterBeanPostProcessors()Đưa các BPP vào BeanFactory theo thứ tựfinishBeanFactoryInitialization()GọipreInstantiateSingletons()doCreateBean()Instantiate target rồi populate dependenciesinitializeBean()Aware, BPP before và callback như@PostConstructpostProcessAfterInitialization()Auto-proxy creator có thể thay target bằng proxyaddSingleton()Cache object cuối cùng mà quá trình tạo bean trả về
Kiểm chứng: breakpoint tại TransactionInterceptor.invoke(). Gọi proxy.save() sẽ chạm; đường this.save() trong target sẽ không chạm lại.
Vì sao là HAI object, không phải một: proxy AOP được đúc qua ObjenesisCglibAopProxy - Objenesis dựng instance mà bỏ qua constructor, đúng để constructor của bạn không chạy lần thứ hai. Hệ quả: proxy không được inject field nào, nó chỉ giữ một TargetSource trỏ tới target cộng chồng advice. Nên khi code đã chạy bên trong target, this không thể là proxy - và cái vỏ chỉ chặn được cửa vào.
Sửa Class Hay Bọc Object
Full configuration enhancement khác AOP proxy
BFPP - sửa BeanDefinition
ConfigurationClassPostProcessor.postProcessBeanFactory()Chuẩn bị full@Configurationcho runtimeenhanceConfigurationClasses()Tráo BeanDefinition thành class con CGLIBbeanDef.setBeanClass(enhancedClass)Sau đó Spring instantiate chính class đã enhance: một object
BPP - bọc object đã tạo
AbstractAutoProxyCreator.postProcessAfterInitialization()Nhận target đã hoàn tất initializationwrapIfNecessary()Tìm advisors phù hợp với beancreateProxy()Nhậnnew SingletonTargetSource(bean), tạo proxy riêng trỏ tới target: hai object
Hai Đường Gọi Nội Bộ
Cùng cú pháp Java, khác object nhận lời gọi
Full @Configuration
thislà enhanced config instance@Beanmethod có bản override trên chính object đóBeanMethodInterceptor.intercept()this.dataSource()đi vào callback CGLIBbeanFactory.getBean(beanName)Trả bean theo scope; với singleton là cùng instance đã quản lý
Spring AOP proxy
proxy.placeOrder()Lời gọi ngoài đi qua interceptor chain rồi tới targetthislà targetCode củaplaceOrder()đang chạy trên object được delegate tớithis.save()Target gọi trực tiếp chính nó; advice củasave()không có cơ hội chạy
Ưu tiên sửa
- Tách sang bean khácĐây là lựa chọn ít xâm lấn nhất: lời gọi từ bean này đi qua proxy của bean kia.
- Self-injection khi chưa thể táchGọi
self.save()qua self reference được inject; chấp nhận coupling và dependency vòng.
Khi KHÔNG được kết luận
- Self-invocation nghĩa là chắc chắn không có transactionChỉ advice của method nội bộ bị bỏ qua; caller có thể đã mở transaction từ trước.
- Mọi
@Configurationđều intercept@BeanKhông đúng với lite mode,proxyBeanMethods=falsehoặc methodstatic. - Dùng
AopContext.currentProxy()như cách sửa mặc địnhNó trói code vào Spring AOP và cần bậtexposeProxy; chỉ nên là phương án cuối.