Spring Core & Boot/Bean lifecycle — 9 giai đoạn từ instantiate đến destroy
16/41
Bài 16 / 41~12 phútBean Lifecycle & ScopesMiễn phí lượt xem

Bean lifecycle — 9 giai đoạn từ instantiate đến destroy

9 giai đoạn cố định của một bean Spring: constructor, populate @Autowired, Aware, init hooks, destroy — vì sao thứ tự không thể đảo và pitfall null field.

TL;DR: Mỗi bean Spring đi qua 9 giai đoạn cố định: instantiate → populate @Autowired → các callback *AwareBeanPostProcessor before-init → @PostConstructInitializingBean.afterPropertiesSet → custom init-method → BeanPostProcessor after-init → ready; rồi destroy khi container shutdown. Thứ tự này không thể đảo vì mỗi bước phụ thuộc bước trước: dependency phải sẵn sàng trước khi init logic chạy. Exception trong @PostConstruct khiến app không khởi động được — đây là fail-fast có chủ đích.

Bài refresh() 12 bước chỉ ra bước 11 là "instantiate all singleton". Câu hỏi phóng to: trong bước 11, mỗi bean cụ thể trải qua giai đoạn nào? Khi bạn ghi @PostConstruct, code đó chạy lúc nào so với lúc @Autowired field được set?

1. 9 giai đoạn — sơ đồ tổng

flowchart TB
    A["1. Instantiate<br/>(goi constructor)"]
    B["2. Populate properties<br/>(@Autowired field/setter)"]
    C["3. Aware callbacks<br/>(BeanNameAware, BeanFactoryAware...)"]
    D["4. BPP before-init<br/>(BeanPostProcessor.postProcessBefore)"]
    E["5. @PostConstruct"]
    F["6. InitializingBean.afterPropertiesSet"]
    G["7. Custom init-method"]
    H["8. BPP after-init<br/>(BeanPostProcessor.postProcessAfter - AOP proxy wrap day)"]
    I["Bean ready - phuc vu request"]
    J["9. @PreDestroy + DisposableBean.destroy + custom destroy-method"]

    A --> B --> C --> D --> E --> F --> G --> H --> I --> J

    style E fill:#d1fae5
    style H fill:#fef3c7
    style J fill:#fee2e2

Bảng mapping từng bước:

#Giai đoạnCode/annotationKhi nào dùng
1InstantiateconstructorBean class được new với DI qua constructor
2Populate@Autowired field/setterField/setter inject dependency không qua constructor
3*Aware callbacksBeanNameAware, BeanClassLoaderAware, BeanFactoryAware (bước invokeAwareMethods)Bean cần biết tên/loader/factory của chính mình
4BPP before-initBeanPostProcessor.postProcessBeforeInitializationHook trước init
5@PostConstructannotation methodInit logic dùng dependency đã inject
6afterPropertiesSetInitializingBean interface(Hiếm) tương đương @PostConstruct, kế thừa từ Spring 1.x
7init-method@Bean(initMethod = "...")Init khi bean third-party không annotate được
8BPP after-initBeanPostProcessor.postProcessAfterInitializationAOP proxy wrap ở đây
9Destroy@PreDestroy, DisposableBean, destroy-methodCleanup khi container shutdown

2. Bước 1-2 — instantiate và populate

Đây là cặp bước đầu tiên và cũng là nguồn gốc của một trong những pitfall phổ biến nhất khi mới học Spring.

@Service
public class OrderService {
    private final PaymentGateway payment;       // constructor inject
    @Autowired private NotificationClient notif; // field inject

    public OrderService(PaymentGateway payment) {
        this.payment = payment;
        // Tai day: 'payment' da SET, 'notif' van NULL
        System.out.println(notif);  // null !!
    }
}

Constructor chạy ở bước 1 — Java buộc gọi new trước để có instance. Chỉ sau đó Spring mới có chỗ để set field @Autowired. Đây là ràng buộc của Java, không phải design Spring.

Vì sao thứ tự này? Để tạo object, JVM phải cấp phát vùng nhớ và gọi constructor. Không có cách nào set field của một object trước khi object tồn tại. Spring tuân theo chuỗi này: tạo object → sau đó mới inject dependency ngoài constructor.

