Backend

99 Ngày Spring — Ngày 03: IoC container & Bean

SSite Admin
31 tháng 07, 2026 4 phút đọc 189 lượt xem
99 Ngày Spring — Ngày 03: IoC container & Bean

Ngày 01 bạn hiểu IoC trên lý thuyết, Ngày 02 đã chạy được ứng dụng Spring Boot đầu tiên. Hôm nay ta mở nắp "trái tim" của Spring: IoC container — cỗ máy đứng sau SpringApplication.run(...) — và bean, những object mà container tạo ra, nuôi dưỡng và trao cho bạn đúng lúc cần.

Sketchnote Ngày 03: IoC container và bean trong Spring

Bean là gì?

Định nghĩa ngắn gọn nhất: bean là một object do Spring container tạo ra và quản lý. Hết. Nó không cần kế thừa class đặc biệt hay implement interface nào — chỉ là một POJO (Plain Old Java Object) bình thường mà bạn giao quyền khởi tạo cho container thay vì tự new:

@Servicepublic class GreetingService {
    public String greet() {
        return "Xin chào từ bean đầu tiên!";
    }
}

Annotation @Service là tấm biển báo: "Spring ơi, class này là bean, hãy tạo và quản lý nó giúp tôi." Từ đó trở đi, vòng đời của object — sinh ra khi nào, tiêm phụ thuộc gì, hủy ra sao — là việc của container. Đó chính là chữ Inversion trong Inversion of Control: quyền kiểm soát đảo từ tay bạn sang framework.

Lưu ý nhỏ: "bean" của Spring không phải "JavaBean" cổ điển (quy ước getter/setter) — trùng tên nhưng khác khái niệm; đừng để hai thuật ngữ này đánh lừa trong phỏng vấn.

ApplicationContext — chiếc hộp thần kỳ

IoC container trong Spring có tên cụ thể: ApplicationContext. Nó là một registry khổng lồ: đọc "bản thiết kế" (bean definitions), tạo object theo đúng thứ tự phụ thuộc, cất tất cả vào trong và phát cho ai cần. Interface gốc tối giản là BeanFactory; ApplicationContext mở rộng nó thêm sự kiện, i18n, đọc resource — thực tế bạn sẽ luôn làm việc với ApplicationContext.

Bằng chứng nó tồn tại? SpringApplication.run(...) từ Ngày 02 trả về chính ApplicationContext — ta có thể cầm nó và "hỏi chuyện":

@SpringBootApplicationpublic class DemoApplication {
    public static void main(String[] args) {
        ApplicationContext ctx =
                SpringApplication.run(DemoApplication.class, args);

        // Xin container một bean — không new, không cấu hình
        GreetingService service = ctx.getBean(GreetingService.class);
        System.out.println(service.greet());

        System.out.println("Tổng số bean: " + ctx.getBeanDefinitionCount());
    }
}

Chạy thử, bạn sẽ thấy con số bất ngờ: một project trống có thể chứa hơn 100 bean — DispatcherServlet, ObjectMapper, cấu hình Tomcat… tất cả do auto-configuration của Boot đăng ký sẵn. Xem danh sách bằng:

String[] names = ctx.getBeanDefinitionNames();
Arrays.sort(names);
for (String name : names) {
    System.out.println(name);
}
// greetingService  ← bean của bạn, tên mặc định là tên class//                    viết thường chữ cái đầu// dispatcherServlet, jacksonObjectMapper, ... ← bean của Boot

Vòng đời bean — tổng quan

