SPRING SERIES - LỖI IM LẶNG

AOP Proxy, @Configuration Và Lời Gọi Nội Bộ

PROXY - XANH = TẦNG PHÓNG TO Đọc từ trái sang phải. Màu đậm dần nghĩa là gần cơ chế hơn. Dấu * đá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()

Cùng một câu hỏithis đ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()

Mức 1 - refresh tới singleton

Hai Thời Điểm Can Thiệp

Đường non-lazy singleton thông thường

  1. invokeBeanFactoryPostProcessors()Chạy BFPP trước khi phần lớn singleton được tạo
  2. registerBeanPostProcessors()Đưa các BPP vào BeanFactory theo thứ tự
  3. finishBeanFactoryInitialization()Gọi preInstantiateSingletons()
  4. doCreateBean()Instantiate target rồi populate dependencies
  5. initializeBean()Aware, BPP before và callback như @PostConstruct
  6. postProcessAfterInitialization()Auto-proxy creator có thể thay target bằng proxy
  7. addSingleton()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.

Mức 2 - zoom hai điểm nóng

Sửa Class Hay Bọc Object

Full configuration enhancement khác AOP proxy

BFPP - sửa BeanDefinition

  1. ConfigurationClassPostProcessor.postProcessBeanFactory()Chuẩn bị full @Configuration cho runtime
  2. enhanceConfigurationClasses()Tráo BeanDefinition thành class con CGLIB
  3. beanDef.setBeanClass(enhancedClass)Sau đó Spring instantiate chính class đã enhance: một object

BPP - bọc object đã tạo

  1. AbstractAutoProxyCreator.postProcessAfterInitialization()Nhận target đã hoàn tất initialization
  2. wrapIfNecessary()Tìm advisors phù hợp với bean
  3. createProxy()Nhận new SingletonTargetSource(bean), tạo proxy riêng trỏ tới target: hai object
Mức 3 - zoom khoảnh khắc this

Hai Đường Gọi Nội Bộ

Cùng cú pháp Java, khác object nhận lời gọi

Full @Configuration

  1. this là enhanced config instance@Bean method có bản override trên chính object đó
  2. BeanMethodInterceptor.intercept()this.dataSource() đi vào callback CGLIB
  3. beanFactory.getBean(beanName)Trả bean theo scope; với singleton là cùng instance đã quản lý

Spring AOP proxy

  1. proxy.placeOrder()Lời gọi ngoài đi qua interceptor chain rồi tới target
  2. this là targetCode của placeOrder() đang chạy trên object được delegate tới
  3. this.save()Target gọi trực tiếp chính nó; advice của save() 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=false hoặc method static.
  • Dùng AopContext.currentProxy() như cách sửa mặc địnhNó trói code vào Spring AOP và cần bật exposeProxy; chỉ nên là phương án cuối.