// SAI — notif chua duoc inject, NPE tai runtime
public OrderService(PaymentGateway payment) {
    this.payment = payment;
    notif.send("init");   // NullPointerException
}

// DUNG — tat ca dependency qua constructor, co ngay khi constructor chay
public OrderService(PaymentGateway payment, NotificationClient notif) {
    this.payment = payment;
    this.notif = notif;
    notif.send("init");   // OK
}

Đây là một trong nhiều lý do constructor injection được khuyến nghị: tất cả dependency đã có khi constructor chạy, không thể có trạng thái nửa chừng.

3. Bước 3 — *Aware callbacks

Spring có hơn 10 interface *Aware; bảng dưới liệt kê 7 cái hay gặp. Nhưng chúng không chạy cùng một chỗ — đây là điểm nhiều người hiểu sai. Chỉ BeanNameAware, BeanClassLoaderAware, BeanFactoryAware được gọi trong bước invokeAwareMethods() riêng (ngay sau populate, trước mọi BeanPostProcessor). Năm interface còn lại trong bảng — ApplicationContextAware, EnvironmentAware, ResourceLoaderAware, ApplicationEventPublisherAware, EmbeddedValueResolverAwarekhông chạy ở bước này; chúng được cấp bởi ApplicationContextAwareProcessor, vốn là một BeanPostProcessor chạy ở bước 4 (BPP before-init).

InterfaceBean nhận được gìKhi nào hữu ích
BeanNameAwaresetBeanName(String) — tên bean trong contextLog/debug khi bean cần biết identity
BeanFactoryAwaresetBeanFactory(BeanFactory)Hiếm — thường anti-pattern
ApplicationContextAwaresetApplicationContext(ApplicationContext)Khi cần lookup động (legacy)
EnvironmentAwaresetEnvironment(Environment)Đọc property trong custom infrastructure code
ResourceLoaderAwaresetResourceLoader(ResourceLoader)Load resource động
ApplicationEventPublisherAwaresetApplicationEventPublisher(...)Publish custom event từ infrastructure code
EmbeddedValueResolverAwaresetEmbeddedValueResolver(StringValueResolver)Resolve ${...} trong custom logic

Vì sao thứ tự Aware sau populate? Bean cần dependency đã inject đầy đủ trước khi nhận tham chiếu infrastructure — ngược lại, callback có thể gọi method dùng dependency chưa set.

Khuyến nghị: không implement *Aware trong code business. Constructor injection thay thế hoàn toàn:

// THAY VI implement ApplicationContextAware:
@Service
public class GoodService {
    private final ApplicationContext ctx;
    public GoodService(ApplicationContext ctx) { this.ctx = ctx; }
}

Spring tự deduce 7+ "infrastructure type" có thể inject như bean thường — ApplicationContext, Environment, ApplicationEventPublisher đều inject được qua constructor. *Aware chỉ nên dùng khi viết infrastructure code (custom BeanFactoryPostProcessor, custom scope). 99% application code không cần.

4. Vì sao thứ tự init (bước 5-7) không thể đảo?

3 cơ chế init chạy theo thứ tự cố định:

  1. @PostConstruct — annotation chuẩn JSR-250.
  2. InitializingBean.afterPropertiesSet() — interface Spring 1.x.
  3. Custom init-method — khai báo qua @Bean(initMethod = "init").

Nếu bean có cả 3, cả 3 đều chạy theo thứ tự trên. Vì sao thứ tự init sau populate và Aware?

Nguyên tắc cốt lõi: init logic dùng dependency — dependency phải sẵn sàng trước. Nếu @PostConstruct chạy trước bước 2 (populate), field @Autowired vẫn null, code init sẽ throw NPE. Spring thiết kế thứ tự này để đảm bảo tại thời điểm init, mọi dependency đã sẵn sàng.

Chi tiết cơ chế — các 'bước' này không phải phase tách rời hoàn toàn

Sơ đồ 9 bước là thứ tự quan sát được (đúng theo trình tự người học thấy). Nhưng dưới nguồn, @PostConstruct không phải một phase độc lập: nó được thực thi bởi một BeanPostProcessorInitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization — tức nằm trong pha BPP before-init (bước 4). Chỉ afterPropertiesSet và init-method mới chạy sau khi mọi postProcessBeforeInitialization xong (trong invokeInitMethods). Điểm cần nhớ: BeanPostProcessor chính là cơ chế cấp nhiều callback mà ta quen gọi là "bước riêng" — @PostConstruct và 5 *Aware họ-context (ApplicationContextAware...) đều đi qua một BPP.

