Backend

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

SSite Admin
31 tháng 07, 2026 4 phút đọc 126 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 Spring — Ngày 14: Bean Validation — @Valid & bộ constraint chuẩn

Ngày 14 của 99 Ngày Spring: Bean Validation khai báo luật ngay trên DTO — @Valid kích hoạt, bộ constraint chuẩn (@NotBlank vs @NotEmpty vs @NotNull, @Size, @Email, @Min/@Max), bẫy int vs Integer, validate object lồng nhau không tự lan, và lỗi 400 gom một lượt.

10 thg 8, 20265 phút0
99 Ngày Java — Ngày 14: Kế thừa (Inheritance)

Ngày 14 của 99 Ngày Java: extends trao gia tài từ lớp cha, override với @Override và super., thứ tự khai sinh cha trước con sau qua super(...), protected trả nợ Ngày 13, và phép thử is-a vs has-a — khi nào nên composition thay vì kế thừa.

10 thg 8, 20266 phút0
99 Ngày Spring — Ngày 13: @RequestBody & DTO — đừng công khai entity ra API

Ngày 13 của 99 Ngày Spring: @RequestBody + Jackson deserialize JSON thành object, DTO là hợp đồng API — chặn lỗ hổng mass assignment chiều vào và rò rỉ dữ liệu chiều ra, request/response DTO khác nhau là bình thường, và Jackson annotations tinh chỉnh hợp đồng JSON.

9 thg 8, 20266 phút9