Mỗi bean đi qua một hành trình được container đạo diễn từ đầu đến cuối:

  • 1. Đọc định nghĩa — container quét @Component/@Service… và các method @Bean để biết cần tạo những gì.

  • 2. Khởi tạo — gọi constructor, theo đúng thứ tự phụ thuộc: bean A cần B thì B được tạo trước.

  • 3. Tiêm phụ thuộc — container đưa các bean khác vào đúng chỗ (dependency injection — mai học kỹ).

  • 4. Callback khởi tạo — method đánh dấu @PostConstruct chạy khi bean đã "đủ đồ nghề", hợp để nạp cache, kiểm tra cấu hình.

  • 5. Phục vụ — bean sống trong container, được tiêm vào nơi cần, xử lý request…

  • 6. Hủy — context đóng (ứng dụng tắt), @PreDestroy chạy để dọn dẹp: đóng kết nối, giải phóng tài nguyên.

@Servicepublic class GreetingService {
    public GreetingService() {
        System.out.println("1. Constructor — container tạo bean");
    }

    @PostConstruct
    void init() {
        System.out.println("2. @PostConstruct — dependency đã tiêm xong");
    }

    @PreDestroy
    void cleanup() {
        System.out.println("3. @PreDestroy — context sắp đóng");
    }
}

Mặc định mỗi bean là singleton: container chỉ tạo một instance duy nhất và ai xin cũng nhận đúng instance đó — gọi ctx.getBean(...) mười lần vẫn là một object. Các scope khác (prototype, request…) và vòng đời chi tiết sẽ được mổ xẻ ở Ngày 07.

Container biết bean từ đâu?

Có ba nguồn chính, và cả ba đều có ngày riêng trong series:

  • Component scan — Spring tự quét package tìm @Component, @Service, @Repository… (Ngày 05).

  • Khai báo thủ công — method @Bean trong class @Configuration, dùng cho class của thư viện ngoài (Ngày 06).

  • Auto-configuration — Boot nhìn classpath và đăng ký sẵn hàng loạt bean hạ tầng (Ngày 10).

Bài tập nhỏ

  • Tạo GreetingService như trên, lấy nó từ ApplicationContext bằng getBean và gọi greet().

  • In getBeanDefinitionCount() — project trống của bạn có bao nhiêu bean?

  • Gọi ctx.getBean(GreetingService.class) hai lần, so sánh bằng == — kết quả nói gì về singleton?

  • Thêm @PostConstruct@PreDestroy như ví dụ, chạy rồi tắt ứng dụng (Ctrl+C) và quan sát thứ tự log.

Kết luận

Bạn đã biết bean chỉ là POJO được container quản lý, ApplicationContext là chiếc hộp thần kỳ chứa cả trăm bean, và vòng đời một bean từ constructor đến @PreDestroy. Nhưng khoan — container tiêm phụ thuộc vào bean bằng cách nào? Ngày 04: ba kiểu dependency injection — constructor, setter, field — và lý do cả thế giới khuyên dùng constructor. Hẹn gặp lại!

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Java — Ngày 58: Optional

Optional là công cụ cho kiểu trả về — không field, không tham số, không bọc collection; chuỗi map/flatMap/filter thay kim tự tháp if; orElse luôn tính tham số còn orElseGet lười, orElseThrow cho “không có là lỗi” — và các anti-pattern isPresent + get, orElse(null).

23 thg 9, 20269 phút9
99 Ngày Spring — Ngày 58: Spring Boot Actuator

Boot 3 mặc định chỉ lộ health; bản đồ endpoint an toàn và nguy hiểm; HealthIndicator tự viết với liveness tách khỏi readiness cho Kubernetes, info từ build và git, metrics Micrometer với tag hữu hạn — và ba lớp khóa: cổng riêng, SecurityFilterChain với EndpointRequest, exclude env/heapdump/threaddump.

23 thg 9, 202610 phút5
So sánh EasyExcel và Apache Fesod: khác biệt thật nằm ở đâu?

Fesod là hậu duệ của EasyExcel, cùng engine SAX/SXSSF. Khác biệt kiểm chứng được: vòng đời dự án, POI 5.2.5 vs 5.5.1, các API mới (numRows, csv(), headerMergeStrategy) và độ bền với file xấu — không phải hiệu năng gấp nhiều lần.

23 thg 9, 20268 phút1