@Service
public class CacheWarmer {
    private final DataSource ds;

    public CacheWarmer(DataSource ds) { this.ds = ds; }

    @PostConstruct
    public void init() {
        // Chay sau khi tat ca dependency da set — ds bao gio cung non-null tai day
        try (var conn = ds.getConnection()) {
            conn.createStatement().execute("SELECT 1");
        }
        System.out.println("CacheWarmer initialized");
    }
}

@PostConstruct lý tưởng cho:

  • Validate cấu hình tại startup (fail fast nếu config sai).
  • Pre-load cache, warm up connection pool.
  • Kiểm tra connectivity đến dependency bên ngoài.

Cảnh báo — fail fast là mục tiêu, không phải bug: exception từ @PostConstruct khiến app không khởi động được. Đây là hành vi có chủ đích — nếu init logic fail, container từ chối start thay vì chạy với trạng thái sai. Bài BeanFactory vs ApplicationContext giải thích tại sao eager instantiation + fail-fast là lựa chọn thiết kế của ApplicationContext.

3 cơ chế tồn tại song song do lịch sử: Spring 1-2 chỉ có InitializingBean và XML init-method; Spring 2.5+ thêm @PostConstruct (JSR-250). Khuyến nghị 2026: dùng @PostConstruct/@PreDestroy — chuẩn, không lock vào Spring API.

init-method cho bean third-party

Khi bean là class từ thư viện ngoài (không sửa source được), dùng initMethod:

@Configuration
public class CacheConfig {
    @Bean(initMethod = "start", destroyMethod = "stop")
    public CacheCluster cacheCluster() {
        return new CacheCluster(/* config */);
    }
}

Spring gọi cacheCluster.start() sau init, cacheCluster.stop() khi shutdown. Với @Bean, nếu không khai báo destroyMethod, Spring tự suy ra ((inferred) mode mặc định): tìm method public tên close hoặc shutdown rồi gọi nó khi shutdown — không cần bean implement Closeable/AutoCloseable, chỉ cần đúng tên method.

5. Bước 9 — destroy callbacks

Khi container shutdown (ctx.close() hoặc JVM nhận SIGTERM với registerShutdownHook), Spring chạy destroy callback theo thứ tự ngược chiều dependency — bean nào được tạo sau thì bị destroy trước.

3 cơ chế destroy (tương tự init, cùng thứ tự):

  1. @PreDestroy method.
  2. DisposableBean.destroy() — interface Spring 1.x.
  3. Custom destroy-method khai báo qua @Bean(destroyMethod = "...").
@Service
public class ResourceManager {
    private ScheduledExecutorService scheduler;

    @PostConstruct
    public void start() {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(this::refresh, 0, 5, TimeUnit.MINUTES);
    }

