Spring vs Spring Boot
Xem thửBạn nghe cả hai tên: "Spring" và "Spring Boot", và nhiều tài liệu dùng lẫn lộn. Một đồng nghiệp đọc rằng "Boot chỉ là Spring có sẵn cấu hình" và kết luận không cần học Spring Framework; ba tháng sau họ gặp một BeanCurrentlyInCreationException và không biết bắt đầu từ đâu. Một đồng nghiệp khác nâng một dự án từ Boot 3 lên Boot 4 theo trí nhớ và mất một ngày để tìm vì sao @MockBean không còn biên dịch được, spring-boot-starter-web bị đánh dấu deprecated và Undertow biến mất. Chọn cái nào để bắt đầu một dịch vụ mới, và học Spring Framework riêng có cần không?
Bài này trả lời bằng thí nghiệm thay vì định nghĩa: chạy một ứng dụng web chỉ với Spring Framework (không Boot, 48 dòng, Tomcat nhúng) để thấy Boot thực sự thêm gì, rồi kiểm chứng từng khác biệt Boot 3 sang Boot 4 trên kho Maven và trên các jar của Boot 4.1.1.
Sau bài này, bạn có thể
- Phân biệt vai trò của Spring Framework và Spring Boot, và chứng minh nó bằng một ứng dụng không có Boot.
- Nêu những việc Spring Boot tự làm và những việc nó không làm.
- Liệt kê các thay đổi từ Boot 3 lên Boot 4 đã kiểm chứng và tác động của chúng.
- Chọn khi nào cần đọc tài liệu Spring Framework dù đang dùng Boot.
Hai lớp, hai vai trò
Spring Framework cung cấp mô hình lập trình: IoC container và Dependency Injection, AOP, Spring MVC, trừu tượng hóa transaction, truy cập dữ liệu. Spring Boot đóng gói Spring Framework cùng quy ước để khởi tạo và chạy ứng dụng nhanh: starter, auto-configuration, máy chủ nhúng, Actuator.
Cách đọc hình
Mũi tên đi từ mã của bạn xuống JDK. Lớp xanh dương là phần Spring Boot thêm vào: starter gom dependency, auto-configuration tạo bean theo classpath, Actuator và máy chủ nhúng phục vụ vận hành. Lớp xanh lá là Spring Framework: nơi bean thật sự sống và request thật sự được xử lý. Boot tạo và nối các bean của Spring Framework giúp bạn, nó không thay thế chúng.
Thí nghiệm: một ứng dụng web chỉ với Spring Framework
Lab dựng một ứng dụng không có Spring Boot: tự tạo Tomcat nhúng, tự đăng ký DispatcherServlet, tự bật Spring MVC bằng @EnableWebMvc:
public class PlainSpring {
@Configuration
@EnableWebMvc
@ComponentScan(basePackageClasses = PlainSpring.class)
static class WebConfig {
}
public record UserResponse(long id, String name) {
}
@RestController
static class UserController {
@GetMapping("/api/users/7")
UserResponse get() {
return new UserResponse(7, "An");
}
}
public static void main(String[] args) throws Exception {
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir(new File(System.getProperty("java.io.tmpdir"), "plain-tomcat").getAbsolutePath());
tomcat.setPort(18202);
tomcat.getConnector();
Context context = tomcat.addContext("", null);
AnnotationConfigWebApplicationContext spring = new AnnotationConfigWebApplicationContext();
spring.register(WebConfig.class);
Tomcat.addServlet(context, "dispatcher", new DispatcherServlet(spring)).setLoadOnStartup(1);
context.addServletMappingDecoded("/", "dispatcher");
tomcat.start();
tomcat.getServer().await();
}
}
PLAIN started in 2543 ms
GET /api/users/7 -> {"id":7,"name":"An"} [200]
GET /nope -> <!doctype html><html lang="en"><head><title>HTTP Status 404 – Not Found</title> ... [404]
GET /actuator/health -> HTML 404
Kết quả cho thấy cả hai chiều. Chiều thứ nhất: ứng dụng chạy được và JSON hoạt động, chứng minh Spring Boot không thay thế Spring Framework: DispatcherServlet, @RestController, bộ chuyển đổi JSON đều là của Spring Framework 7. Chiều thứ hai: những thứ bạn quen với Boot không có: đường dẫn không tồn tại trả trang HTML 404 của Tomcat (không phải JSON {"status":404,...}), không có /actuator/health, không đọc application.properties, không có profile, không có --server.port, không có tắt êm có cấu hình, không có báo cáo lỗi khởi động. Bốn việc lắp ráp bạn phải tự làm (tạo máy chủ, đăng ký servlet, bật MVC, chọn bộ chuyển đổi) và đó mới chỉ là phần tối thiểu; ứng dụng thật còn cần nguồn dữ liệu, giao dịch, cấu hình ngoài, logging, đo lường.
Về thời gian (lưu ý hai ứng dụng không cùng phạm vi, nên chỉ so sánh bậc độ lớn): ứng dụng Spring thuần khởi động trong 2,54 giây, dự án Boot mẫu có Web và Actuator trong 3,98 giây (bài Tổng quan). Boot không "nhanh hơn" lúc chạy; phần chênh lệch là hạ tầng bổ sung (Actuator, auto-configuration). Điều Boot rút ngắn là thời gian của bạn: 48 dòng cấu hình hạ tầng thành một annotation và một dependency.
Đang tải Animation…
Thử và quan sát
Dự đoán trước: để có endpoint JSON đầu tiên, bạn cần lắp bao nhiêu thành phần hạ tầng trước khi viết dòng mã nghiệp vụ nào? Chạy biến thể Spring Boot rồi quay lại Spring Framework thuần. Chú ý bước cuối cùng: dù Boot lo việc lắp ráp, các khái niệm của Spring Framework vẫn có mặt khi bạn gỡ lỗi.
So sánh thực dụng
| Khía cạnh | Spring Framework thuần | Spring Boot |
|---|---|---|
| Cấu hình | Khai báo bean và hạ tầng bằng XML hoặc @Configuration | Auto-configuration cộng property |
| Chạy ứng dụng | Thường đóng gói WAR lên máy chủ ngoài, hoặc tự dựng Tomcat (như lab) | Jar có máy chủ nhúng, chạy bằng java -jar |
| Phiên bản thư viện | Tự chọn và tự kiểm tra tương thích | BOM của Boot quản lý phiên bản |
| Cấu hình ngoài, profile | Tự cài đặt | application.yml, biến môi trường, profile |
| Lỗi khởi động | Stack trace | Báo cáo Description, Reason, Action |
| Vận hành | Tự dựng | Actuator, tắt êm, probe |
| Khởi tạo dự án | Tự dựng | start.spring.io, DevTools |
Khi nào vẫn phải hiểu Spring Framework
Luôn luôn. Auto-configuration chỉ tạo bean; bạn vẫn đọc log của ApplicationContext, xử lý vòng phụ thuộc, hiểu proxy của @Transactional. Các bài về IoC, DI, scope, proxy và AOP trong khóa học này là Spring Framework thuần: chúng áp dụng nguyên vẹn trong Boot. Khóa học nên được học theo thứ tự: khái niệm cốt lõi của Spring trước, sau đó tính năng của Boot.
Từ Boot 3 lên Boot 4: khác biệt đã kiểm chứng
Mỗi dòng dưới đây được kiểm chứng trên kho Maven hoặc trên các jar của Boot 4.1.1 / Spring Framework 7.0.9:
| Nội dung | Boot 3.x | Boot 4.x (đã kiểm chứng) |
|---|---|---|
| Starter web | spring-boot-starter-web | spring-boot-starter-webmvc; starter cũ vẫn có ở 4.1.1 nhưng mô tả ghi "deprecated in favor of spring-boot-starter-webmvc" |
| Máy chủ nhúng | Tomcat 10.1, Jetty, Undertow | Tomcat 11.0.24 mặc định; Jetty còn (spring-boot-starter-jetty có ở 4.1.1); Undertow không có starter ở 4.1.1 (bản mới nhất trong kho là 4.0.0-M1) |
| Servlet | 6.0 | 6.1 trở lên (Jakarta EE 11) |
| Spring Framework | 6.x | 7.0.9 |
| JSON mặc định | Jackson 2 (com.fasterxml.jackson) | Jackson 3 (tools.jackson, bản 3.1.5); Jackson 2.21.5 vẫn có trong BOM cho thư viện chưa chuyển |
| Mock trong test | @MockBean, @SpyBean | Hai lớp MockBean và SpyBean không còn trong spring-boot-test 4.1.1; dùng @MockitoBean, @MockitoSpyBean của Spring Framework 7 (spring-test) |
| Starter kiểm thử | spring-boot-starter-test | Thêm starter kiểm thử riêng theo từng starter (spring-boot-starter-webmvc-test, spring-boot-starter-actuator-test) |
| Auto-configuration | Phần lớn trong một jar | Chia theo module, nhiều tệp AutoConfiguration.imports (bài Auto-configuration: 12 tệp) |
| Java | 17 trở lên | 17 trở lên, tương thích tới 26 |
Hai điểm Initializr thể hiện rõ: dự án sinh cho Boot 4.1.1 với dependency web chứa spring-boot-starter-webmvc và hai starter kiểm thử spring-boot-starter-webmvc-test, spring-boot-starter-actuator-test (bài Chạy ứng dụng). Các khác biệt này được nhắc lại ở bài liên quan. Nếu dự án của bạn đang ở 3.x, cú pháp cũ vẫn dùng được cho đến khi bạn nâng cấp; hãy đọc hướng dẫn di chuyển (Migration Guide) trước khi nâng.
Hiểu lầm thường gặp
- "Dùng Boot thì không cần biết
ApplicationContext." Sai: phần lớn lỗi inject và vòng phụ thuộc nằm ở Spring Framework. - "Boot chỉ dành cho microservices." Sai: một ứng dụng nguyên khối chia module vẫn dùng Boot rất tốt.
- "Boot chạy nhanh hơn Spring." Không đúng: lab đo ứng dụng Spring thuần khởi động trong 2,54 giây; Boot không thay đổi tốc độ xử lý request, nó rút ngắn thời gian dựng và cấu hình.
- "Starter là phép màu." Starter chỉ gom thư viện; bean đến từ mã của bạn và auto-configuration có điều kiện.
Cái giá thật của hạ tầng tự dựng
Bốn mươi tám dòng mã của ứng dụng thuần ở trên chưa phải toàn bộ chi phí. Hãy đếm thứ bạn không có, rồi đếm kích thước thứ bạn phải mang:
classpath của lab Boot (webmvc, validation, actuator, aspectj): 90 jar, 36,7 MB
tập con đủ để chạy ứng dụng web thuần Spring (spring-core, beans, context, aop, expression, web, webmvc, tomcat-embed-core, Jackson 3, micrometer): 14 jar, khoảng 14,9 MB
Phần chênh lệch 76 jar, hơn 21 MB, là những gì Boot kéo thêm: Actuator và Micrometer, validation, thư viện ghi log, các module spring-boot-*, AspectJ. Đó không phải "phình to vô ích": mỗi thứ tương ứng với một khả năng ở bảng so sánh ở trên. Nhưng nó cho thấy một thực tế dễ quên khi chọn công nghệ: ứng dụng Spring thuần nhỏ hơn, khởi động ít hạ tầng hơn, và phù hợp với các trường hợp cần tối thiểu (công cụ nhỏ, hàm serverless, kiểm thử hạ tầng). Ngược lại với dịch vụ chạy lâu cần cấu hình ngoài, đo lường, kiểm tra sức khỏe, tắt êm, mỗi thứ bạn tự dựng là một thứ bạn phải viết, kiểm thử và vá lỗi.
Có một cách đo công bằng: đếm những thứ bạn sẽ phải thêm để ứng dụng thuần ngang với ứng dụng Boot tối thiểu trong sản xuất. Ứng dụng thuần của lab cần thêm, tối thiểu: đọc cấu hình ngoài và ghi đè theo môi trường (thang ưu tiên bài Application Properties), một @RestControllerAdvice cho phản hồi lỗi JSON, endpoint sức khỏe và hai trạng thái liveness và readiness, tắt êm có giới hạn thời gian (bài triển khai trên Cloud), cấu hình ghi log có mã tương quan, số liệu cho giám sát, và báo cáo lỗi khởi động dễ đọc. Mỗi mục là từ vài chục tới vài trăm dòng mã cùng kiểm thử, và mỗi mục Boot cho sẵn với một dependency hoặc một property đã được hàng nghìn dự án kiểm chứng. Đó là lý do câu trả lời thực dụng cho "chọn cái nào" gần như luôn là Boot: không phải vì Spring thuần không làm được, mà vì chi phí cơ hội của việc tự lắp ráp lớn hơn lợi ích của một classpath nhỏ hơn.
Điều ngược lại cũng đáng nói: ứng dụng Spring thuần của lab đã chứng minh các khái niệm bạn học (bean, DispatcherServlet, bộ chuyển đổi JSON) giống hệt trong Boot. Học Spring Framework trước không phải lãng phí; nó là phần không đổi qua mọi phiên bản Boot và giúp bạn gỡ lỗi khi hạ tầng tự động làm điều bạn không ngờ. Cách học hiệu quả: chạy ứng dụng thuần, thêm từng tính năng ở bảng trên bằng tay một lần để hiểu nó giải quyết gì, rồi dùng phiên bản Boot của tính năng đó mãi mãi.
Jackson 3 và chú thích của Jackson 2
Chuyển từ Jackson 2 sang 3 làm nhiều người lo toàn bộ chú thích @JsonProperty, @JsonIgnore trong mã sẽ phải đổi. Lab kiểm tra điều đó. Danh sách jar ở phần trên đã có jackson-annotations-2.21.jar cạnh jackson-core-3.1.5.jar và jackson-databind-3.1.5.jar: Jackson 3 vẫn dùng bộ chú thích cũ trong package com.fasterxml.jackson.annotation, chỉ các lớp xử lý (ObjectMapper đổi thành JsonMapper, package tools.jackson.databind) đổi tên. Lab dùng chú thích Jackson 2 trên một record và ghi bằng JsonMapper của Jackson 3:
record Item(@com.fasterxml.jackson.annotation.JsonProperty("item_name") String name, int qty) {
}
JsonMapper (tools.jackson.databind.json.JsonMapper) viết: {"item_name":"keyboard","qty":2}
đọc lại {"item_name":"mouse","qty":5} -> Item[name=mouse, qty=5]
Hai chiều đều đúng. Hệ quả thực tế cho một cuộc nâng cấp: (1) các lớp DTO của bạn dùng chú thích com.fasterxml.jackson.annotation.* không phải đổi import; (2) mã nào dùng trực tiếp ObjectMapper, JsonNode, JsonDeserializer, module Jackson 2 của bên thứ ba thì phải đổi sang tools.jackson.* hoặc chờ bản tương thích; (3) BOM của Boot vẫn quản lý Jackson 2 (jackson-2-bom.version là 2.21.5) cho thư viện chưa chuyển, nên hai dòng có thể cùng tồn tại trên classpath mà không đụng độ vì khác package. Khi nâng cấp, tìm kiếm import com.fasterxml.jackson.databind trong mã để lập danh sách thay đổi thật, bỏ qua annotation. Cấu hình spring.jackson.* vẫn là cách tùy biến mặc định (bài REST đo các mặc định của Jackson 3 như bỏ qua trường lạ).
Quyết định thiết kế và đánh đổi
| Quyết định | Khuyến nghị | Đánh đổi |
|---|---|---|
| Dịch vụ mới | Spring Boot | Phụ thuộc quy ước và BOM |
| Cần kiểm soát tối đa hạ tầng | Spring Framework thuần hiếm khi đáng | Phải tự làm mọi thứ |
| Nâng Boot 3 lên 4 | Theo Migration Guide, nâng từng bước | Sửa test, starter, Jackson |
| Máy chủ nhúng | Tomcat mặc định, Jetty nếu cần | Không còn Undertow |
| JSON | Jackson 3 mặc định | Thư viện dùng Jackson 2 cần cầu nối |
| Mock trong test | @MockitoBean | Đổi import khi nâng |
| Học | Spring Framework trước, Boot sau | Chậm hơn lúc đầu, vững hơn lâu dài |
| Phiên bản | Theo BOM | Theo nhịp của Boot |
Sổ tay xử lý sự cố
- Triệu chứng:
@MockBeankhông biên dịch được sau khi nâng. Nguyên nhân: đã bị gỡ ở Boot 4. Sửa:@MockitoBean. - Triệu chứng:
spring-boot-starter-undertowkhông tìm thấy. Nguyên nhân: không có ở 4.1.1. Sửa: Tomcat hoặc Jetty. - Triệu chứng: package
com.fasterxml.jacksonkhông khớp. Nguyên nhân: Boot 4 dùng Jackson 3 (tools.jackson). Sửa: cập nhật import, kiểm tra thư viện bên thứ ba. - Triệu chứng: cảnh báo deprecated về
spring-boot-starter-web. Nguyên nhân: đổi tên. Sửa:spring-boot-starter-webmvc. - Triệu chứng: không hiểu lỗi inject dù dùng Boot. Nguyên nhân: khái niệm Spring Framework. Sửa: các bài IoC, DI, scope.
- Triệu chứng: trang lỗi HTML thay vì JSON. Nguyên nhân: không có hạ tầng lỗi của Boot (ứng dụng Spring thuần hay filter chặn). Sửa: kiểm tra có Boot và
/error. - Triệu chứng: test không tìm thấy thư viện sau nâng. Nguyên nhân: starter kiểm thử đổi cấu trúc. Sửa: thêm starter kiểm thử tương ứng.
- Triệu chứng: Servlet 6.1 không tương thích với máy chủ cũ. Nguyên nhân: Boot 4 cần Servlet 6.1 trở lên. Sửa: dùng Tomcat 11 hoặc nâng container.
Hiệu năng, bảo mật và vận hành
- Nâng phiên bản chính là việc có kế hoạch: chạy toàn bộ test, đọc Migration Guide, nâng từng bước (3.5 trước rồi 4.0).
- Jackson 3 đổi tên package và một số mặc định; kiểm tra tuần tự hóa của API công khai bằng test hợp đồng.
- Cố định phiên bản Boot và ghi lại ghi đè BOM để lần nâng sau không bất ngờ.
- Không tự dựng hạ tầng như lab trừ khi có lý do rất cụ thể; mỗi phần tự dựng là một phần bạn phải bảo trì và vá lỗi bảo mật.
- Theo dõi lộ trình hỗ trợ của Boot (bản 4.x kế tiếp) để lên kế hoạch nâng cấp.
Bài thực hành
- Chạy ứng dụng Spring thuần như trên và ghi: JSON của
/api/users/7, phản hồi của đường dẫn không tồn tại, của/actuator/health. - Dựng cùng controller trong một dự án Boot và so sánh ba phản hồi đó.
- Đo thời gian khởi động của cả hai (chạy ba lần, lấy lần sau).
- Kiểm tra
spring-boot-starter-undertowvàspring-boot-starter-jettytrên kho Maven cho phiên bản Boot bạn dùng. - Tìm hai lớp
MockBeanvàSpyBeantrongspring-boot-testvàMockitoBeantrongspring-test. - Liệt kê các dependency kiểm thử mà Initializr sinh cho dự án Boot 4 và so với dự án Boot 3 cũ của bạn.
Tiêu chí hoàn thành: bước 1 cho JSON {"id":7,"name":"An"}, HTML 404 và HTML 404; bước 2 cho JSON, JSON 404 dạng Boot và UP; bước 4 cho Undertow 404 và Jetty 200; bước 5 cho MockBean và SpyBean không tồn tại. Gợi ý nếu bước 3 cho chênh lệch lớn: loại Actuator khỏi dự án Boot để so cùng phạm vi.
Tự kiểm tra
- Nếu dependency
spring-boot-starter-webmvcbiến mất khỏi classpath, thành phần nào của hình sẽ không còn được tạo tự động? - Vì sao vẫn cần đọc tài liệu Spring Framework khi dùng Boot?
- Dự án của bạn đang dùng
@MockBean. Bản Boot nào cần đổi sang@MockitoBean? - Ứng dụng Spring thuần của lab thiếu những gì mà Boot cho sẵn?
- Undertow có dùng được với Boot 4.1.1 không?
- JSON mặc định của Boot 4 là thư viện nào, package nào?
- Boot có làm ứng dụng nhanh hơn lúc xử lý request không?
- Vì sao nên nâng từng bước từ 3.x lên 4.x?
Gợi ý đáp án. (1) Tomcat nhúng, DispatcherServlet và Jackson. (2) Bean, proxy, MVC và transaction là khái niệm của Spring Framework. (3) Boot 4. (4) Đọc cấu hình ngoài, phản hồi lỗi JSON, Actuator, profile, tham số dòng lệnh, tắt êm có cấu hình, báo cáo lỗi khởi động. (5) Không (không có starter ở 4.1.1). (6) Jackson 3, package tools.jackson. (7) Không; nó rút ngắn thời gian dựng và cấu hình. (8) Giảm số thay đổi cùng lúc, bắt lỗi sớm bằng test và dùng Migration Guide cho từng chặng.
Điều cần nhớ
- Spring Framework là mô hình lập trình; Spring Boot là lớp lắp ráp trên nó, không thay thế.
- Boot nhận bốn việc: đăng ký servlet, chọn bộ chuyển đổi, máy chủ nhúng, quản lý phiên bản; cộng cấu hình ngoài, lỗi, vận hành.
- Boot 4:
webmvcthayweb, Tomcat 11, Jackson 3,@MockitoBean, không còn Undertow. - Vẫn phải học Spring Framework.
- Nâng cấp có kế hoạch và đọc Migration Guide.
Học liệu chọn lọc
- Spring Boot 4.0 Migration Guide
- Spring Boot Reference: System Requirements
- Spring Framework Reference: Overview
- Spring Framework Reference: Bean Overriding in Tests (@MockitoBean)
- Spring Framework Reference: Web MVC
- Jakarta Servlet Specification
- Apache Tomcat 11 Documentation
- Sách: Spring in Action (Craig Walls).