Spring Framework là một trong những kỹ năng quan trọng và được yêu cầu nhiều nhất đối với các lập trình viên Java, dù bạn mới ra trường hay là một kiến trúc sư dày dặn kinh nghiệm. Bài viết này sẽ tổng hợp 40+ câu hỏi phỏng vấn Spring thường gặp, phân loại theo từng nhóm kiến thức và cấp độ từ Junior đến Senior, giúp bạn hệ thống hóa kiến thức và tự tin “chinh phục” các buổi phỏng vấn sắp tới.
Tổng hợp các nhóm kiến thức thường gặp
Một buổi phỏng vấn về Spring thường xoay quanh các nhóm chủ đề chính sau đây:
- Các nguyên tắc cốt lõi: Đây là nền tảng của Spring, bao gồm Inversion of Control (IoC), Dependency Injection (DI), và cách Spring quản lý các đối tượng (Beans). Đây là phần không thể thiếu trong bất kỳ cuộc phỏng vấn nào.
- Xây dựng ứng dụng Web/API: Tập trung vào Spring MVC, cách nó xử lý các HTTP request, các annotations quan trọng và kiến trúc tổng thể.
- Tương tác dữ liệu và Giao dịch: Khai thác khả năng làm việc với cơ sở dữ liệu của Spring, đặc biệt là Spring Data JPA và cách quản lý tính toàn vẹn dữ liệu với @Transactional.
- Spring Boot: Spring Boot đã trở thành tiêu chuẩn de-facto. Các câu hỏi sẽ xoay quanh cơ chế tự động cấu hình (auto-configuration), starters, và các tính năng giúp đơn giản hóa việc phát triển.
- Các chủ đề nâng cao: Dành cho các vị trí Middle và Senior, bao gồm Lập trình hướng khía cạnh (AOP), bảo mật với Spring Security, và Caching.
- Kiến trúc hiện đại và Cloud-Native: Với sự trỗi dậy của microservices, các kiến thức về Spring Cloud, Reactive Programming (WebFlux) ngày càng được coi trọng, đặc biệt cho các vị trí Senior và Architect.
Đọc chi tiết: Spring là gì? Spring Framework là gì?
Câu hỏi phỏng vấn Spring: Các nguyên tắc cốt lõi – IoC, DI, và Spring Beans
Nhóm câu hỏi này là nền tảng và thường dành cho các lập trình viên ở mọi cấp độ, đặc biệt là Junior để kiểm tra kiến thức cơ bản nhất.
1. Inversion of Control (IoC) và Dependency Injection (DI) là gì? Giải thích Nguyên tắc Đảo ngược Phụ thuộc (Dependency Inversion Principle – DIP).
Inversion of Control (IoC)
Là một nguyên tắc thiết kế phần mềm, trong đó quyền kiểm soát (control) việc khởi tạo và quản lý các đối tượng được “đảo ngược” từ code của lập trình viên sang cho một framework hoặc container. Thay vì bạn tự tạo đối tượng bằng từ khóa new, Spring Container sẽ làm điều đó cho bạn.
Dependency Injection (DI)
Là một cách triển khai cụ thể của IoC. Thay vì một đối tượng tự tìm kiếm hoặc khởi tạo các phụ thuộc (dependencies – các đối tượng khác mà nó cần), các phụ thuộc này sẽ được “tiêm” (inject) vào đối tượng từ bên ngoài bởi Spring Container.
Dependency Inversion Principle (DIP)
Là một trong năm nguyên tắc SOLID, DIP cho rằng:
- Các module cấp cao không nên phụ thuộc vào các module cấp thấp. Cả hai nên phụ thuộc vào một lớp trừu tượng (abstraction, thường là interface).
- Các lớp trừu tượng không nên phụ thuộc vào chi tiết. Chi tiết nên phụ thuộc vào trừu tượng. DI chính là một cơ chế giúp tuân thủ nguyên tắc DIP một cách dễ dàng.
2. Spring Bean là gì? Scope mặc định của một Spring Bean là gì?
Spring Bean: Là một đối tượng được khởi tạo, lắp ráp và quản lý bởi Spring IoC Container. Chúng là xương sống của bất kỳ ứng dụng Spring nào.
Scope mặc định là Singleton. Điều này có nghĩa là trong toàn bộ IoC container, chỉ có một và chỉ một instance của bean đó được tạo ra. Mọi yêu cầu đến bean này đều sẽ nhận về cùng một đối tượng.
3. Có những cách nào để cấu hình một ứng dụng Spring? Bạn ưa thích cách nào và tại sao?
Có ba cách chính:
- Cấu hình bằng XML (XML-based configuration): Cách tiếp cận truyền thống.
- Cấu hình bằng Annotation (Annotation-based configuration): Sử dụng các annotation như @Component, @Service, @Autowired.
- Cấu hình bằng Java (Java-based configuration): Sử dụng các lớp @Configuration và các phương thức @Bean.
Trong hầu hết các dự án hiện đại, lựa chọn ưa thích của tôi là cấu hình bằng Annotation kết hợp với Java-based. Lý do là nó giúp giảm thiểu sự dài dòng của XML, giữ cấu hình gần với code logic hơn, dễ dàng refactor và tận dụng được sự an toàn kiểu (type-safety) của Java.
4. Giải thích ba loại Dependency Injection. Loại nào được đội ngũ Spring khuyến nghị và tại sao?
- Constructor Injection: Phụ thuộc được truyền vào qua constructor.
- Setter Injection: Phụ thuộc được truyền vào qua một phương thức setter.
- Field Injection: Phụ thuộc được tiêm trực tiếp vào trường.
Loại được khuyến nghị là Constructor Injection vì có các ưu điểm sau:
- Bất biến (Immutability): Cho phép khai báo các trường là final.
- Đảm bảo sự tồn tại: Đối tượng luôn được tạo ra với đầy đủ các phụ thuộc cần thiết.
- Dễ dàng cho việc test: Dễ dàng khởi tạo đối tượng với các đối tượng giả (mock).
- Tránh phụ thuộc vòng (Circular Dependencies): Spring sẽ báo lỗi ngay khi khởi động.
5. Spring hỗ trợ những bean scope nào? Giải thích từng loại.
- singleton: (Mặc định) Chỉ một instance duy nhất cho mỗi container.
- prototype: Một instance mới được tạo ra mỗi khi có yêu cầu.
- request: (Web) Một instance cho mỗi HTTP request.
- session: (Web) Một instance cho mỗi HTTP session.
- application: (Web) Một instance cho ServletContext.
- websocket: (Web) Một instance cho WebSocket session.
6. Sự khác biệt giữa BeanFactory và ApplicationContext là gì?
- BeanFactory: Là giao diện gốc, cung cấp chức năng cơ bản của IoC container, sử dụng cơ chế khởi tạo lười biếng (lazy initialization).
- ApplicationContext: Là một giao diện con của BeanFactory, cung cấp nhiều tính năng cấp cao hơn (AOP, i18n, event propagation) và mặc định sử dụng cơ chế khởi tạo sớm (eager initialization) cho các bean singleton.
Trong hầu hết các ứng dụng, ApplicationContext là lựa chọn được ưu tiên.
7. Giải thích toàn bộ vòng đời của một Spring Bean.
- Khởi tạo (Instantiation): Spring Container khởi tạo instance của bean.
- Tiêm thuộc tính (Populate Properties): Spring tiêm các phụ thuộc (DI).
- Nhận biết (Awareness): Nếu bean implement các interface Aware (như BeanNameAware), các phương thức tương ứng sẽ được gọi.
- Xử lý trước khởi tạo (Pre-initialization): Các BeanPostProcessor được áp dụng (postProcessBeforeInitialization).
- Khởi tạo (Initialization): Các callback khởi tạo được gọi (ví dụ: afterPropertiesSet() của InitializingBean hoặc init-method).
- Xử lý sau khởi tạo (Post-initialization): Các BeanPostProcessor được áp dụng (postProcessAfterInitialization).
- Sẵn sàng sử dụng (Bean is ready).
- Hủy (Destruction): Khi container đóng lại, các callback hủy được gọi (ví dụ: destroy() của DisposableBean hoặc destroy-method).
Câu hỏi phỏng vấn Spring: Xây dựng ứng dụng Web và API với Spring MVC
Nhóm câu hỏi này kiểm tra kỹ năng xây dựng ứng dụng web, thường dành cho các lập trình viên Junior và Middle.
8. Giải thích kiến trúc của Spring MVC và vai trò của DispatcherServlet.
Kiến trúc của Spring MVC xoay quanh mẫu thiết kế “Front Controller”. DispatcherServlet chính là Front Controller. Nó là servlet trung tâm, nhận tất cả các HTTP request đến và điều phối chúng đến các thành phần xử lý phù hợp (HandlerMapping, HandlerAdapter, ViewResolver…). Vai trò của nó là “nhạc trưởng” của toàn bộ luồng xử lý request.
9. Mô tả luồng đi của HTTP request từ khi nó được gửi đi cho đến khi nó đến được hàm xử lý (action) tương ứng trong Controller ở phía server.
- Client gửi request.
- DispatcherServlet nhận request.
- DispatcherServlet hỏi HandlerMapping để tìm controller xử lý.
- DispatcherServlet tìm một HandlerAdapter phù hợp để thực thi controller.
- HandlerAdapter gọi phương thức trong Controller.
- Controller xử lý và trả về ModelAndView (hoặc đối tượng được serialize).
- ViewResolver (nếu có) tìm view tương ứng.
- View được render và trả về HTTP response cho client.
10. Sự khác biệt giữa @Controller và @RestController là gì?
- @Controller: Đánh dấu một lớp là một controller MVC. Các phương thức thường trả về tên view logic để ViewResolver xử lý.
- @RestController: Là sự kết hợp giữa @Controller và @ResponseBody. Mọi phương thức trong lớp này đều mặc định serialize giá trị trả về thành JSON/XML và ghi vào HTTP response body, thích hợp cho việc xây dựng RESTful API.
11. Giải thích mục đích của @RequestMapping và các annotation liên quan.
@RequestMapping dùng để ánh xạ các HTTP request đến các phương thức xử lý trong controller. Nó có thể chỉ định URL path, HTTP method, consumes/produces media types…
Các annotation liên quan: @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping là các annotation viết tắt chuyên biệt cho từng HTTP method, giúp code ngắn gọn và rõ ràng hơn.
12. Sự khác biệt giữa @RequestParam và @PathVariable là gì?
- @PathVariable: Trích xuất giá trị từ URI path (ví dụ: /users/{id}).
- @RequestParam: Trích xuất giá trị từ query parameter trong URL (ví dụ: /users/search?name=john) hoặc từ form-data.
13. Trong Spring MVC, có những cách nào để xử lý exception (ngoại lệ)?
Các cách xử lý exception trong Spring MVC:
- @ResponseStatus: Đặt trên lớp exception tùy chỉnh để trả về HTTP status code cố định.
- @ExceptionHandler: Định nghĩa một phương thức trong controller để xử lý exception từ chính controller đó.
- @ControllerAdvice / @RestControllerAdvice: Cách được khuyến nghị nhất. Tạo một lớp toàn cục để xử lý exception từ tất cả các controller, giúp tập trung logic xử lý lỗi.
Đọc chi tiết: Spring MVC là gì: Hướng dẫn xây dựng ứng dụng với Spring MVC
Câu hỏi phỏng vấn Spring: Kiến thức về Spring Boot
Kiến thức về Spring Boot là yêu cầu cơ bản dành cho level từ Junior. Ngoài ra, ở cấp độ Middle trở lên, bạn có thể được hỏi sâu về cơ chế bên trong Spring Boot.
14. Spring Boot là gì và ưu điểm của nó?
Spring Boot là một dự án giúp đơn giản hóa việc xây dựng các ứng dụng Spring độc lập, sẵn sàng cho production.
Ưu điểm: Tự động cấu hình (Auto-Configuration), cung cấp cấu hình mặc định “có chính kiến”, hệ thống Starter Dependencies, tích hợp sẵn server, và các tính năng production-ready (Actuator).
Đọc chi tiết: Spring Boot là gì: Chi tiết cách xây dựng ứng dụng với Spring Boot
15. Mục đích của annotation @SpringBootApplication là gì?
@SpringBootApplication là một annotation tiện ích, kết hợp ba annotation khác:
- @SpringBootConfiguration: Đánh dấu lớp là một lớp cấu hình.
- @EnableAutoConfiguration: Kích hoạt cơ chế tự động cấu hình.
- @ComponentScan: Quét các component từ package của lớp này trở xuống.
16. Spring Boot Starters là gì? Cho một ví dụ.
Spring Boot Starters là một bộ các dependency descriptor giúp đơn giản hóa việc quản lý thư viện bằng cách gộp các dependency thường được sử dụng cùng nhau vào một gói duy nhất.
Ví dụ: spring-boot-starter-web để xây dựng ứng dụng web.
17. Spring Profiles là gì và làm thế nào để sử dụng chúng?
Spring Profiles là cơ chế cho phép đăng ký các bean hoặc file cấu hình khác nhau cho các môi trường khác nhau (dev, staging, prod).
Cách sử dụng: Dùng @Profile(“dev”) trên bean, hoặc tạo các file application-{profile}.properties, và kích hoạt profile bằng thuộc tính spring.profiles.active.
18. Giải thích cơ chế tự động cấu hình. Làm thế nào để loại trừ một cấu hình?
- Cơ chế tự động cấu hình: Spring Boot quét classpath, và dựa trên các thư viện có mặt, nó sẽ tự động cấu hình các bean cần thiết bằng cách sử dụng các lớp @Configuration có điều kiện.
- Cách loại trừ một cấu hình: Sử dụng thuộc tính exclude của annotation @SpringBootApplication hoặc @EnableAutoConfiguration.
19. Spring Boot Actuator là gì? Nêu tên ba endpoint hữu ích.
Spring Boot Actuator giúp giám sát và quản lý ứng dụng trong môi trường production thông qua các endpoint.
Ba endpoint hữu ích:
- /actuator/health: Kiểm tra “sức khỏe” của ứng dụng.
- /actuator/info: Hiển thị thông tin tùy chỉnh về ứng dụng.
- /actuator/metrics: Hiển thị các chỉ số của ứng dụng.
Đọc chi tiết: Spring Boot Actuator là gì: Cách quản lý ứng dụng trên production
Câu hỏi phỏng vấn Spring: Tương tác dữ liệu và tính toàn vẹn giao dịch
Nhóm câu hỏi này đi sâu vào việc làm việc với cơ sở dữ liệu, thường dành cho các lập trình viên cấp độ Middle.
20. Spring Data JPA là gì? Mối quan hệ giữa JPA, Spring Data JPA và Hibernate là gì?
- JPA (Java Persistence API): Là một đặc tả (specification) của Java, định nghĩa API chuẩn cho ORM.
- Hibernate: Là một implementation nổi tiếng của JPA.
- Spring Data JPA: Là một dự án con của Spring, nằm trên cùng JPA. Mục tiêu của nó là làm cho việc triển khai các repository (lớp truy cập dữ liệu) trở nên cực kỳ đơn giản, giảm thiểu code boilerplate.
Mối quan hệ: Spring Data JPA sử dụng JPA, và JPA được implement bởi một provider như Hibernate.
Đọc chi tiết: Spring Data JPA: Hướng dẫn toàn tập từ cơ bản đến nâng cao
21. Giải thích giao diện JpaRepository. Nó cung cấp những lợi ích gì?
JpaRepository là một giao diện trong Spring Data JPA. Khi kế thừa từ JpaRepository<T, ID>, bạn sẽ tự động có được các phương thức CRUD, phân trang và sắp xếp mà không cần viết implementation.
Lợi ích: Giảm code boilerplate, cung cấp query-method derivation (tự tạo truy vấn từ tên phương thức).
22. Mục đích của annotation @Transactional là gì? (Level: Middle)
@Transactional cho phép Spring quản lý các giao dịch cho một phương thức/lớp. Spring sẽ tạo một proxy, bắt đầu một giao dịch trước khi phương thức chạy. Nếu phương thức thành công, giao dịch được commit. Nếu có RuntimeException, giao dịch sẽ được rollback.
23. Điều gì xảy ra nếu một phương thức @Transactional gọi một phương thức @Transactional khác trong cùng một lớp? Một giao dịch mới có được tạo ra không?
Không, một giao dịch mới sẽ không được tạo ra. Giao dịch hiện tại sẽ được tiếp tục sử dụng. Lý do là các lời gọi phương thức nội bộ (this.otherMethod()) không đi qua proxy do Spring tạo ra, do đó proxy không có cơ hội can thiệp để áp dụng hành vi của @Transactional.
24. Giải thích về lan truyền giao dịch (transaction propagation).
Lan truyền giao dịch định nghĩa cách một phương thức @Transactional hành xử khi được gọi từ một phương thức khác cũng có @Transactional:
- REQUIRED (Mặc định): Tham gia giao dịch hiện tại; nếu không có, tạo giao dịch mới.
- REQUIRES_NEW: Luôn bắt đầu một giao dịch mới. Giao dịch hiện tại (nếu có) sẽ được tạm dừng.
- NESTED: Bắt đầu một giao dịch “lồng nhau” sử dụng savepoint. Nếu rollback, nó chỉ quay lại savepoint.
25. Mức độ cô lập giao dịch (transaction isolation levels) là gì?
Mức độ cô lập giao dịch định nghĩa mức độ một giao dịch bị ảnh hưởng bởi các giao dịch khác đang chạy đồng thời, giúp giải quyết các vấn đề như Dirty Read, Non-Repeatable Read, và Phantom Read.
Ví dụ: Vấn đề mà REPEATABLE_READ giải quyết so với READ_COMMITTED là “Non-Repeatable Read”. Nó đảm bảo rằng nếu một giao dịch đọc một dòng dữ liệu nhiều lần, nó sẽ luôn nhận được cùng một giá trị, ngay cả khi các giao dịch khác đã cập nhật và commit dòng đó.
Câu hỏi phỏng vấn Spring: Các chủ đề nâng cao trong kiến trúc ứng dụng
Nhóm câu hỏi này đòi hỏi sự hiểu biết sâu sắc, thường dành cho các lập trình viên cấp độ Mid-Senior và Senior.
26. Lập trình Hướng khía cạnh (AOP) là gì? Nó giải quyết vấn đề gì?
- AOP là một mô hình lập trình giúp tăng tính module hóa bằng cách cho phép tách biệt các mối quan tâm xuyên suốt (cross-cutting concerns) như logging, transaction, security.
- Vấn đề giải quyết: AOP giúp định nghĩa các logic này ở một nơi duy nhất (Aspect) và áp dụng chúng vào các điểm cần thiết, tránh lặp lại code và làm cho code nghiệp vụ sạch hơn.
27. Giải thích các khái niệm chính trong AOP.
- Aspect: Một module chứa logic của một cross-cutting concern.
- Join Point: Một điểm trong quá trình thực thi chương trình (ví dụ: việc thực thi một phương thức).
- Advice: Hành động được thực hiện bởi một aspect tại một join point (ví dụ: code logging).
- Pointcut: Một biểu thức để chọn ra các join point mà advice sẽ được áp dụng.
- Weaving: Quá trình liên kết các aspect với các đối tượng mục tiêu (thường tại runtime).
28. Các loại advice khác nhau trong Spring AOP là gì?
- @Before: Thực thi trước join point.
- @AfterReturning: Thực thi sau khi join point hoàn thành bình thường.
- @AfterThrowing: Thực thi nếu join point ném ra exception.
- @After: Thực thi sau khi join point kết thúc (bất kể thành công hay thất bại).
- @Around: Advice mạnh mẽ nhất, bao quanh join point, cho phép thực thi logic trước và sau khi join point chạy.
29. Authentication và Authorization là gì? Spring Security xử lý chúng như thế nào?
- Authentication (Xác thực): Là quá trình xác định “bạn là ai”.
- Authorization (Phân quyền): Là quá trình xác định “bạn được phép làm gì”.
Spring Security sử dụng AuthenticationManager và các AuthenticationProvider để xác thực. Sau đó, nó sử dụng SecurityContext và các cấu hình để thực hiện phân quyền dựa trên GrantedAuthority.
Đọc chi tiết: Spring security: Khám phá framework bảo mật hàng đầu cho ứng dụng Java
30. Giải thích vai trò của SecurityFilterChain trong Spring Security.
SecurityFilterChain là một chuỗi các bộ lọc (filters) mà mỗi HTTP request phải đi qua. Mỗi bộ lọc có một trách nhiệm cụ thể (xử lý login, phân quyền…). Bạn có thể định nghĩa nhiều SecurityFilterChain bean, mỗi bean áp dụng cho một nhóm URL pattern khác nhau, cho phép cấu hình bảo mật rất linh hoạt.
31. CSRF là gì và Spring Security bảo vệ chống lại nó như thế nào?
CSRF (Cross-Site Request Forgery) là một kiểu tấn công buộc người dùng đã đăng nhập thực hiện một hành động không mong muốn.
Spring Security sử dụng Synchronizer Token Pattern, tạo ra một token CSRF ngẫu nhiên, lưu trong session và chèn vào form. Khi submit, token này được gửi và so sánh ở phía server để xác thực request.
32. Bạn sẽ bảo mật một REST API như thế nào?
Tôi thường sử dụng các cơ chế không trạng thái (stateless) như JWT (JSON Web Tokens) hoặc OAuth2.
Sự khác biệt so với web truyền thống:
- Web truyền thống thường dùng session (stateful), API thường stateless.
- Bảo vệ CSRF dựa trên session, thường bị vô hiệu hóa cho API stateless.
- API thường cần cấu hình CORS (Cross-Origin Resource Sharing).
33. Làm thế nào để kích hoạt caching trong Spring?
Để kích hoạt caching, cần thêm annotation @EnableCaching vào một lớp @Configuration và cung cấp một CacheManager bean. Các annotation phổ biến:
- @Cacheable: Nếu có trong cache, trả về kết quả; nếu không, thực thi phương thức và lưu kết quả vào cache.
- @CachePut: Luôn thực thi phương thức và cập nhật giá trị trong cache.
- @CacheEvict: Xóa một hoặc nhiều entry khỏi cache.
34. Một số chiến lược caching phổ biến là gì?
3 chiến lược caching phổ biến:
Cache-Aside (Lazy Loading)
Đây là chiến lược phổ biến và linh hoạt nhất. Với Cache-Aside, ứng dụng chịu trách nhiệm chính trong việc quản lý cache.
Cách hoạt động:
- Khi cần dữ liệu, ứng dụng sẽ tìm trong cache trước.
- Nếu có (Cache Hit), dữ liệu được trả về ngay.
- Nếu không có (Cache Miss), ứng dụng sẽ tự mình truy vấn database để lấy dữ liệu.
- Sau đó, ứng dụng sẽ ghi dữ liệu này vào cache để các lần gọi sau có thể sử dụng.
- Cuối cùng, trả dữ liệu cho client.
Ưu – nhược điểm:
| Ưu điểm | Khả năng phục hồi cao: Nếu cache bị lỗi, hệ thống vẫn có thể hoạt động (dù chậm hơn) bằng cách đọc trực tiếp từ database.Linh hoạt: Dữ liệu trong cache và database không nhất thiết phải giống hệt nhau. Ta có thể cache một đối tượng đã được tối ưu cho việc hiển thị (DTO). |
| Nhược điểm | Dữ liệu có thể bị cũ (Stale Data): Nếu có một tiến trình khác cập nhật thẳng vào database, cache sẽ không biết và trở nên lỗi thời.Độ trễ ở lần đọc đầu tiên: Request đầu tiên cho một dữ liệu chưa được cache sẽ luôn chậm hơn vì phải thực hiện cả thao tác đọc từ DB và ghi vào cache.Code phức tạp hơn: Logic kiểm tra cache và đọc từ DB thường bị lặp lại trong mã nguồn ứng dụng. |
Khi nào nên dùng: Rất phù hợp cho các hệ thống thiên về đọc (read-heavy) và có thể chấp nhận một độ trễ nhỏ trong việc cập nhật dữ liệu (ví dụ: danh sách sản phẩm, bài viết blog).
Read-Through
Cache provider chịu trách nhiệm đọc từ DB nếu cache miss.
Cách hoạt động:
- Ứng dụng chỉ cần hỏi cache.
- Nếu cache có dữ liệu, nó sẽ trả về.
- Nếu không có, chính cache provider sẽ tự động kết nối tới database, lấy dữ liệu, lưu vào chính nó rồi mới trả về cho ứng dụng.
Ưu – nhược điểm:
| Ưu điểm | Mã nguồn ứng dụng sạch sẽ: Logic đọc dữ liệu từ DB được trừu tượng hóa hoàn toàn, ứng dụng không cần quan tâm đến nó. |
| Nhược điểm | Kém linh hoạt: Thường yêu cầu dữ liệu trong cache và DB có cấu trúc giống nhau.Phụ thuộc vào cache provider: Cần cấu hình và phụ thuộc vào tính năng của thư viện cache (ví dụ: Hazelcast, EhCache). |
Khi nào nên dùng: Khi bạn muốn một giải pháp caching gọn gàng, tách biệt hoàn toàn logic cache ra khỏi logic nghiệp vụ.
Write-Through
Cache provider chịu trách nhiệm ghi đồng bộ xuống DB.
Lớp trừu tượng caching mặc định của Spring (@Cacheable) gần giống nhất với chiến lược Cache-Aside.
Cách hoạt động:
- Khi ứng dụng ghi dữ liệu, nó sẽ ghi vào cache.
- Ngay sau đó, cache provider sẽ ghi đồng bộ dữ liệu đó xuống database.
- Thao tác chỉ hoàn tất khi cả hai nơi đều đã được ghi thành công.
Ưu – nhược điểm:
| Ưu điểm | Tính nhất quán dữ liệu rất cao: Dữ liệu trong cache và database luôn được đồng bộ. Dữ liệu đọc ra ngay sau khi ghi luôn là dữ liệu mới nhất. |
| Nhược điểm | Tăng độ trễ khi ghi (Write Latency): Thao tác ghi sẽ chậm hơn vì phải thực hiện ghi ở hai nơi. |
Khi nào nên dùng: Lý tưởng cho các dữ liệu quan trọng, yêu cầu phải luôn chính xác và thường được đọc ngay sau khi ghi (ví dụ: thông tin tài khoản người dùng, số dư ví).
35. Lớp trừu tượng caching mặc định là gì?
Lớp trừu tượng caching của Spring được định nghĩa bởi hai interface chính: CacheManager và Cache.
- CacheManager: Quản lý các cache.
- Cache: Đại diện cho một cache cụ thể, nơi bạn có thể lưu trữ (put), truy xuất (get), và xóa (evict) dữ liệu.
Spring không cung cấp một cơ chế caching thực sự trong lõi của nó. Tuy nhiên, nếu bạn không cấu hình bất kỳ nhà cung cấp nào (như Redis, EhCache), Spring Boot sẽ tự động sử dụng ConcurrentMapCacheManager, một implementation đơn giản dùng ConcurrentHashMap để lưu trữ cache trong bộ nhớ.
Câu hỏi phỏng vấn Spring: Spring trong bối cảnh Cloud-Native hiện đại
Nhóm câu hỏi này tập trung vào các kiến trúc phức tạp, thường dành cho các lập trình viên Senior và Architect.
36. Thách thức của microservices và Spring Cloud giải quyết chúng như thế nào?
Thách thức: Service Discovery, Configuration Management, Resilience, API Gateway, Distributed Tracing.
Spring Cloud giải quyết: Cung cấp các công cụ như Eureka (Service Discovery), Config Server, Circuit Breaker, API Gateway, Sleuth (Tracing).
37. API Gateway là gì và tại sao nó quan trọng trong kiến trúc microservices?
- API Gateway: Là một server đóng vai trò là điểm vào duy nhất cho tất cả các request từ client đến hệ thống microservices.
- Mục đích: Đơn giản hóa client, routing, xử lý các cross-cutting concerns (authentication, rate limiting), và response aggregation.
38. Giải thích mẫu thiết kế Circuit Breaker là gì, tại sao nó quan trọng?
Là một mẫu thiết kế dùng để ngăn chặn lỗi lan truyền (cascading failures). Nó có ba trạng thái: CLOSED (cho phép request), OPEN (từ chối request ngay lập tức), và HALF-OPEN (thử cho một vài request đi qua để kiểm tra service đã phục hồi chưa).
Nó giúp hệ thống nhanh chóng thất bại (fail-fast) và tăng tính ổn định.
39. Lập trình Phản ứng (Reactive Programming) là gì? Spring hỗ trợ Reactive Programming như thế nào?
Reactive Programming là một mô hình lập trình xoay quanh các luồng dữ liệu bất đồng bộ (asynchronous data streams) và việc lan truyền sự thay đổi.
Điểm khác biệt chính so với lập trình truyền thống (imperative):
- Lập trình truyền thống (Blocking): Bạn viết code để yêu cầu dữ liệu và phải chờ đợi (block) cho đến khi nhận được kết quả rồi mới xử lý tiếp. Giống như bạn gọi điện thoại và phải giữ máy chờ người kia trả lời.
- Lập trình phản ứng (Non-blocking): Bạn định nghĩa một chuỗi các hành động sẽ được thực thi khi dữ liệu đến. Hệ thống sẽ được thông báo khi có dữ liệu để xử lý mà không cần phải chờ đợi. Giống như bạn gửi một tin nhắn và sẽ nhận được thông báo khi có người trả lời, trong thời gian đó bạn có thể làm việc khác.
Mô hình này đặc biệt hiệu quả trong việc xây dựng các hệ thống có tính đáp ứng cao, khả năng chịu tải tốt và sử dụng tài nguyên hiệu quả.
Spring hỗ trợ Reactive Programming như thế nào?
Spring hỗ trợ mô hình này chủ yếu thông qua Spring WebFlux – một web framework hoàn toàn non-blocking, được xây dựng dựa trên thư viện Project Reactor. Nó được thiết kế để xử lý một lượng lớn các kết nối đồng thời với số lượng luồng (thread) tối thiểu, thay thế cho mô hình blocking của Spring MVC truyền thống.
40. Giải thích các khái niệm Mono và Flux trong Spring WebFlux
Để làm việc với các luồng dữ liệu, WebFlux sử dụng hai kiểu “Publisher” chính từ Project Reactor:
- Mono<T>: Đại diện cho một luồng dữ liệu bất đồng bộ chứa 0 hoặc 1 phần tử. Rất phù hợp cho các API chỉ trả về một kết quả duy nhất (hoặc không có gì), ví dụ:
getUserById(id). - Flux<T>: Đại diện cho một luồng dữ liệu bất đồng bộ chứa 0 đến N phần tử. Thường được dùng cho các API trả về một tập hợp dữ liệu, ví dụ:
getAllUsers().
Tổng kết
Để ôn luyện phỏng vấn Spring bài bản, hiệu quả, bạn cần đi từ những khái niệm cốt lõi nhất mà bất kỳ lập trình viên Java nào cũng cần biết, đến những kiến trúc phức tạp trong thế giới cloud-native. Hy vọng rằng với bộ câu hỏi phỏng vấn Spring kèm câu trả lời chi tiết này, bạn đã có một cái nhìn tổng quan và hệ thống hơn về những gì nhà tuyển dụng mong đợi.
Hãy nhớ rằng, việc hiểu sâu bản chất vấn đề quan trọng hơn là chỉ thuộc lòng câu trả lời. Chúc bạn có một buổi phỏng vấn thành công!

