SPRING SERIES - LỖI IM LẶNG
Vỏ Bọc Proxy Và Lỗi Bốc Hơi Transaction
* đánh dấu điểm bài viết khai thác.
Triệu chứng A - Tên class lạ
getClass() in ra tên có $$SpringCGLIB$$ thay vì class tự viết
Triệu chứng B - Advice bốc hơi
@Transactional trên method final không mở transaction
AbstractAutoProxyCreator
Từ BPP cuối lifecycle tới implementation thật
postProcessAfterInitialization()Trạm chốt chặn cuối của dây chuyền tạo beanwrapIfNecessary()Tìm advice; trả bean gốc nếu không cần proxycreateProxy()->buildProxy()DựngProxyFactory, advisors vàTargetSourceProxyFactory.getProxy()Yêu cầu factory tạo proxy theo cấu hình đã chốtProxyCreatorSupport.createAopProxy()Chuyển cấu hình sangAopProxyFactoryDefaultAopProxyFactory.createAopProxy()Chọn implementation JDK hay CGLIBAopProxy.getProxy()Implementation sinh object proxy và trả về context
Hai loại vỏ, hai kiểu "giống bạn": JDK proxy chỉ đeo cùng biển - nó implement cùng interface, nên chỉ có những method nằm trên interface. Method public nào bạn viết thêm ở class mà quên khai lên interface thì proxy không có, và autowire bằng class cụ thể sẽ nổ. CGLIB proxy là con ruột - kế thừa chính class của bạn nên có đủ method. Đó là lý do Boot lật mặc định sang CGLIB thay vì giữ luật JDK-first của Spring Framework.
DefaultAopProxyFactory.createAopProxy()
Đọc flag đã có, không đọc file cấu hình
isOptimize() || isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)Nếu đúng, kiểm tra tiếp loại target class- Target null, interface, JDK proxy hoặc lambdaTrả
JdkDynamicAopProxy, kể cả khi flag class proxy đang bật - Target là class concreteTrả
ObjenesisCglibAopProxy - Flag false và có user-supplied interfaceĐi thẳng sang
JdkDynamicAopProxy
Flag được đặt từ trước: AopAutoConfiguration của Boot ánh xạ spring.aop.proxy-target-class vào auto-proxy creator; mặc định là true. Method trên chỉ đọc trạng thái đã có.
Kiểm chứng trong 30 giây
AopUtils.isAopProxy(), isCglibProxy(), isJdkDynamicProxy() và getTargetClass() trả lời bốn câu khác nhau.
Đặt spring.aop.proxy-target-class=false bằng một property source của Boot, restart rồi gọi bean qua interface để quan sát nhánh JDK.
Vỏ ObjenesisCglibAopProxy
Cơ chế kế thừa và các giới hạn
- Sinh subclass (lớp con) lúc runtimeKế thừa trực tiếp từ Target class thật
- Tên class
$$SpringCGLIB$$0Dấu hiệu nhận biết lớp vỏ CGLIB - Override method có thể chặnCallback đưa lời gọi qua Advice rồi sang target
- Method
finalĐược kế thừa nhưng không override; lời gọi không được route sang target và có thể chạm field rỗng trên proxy - Method
privateKhông được kế thừa; subclass không nhìn thấy để tạo điểm chặn
Lối ra an toàn
- Hỏi đúng câuDùng
getClass()để xem lớp ngoài; dùngAopUtils.getTargetClass(bean)để hỏi target class Spring xác định được. - Giữ đường vào có thể chặnĐặt advice trên method
public, non-final và gọi method đó qua proxy.
Khi KHÔNG được kết luận
- Mất transaction là do method
finalChưa đủ. Lời gọi nội bộ (self-invocation) cũng làm transaction bốc hơi dù method hợp lệ. - Tắt bằng
@EnableAspectJAutoProxy(proxyTargetClass = false)Không hạ được flagtrueđã hợp nhất trong context Boot; dùng propertyspring.aop.proxy-target-class=false.