99 Ngày Spring — Ngày 05: @Component & component scan
Ngày 03 ta biết bean sống trong container, Ngày 04 biết container tiêm bean vào nhau. Còn một mắt xích đầu tiên chưa trả lời kỹ: container tìm thấy các class của bạn bằng cách nào? Câu trả lời là component scan — cùng bộ stereotype annotations (@Component, @Service, @Repository…) và lỗi kinh điển nhất Spring Boot: bean nằm ngoài package được quét.

Component scan là gì?
Khi ứng dụng khởi động, Spring quét (scan) các package để tìm class mang annotation đánh dấu bean, đọc thành bean definition rồi đưa vào container — đúng bước "đọc định nghĩa" trong vòng đời bean Ngày 03. Bạn không đăng ký từng bean bằng tay; bạn chỉ dán nhãn lên class và để máy quét làm việc.
Bộ stereotype annotations
@Component // gốc: "class này là bean, quản lý giúp tôi"public class PriceCalculator { ... }
@Service // = @Component + ngữ nghĩa "tầng nghiệp vụ"public class OrderService { ... }
@Repository // = @Component + dịch exception DB sang DataAccessExceptionpublic class OrderRepository { ... }
@RestController // = @Controller + @ResponseBody — tầng web (Ngày 11)public class OrderController { ... }Về mặt kỹ thuật, cả 4 đều tạo bean như nhau — @Service, @Repository, @Controller đều là @Component chuyên môn hóa. Vậy sao không dùng @Component cho tất cả?
Ngữ nghĩa — nhìn annotation biết ngay class thuộc tầng nào: nghiệp vụ, dữ liệu hay web. Code là để người đọc.
@Repositorycó phép thuật riêng: dịch exception đặc thù của JDBC/JPA thành họDataAccessExceptionthống nhất của Spring.Các công cụ (AOP, slice test như
@WebMvcTest…) lọc bean theo đúng stereotype này.
Quy tắc vàng: bean phải nằm dưới gốc
@SpringBootApplication (Ngày 02) chứa sẵn @ComponentScan không tham số — nghĩa là: quét từ package chứa main class trở xuống. Mọi bean nằm trong "cây" đó được thấy; nằm ngoài là vô hình:
com.example.demo
├── DemoApplication.java ← @SpringBootApplication ở GỐC
├── order
│ ├── OrderService.java ✓ được quét
│ └── OrderRepository.java ✓ được quét
└── payment
└── PaymentService.java ✓ được quét
com.example.util
└── LegacyHelper.java ✗ NGOÀI cây gốc — vô hình!Triệu chứng của bean vô hình: khởi động fail với required a bean of type ... that could not be found — hoặc tệ hơn, endpoint trả 404 mà không ai báo lỗi. Gặp lỗi "bean biến mất", việc đầu tiên hãy nhìn cây package, đừng vội nghi ngờ code.
Sửa thế nào khi bean nằm ngoài?
// Cách 1 (nên dùng): chuyển package vào dưới gốc// com.example.util → com.example.demo.util
// Cách 2: khai báo quét thêm — nhớ liệt kê CẢ gốc cũ!@SpringBootApplication@ComponentScan(basePackages = {
"com.example.demo", // quên dòng này là mất sạch bean chính
"com.example.util"
})public class DemoApplication { ... }Cách 1 gần như luôn đúng — cấu trúc chuẩn của Spring Boot là main class ở package gốc, mọi thứ nằm dưới. Cách 2 dành cho tình huống bất khả kháng (thư viện nội bộ, code legacy) và mang theo cái bẫy trứ danh: khai basePackages là thay thế chứ không phải bổ sung — quên liệt kê gốc cũ là toàn bộ bean chính biến mất theo.
Tên bean & lọc quét
@Service("checkout") // tự đặt tên beanpublic class OrderService { ... }
// Mặc định: tên class viết thường chữ đầu// OrderService → "orderService"Tên bean hiếm khi phải đụng tới — nó quan trọng khi hai bean cùng kiểu cần phân biệt (chuyện của @Qualifier, ta gặp lại khi bàn về nhiều implementation). @ComponentScan còn có includeFilters/excludeFilters để lọc tinh vi — biết là đủ, thực tế rất hiếm dùng.
Bài tập nhỏ
Tạo
PriceCalculatorvới@Componenttrong package con, tiêm vào một@Servicebằng constructor (Ngày 04) và in kết quả.Cố tình chuyển
PriceCalculatorra package ngang hàng với gốc, chạy lại và đọc kỹ thông báo lỗi — rồi sửa bằng cả 2 cách.Đổi
@Componentthành@Servicerồi@Repository— chạy lại: có gì khác? (Gợi ý: hôm nay thì chưa — ngữ nghĩa là cho người đọc và công cụ.)In mọi tên bean chứa chữ "price" bằng
getBeanDefinitionNames()(Ngày 03) để thấy tên mặc định.
Kết luận
Bạn đã hiểu máy quét của Spring: stereotype annotations dán nhãn class, @ComponentScan quét từ gốc trở xuống, và quy tắc vàng "main class ở gốc, bean nằm dưới". Nhưng còn class của thư viện ngoài — nơi bạn không thể dán @Component lên — thì sao? Ngày 06: @Configuration và @Bean — đăng ký bean thủ công. Hẹn gặp lại!
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.