    @PreDestroy
    public void stop() {
        if (scheduler != null) {
            scheduler.shutdown();
            try {
                scheduler.awaitTermination(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    private void refresh() { /* ... */ }
}

Destroy callback chỉ chạy khi container shutdown đúng cách:

  • Spring Boot tự register shutdown hook → kill <pid> (SIGTERM) trigger graceful shutdown.
  • kill -9 <pid> (SIGKILL) — JVM bị giết trực tiếp, không kịp callback.
  • Crash JVM (OOM, segfault) cũng không trigger.

Prototype scope — ngoại lệ quan trọng: @PreDestroy trên prototype bean không chạy. Spring docs ghi rõ: Spring không quản lý trọn vòng đời của prototype. Container tạo bean, inject dependency, rồi giao lại cho caller — sau đó không track nữa. Caller phải tự cleanup, hoặc dùng BeanFactory.destroyBean(bean) thủ công.

Xem Singleton và Prototype scopes để hiểu thêm tại sao Spring chọn không track destroy cho prototype.

6. SmartLifecycle — kiểm soát thứ tự start/stop

SmartLifecycle là interface Spring cho phép bean kiểm soát thứ tự start và stop khi container khởi động/shutdown:

@Component
public class KafkaConsumer implements SmartLifecycle {
    private volatile boolean running;

    public void start() {
        running = true;
        // bat dau consumer thread
    }

    public void stop() {
        running = false;
        // dong consumer
    }

    public boolean isRunning() { return running; }

    // Bean voi phase nho duoc start truoc, stop sau
    public int getPhase() { return 0; }

    public boolean isAutoStartup() { return true; }

    // Async stop voi timeout callback
    public void stop(Runnable callback) { stop(); callback.run(); }
}

SmartLifecycle tốt hơn @PostConstruct/@PreDestroy cho các trường hợp cần:

  • Graceful shutdown sequence: Tomcat (phase cao) stop trước để ngừng nhận request mới; Kafka consumer (phase thấp) stop sau để xử lý xong message đang chạy.
  • Resume sau pause: gọi start() lại sau stop()@PostConstruct chỉ chạy 1 lần duy nhất khi bean init.

Spring Boot dùng SmartLifecycle cho web server, scheduling, Kafka, RabbitMQ. Khi debug thấy log "Stopping bean X with phase 2147483646" — đó là Spring đang chạy SmartLifecycle shutdown sequence theo phase.

7. Graceful shutdown production

Production yêu cầu graceful shutdown — nhận SIGTERM, ngừng accept request mới, xử lý nốt in-flight request, rồi mới shutdown. Spring Boot 2.3+ có sẵn:

# application.yml
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s
sequenceDiagram
    participant K8s
    participant App
    participant Tomcat
    participant InFlight as In-flight requests

    K8s->>App: SIGTERM
    App->>Tomcat: stop accepting new connections
    Note over Tomcat: 503 cho request moi
    Tomcat->>InFlight: wait for completion (max 30s)
    InFlight-->>Tomcat: all done
    App->>App: @PreDestroy callbacks
    App->>K8s: process exit 0

Pitfall production:

Vấn đềHệ quả
Không cấu hình server.shutdown=gracefulTomcat đóng socket ngay, in-flight request bị cắt, user nhận 502
K8s terminationGracePeriodSeconds mặc định 30s quá ngắnK8s gửi SIGKILL khi quá hạn, callback không chạy
@PreDestroy chạy lâu hơn timeoutState không được flush trước khi process exit

Best practice K8s:

spec:
  terminationGracePeriodSeconds: 60   # > spring.lifecycle.timeout
  containers:
    - name: app
      readinessProbe:
        httpGet: { path: /actuator/health/readiness, port: 8080 }
      lifecycle:
        preStop:
          exec:
            command: ["sleep", "5"]   # cho service mesh propagate endpoint removal

Rule: terminationGracePeriodSeconds phải lớn hơn timeout-per-shutdown-phase cộng thêm buffer ~20-30s để có thời gian cho pre-stop hook và flush log.

8. Bài toán ví dụ — warm-up cache lúc khởi động, flush an toàn khi shutdown

Một yêu cầu thực tế buộc bạn phải chọn đúng giai đoạn vòng đời; chọn sai sẽ NPE lúc start hoặc mất dữ liệu lúc stop.

Đề bài: PriceCacheService giữ bảng giá trong RAM để request đầu tiên không phải chờ 2 giây query DB. Hai ràng buộc:

  1. Lúc khởi động, nạp sẵn 50.000 dòng giá từ DB vào cache — chỉ nạp được khi DataSource đã sẵn sàng.
  2. Khi pod scale-down (SIGTERM), các giá vừa đổi trong RAM (dirty) phải được ghi về DB trước khi connection pool đóng.

Tự hỏi trước khi đọc lời giải: nếu đặt code nạp cache ngay trong constructor thì DataSource đã sẵn sàng chưa? Khi shutdown, PriceCacheService bị huỷ trước hay sau DataSource?

Lời giải:

@Service
public class PriceCacheService {
    private final DataSource ds;
    private final Map<String, BigDecimal> cache = new ConcurrentHashMap<>();
    private final Set<String> dirty = ConcurrentHashMap.newKeySet();

    public PriceCacheService(DataSource ds) {   // buoc 1-2: constructor inject -> ds san sang ngay
        this.ds = ds;
    }

    @PostConstruct                              // buoc 5: chay sau populate, ds chac chan non-null
    void warmUp() {
        // doc 50.000 gia tu DB vao cache
    }

    @PreDestroy                                 // buoc 9: chay khi SIGTERM
    void flushDirty() {
        // ghi cac gia dirty ve DB qua ds
    }
}

Vì sao đúng:

  • Nạp cache trong @PostConstruct, không phải constructor. Constructor chạy ở bước 1, trước khi field được inject (bước 2). Ở đây ds là constructor inject nên có ngay, nhưng việc nạp dữ liệu vẫn nên để ở @PostConstruct (bước 5) — nơi mọi dependency chắc chắn đã sẵn sàng, tránh phụ thuộc vào kiểu inject.
  • flushDirty() luôn còn pool để ghi. DataSource là dependency của PriceCacheService nên được tạo trước. Spring huỷ bean theo thứ tự ngược chiều tạo, nên PriceCacheService bị huỷ trước DataSource. Khi @PreDestroy chạy, pool vẫn mở.
  • Giới hạn cần biết: nếu pod bị kill -9 (SIGKILL) hoặc OOM, @PreDestroy không chạy và dirty mất. Với dữ liệu không được phép mất, đừng dựa vào @PreDestroy — ghi ngay (write-through) hoặc dùng store ngoài.

Liên hệ các bài khác

  • refresh() 12 bước: bước 11 finishBeanFactoryInitialization gọi createBean cho từng singleton — 9 giai đoạn trong bài này là nội dung của createBean. Hiểu refresh() cho bức tranh container; bài này phóng to vào một bean đơn.
  • AOP proxy — JDK vs CGLIB: bước 8 BPP after-init là nơi AnnotationAwareAspectJAutoProxyCreator wrap bean trong proxy. Bài này chỉ điểm giai đoạn; bài đó đào sâu cơ chế tạo proxy và pitfall self-call @Transactional.
  • Singleton và Prototype scopes: 9 giai đoạn trên áp dụng đầy đủ cho singleton. Prototype và request scope có biến thể — đặc biệt destroy không được track cho prototype. Bài đó giải thích vì sao Spring chọn thiết kế này.

Tóm tắt

  • Bean trải qua 9 giai đoạn có thứ tự cố định: instantiate → populate → *Aware → BPP-before → @PostConstructafterPropertiesSet → init-method → BPP-after → ready, rồi destroy khi shutdown.
  • Thứ tự này không thể đảo: dependency phải sẵn sàng (bước 2) trước khi init logic chạy (bước 5+). Constructor chạy trước populate — không dùng @Autowired field trong constructor.
  • 3 cơ chế init (@PostConstruct, InitializingBean, init-method) tồn tại do lịch sử. Khuyến nghị: @PostConstruct cho bean tự viết, init-method cho bean third-party.
  • Exception trong @PostConstruct → app không khởi động — fail-fast có chủ đích, không phải bug.
  • *Aware interface (hơn 10 cái) chỉ dùng cho infrastructure code; chỉ BeanName/BeanClassLoader/BeanFactory chạy ở bước Aware riêng, các Aware họ-context còn lại được cấp qua một BPP. Constructor inject thay thế hoàn toàn cho business code.
  • BPP after-init (bước 8) là nơi AOP proxy được tạo — bài AOP proxy đào sâu cơ chế này.
  • Destroy callback chỉ chạy khi SIGTERM (không chạy với SIGKILL). Prototype bean không có destroy — Spring không track.
  • Production: server.shutdown=graceful + K8s terminationGracePeriodSeconds=60 + readiness probe.

Tự kiểm tra

Tự kiểm tra
Q1
Đoạn sau in gì? Giải thích dựa trên thứ tự 9 giai đoạn.
@Service
public class MyService {
  @Autowired private Foo foo;

  public MyService() {
      System.out.println("ctor: " + foo);
  }

  @PostConstruct
  void init() {
      System.out.println("init: " + foo);
  }
}

In:

ctor: null
init: <Foo bean instance>

Vì sao: constructor chạy ở bước 1 (instantiate) — Java buộc gọi new trước, lúc này chưa có instance để set field. @Autowired field được populate ở bước 2 sau khi object đã tồn tại. @PostConstruct chạy ở bước 5 — tất cả field đã được inject đầy đủ.

Bài học: không dùng @Autowired field trong constructor. Chuyển sang constructor injection — mọi dependency có ngay khi constructor chạy.

Q2
Vì sao Spring thiết kế thứ tự: populate @Autowired (bước 2) rồi mới @Aware callbacks (bước 3), rồi mới @PostConstruct (bước 5)? Không thể đảo thứ tự được không?

Không thể đảo vì mỗi bước phụ thuộc bước trước:

  • Bước 2 phải sau bước 1: Spring cần object đã tồn tại mới set field được.
  • Bước 3 nên sau bước 2: *Aware callback có thể gọi method dùng dependency đã inject — nếu chạy trước populate, dependency vẫn null.
  • Bước 5 phải sau bước 2: init logic dùng dependency (@PostConstruct void init() { this.dependency.doStuff(); }) — dependency phải sẵn sàng trước.

Nếu @PostConstruct chạy trước populate, mọi field @Autowired đều null → NPE tại init. Thứ tự này là invariant của Spring bean lifecycle, được đảm bảo bởi source AbstractAutowireCapableBeanFactory.initializeBean().

Q3
Bạn viết @PostConstruct method validate kết nối DB. Method này throw SQLException. App xử lý thế nào? Đây có phải hành vi bug không?

App không khởi động được. Spring wrap exception thành BeanCreationException và fail container startup. Đây là hành vi có chủ đích — fail fast.

Lý do: ApplicationContext instantiate eager toàn bộ singleton lúc startup. Nếu bean init fail, container từ chối start thay vì chạy với trạng thái không hợp lệ. Lỗi lộ ra ngay khi deploy, không phải lúc user gửi request đầu tiên.

Nếu muốn graceful: catch exception trong @PostConstruct, log warning, continue. Nhưng đừng swallow lỗi config nghiêm trọng — fail fast giúp phát hiện vấn đề sớm hơn nhiều.

Q4
App Spring Boot deploy K8s. DevOps thấy khi pod terminate, log không có dòng "shutdown complete" dù bean có @PreDestroy. Liệt kê 3 nguyên nhân khả thi và cách kiểm tra từng cái.
  • 1. K8s gửi SIGKILL thay SIGTERM: nếu container không thoát trong terminationGracePeriodSeconds (default 30s), K8s gửi SIGKILL — JVM bị giết ngay, shutdown hook không chạy. Kiểm tra: log "SIGTERM received" ở đầu shutdown. Nếu không thấy log này → SIGKILL. Fix: tăng grace period, đảm bảo app thoát trong timeout.
  • 2. server.shutdown=graceful chưa bật: thiếu config này, Tomcat đóng socket ngay khi nhận SIGTERM nhưng Spring shutdown hook vẫn chạy. Tuy nhiên nếu @PreDestroy phụ thuộc resource đang đóng, có thể throw và log bị mất. Kiểm tra: grep server.shutdown application.yml.
  • 3. Bean là prototype scope: Spring không track lifecycle prototype, @PreDestroy không được gọi tự động. Kiểm tra: xem bean declaration có @Scope("prototype") không.
Q5
Bạn có bean third-party CacheCluster không có source code, cần gọi cluster.start() sau khi inject xong và cluster.stop() khi shutdown. Dùng cơ chế nào? Viết code config.

Dùng @Bean(initMethod = "start", destroyMethod = "stop") — đây đúng là use case của init-method: bean third-party không annotate được.

@Configuration
public class CacheConfig {
  @Bean(initMethod = "start", destroyMethod = "stop")
  public CacheCluster cacheCluster() {
      return new CacheCluster(/* config */);
  }
}

Spring gọi cacheCluster.start() tại bước 7 (sau @PostConstructafterPropertiesSet), và cacheCluster.stop() khi container shutdown.

Bonus: với @Bean, nếu không khai báo destroyMethod, Spring tự suy ra ((inferred) mode mặc định) — tìm method public tên close hoặc shutdown rồi gọi nó khi shutdown. Không bắt buộc implement Closeable/AutoCloseable, chỉ cần đúng tên method.

Bài tiếp theo: AOP proxy — JDK vs CGLIB

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Ôn phỏng vấn

Bài này trả lời được các câu phỏng vấn sau — tự trả lời thử trước khi mở đáp án.

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

AOP Proxy — JDK Dynamic Proxy vs CGLIB và pitfall self-call