Spring Boot giải quyết gì — opinionated defaults và 5 trụ cột
Spring Boot không phải framework mới mà là 5 lớp đóng gói trên Spring Framework: auto-config, starter, embedded server, Actuator, Spring Boot CLI/build plugin. Bài này bóc bài toán Boot ra đời để giải quyết, đối chiếu code Spring 4 vs Boot 3, vòng đời SpringApplication.run(), và sự khác biệt giữa 'opinionated' và 'lock-in'.
Module 01 đã giới thiệu Spring Boot ở section "5 lớp cốt lõi" — đủ để bạn nhìn map. Module 02 này bóc từng lớp ở mức bytecode. Bài đầu tiên trả lời câu hỏi căn bản: Spring Boot tồn tại để làm gì khi đã có Spring Framework?
Câu trả lời ngắn: xoá boilerplate setup. Câu trả lời đầy đủ là: Spring Boot đóng gói 5 thứ để mọi project mới start trong 60 giây thay vì 60 phút. Sau bài này, bạn không bao giờ nhìn @SpringBootApplication như "magic" nữa — bạn biết chính xác nó kích hoạt cái gì.
1. Bài toán Spring 4 (2014) — vì sao Boot ra đời
Đầu 2014, mỗi project Spring 4 mới phải làm 5 thao tác y hệt:
1.1 Khai báo dependency
<!-- pom.xml — hang chuc dong -->
<properties>
<spring.version>4.0.0</spring.version>
<hibernate.version>4.3.0</hibernate.version>
<jackson.version>2.3.0</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 15 dep khac -->
</dependencies>
Pain: version mismatch — spring-web 4.0 không tương thích spring-context 4.0.5. Mỗi lần upgrade phải kiểm tra compatibility matrix thủ công.
1.2 Khai báo web.xml
<!-- WEB-INF/web.xml -->
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:web-context.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Pain: XML verbose, dễ typo, IDE không validate runtime behavior.
1.3 Cấu hình applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans">
<context:component-scan base-package="com.olhub"/>
<mvc:annotation-driven/>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:postgresql://localhost:5432/app"/>
<property name="username" value="app"/>
<property name="password" value="secret"/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.olhub.domain"/>
<!-- 20 dong properties JPA -->
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
Pain: 30-100 dòng XML cho mỗi config aspect. Mọi dev phải copy từ project trước.
1.4 Build và deploy WAR
mvn package
# Tao file app.war
# Cai Tomcat 8 lieng (download tu apache.org)
# Copy app.war vao $TOMCAT_HOME/webapps/
# Start Tomcat
$TOMCAT_HOME/bin/startup.sh
Pain: Tomcat cài tách rời, version skew giữa dev local và prod, không reproducible.
1.5 Logging + DataSource + JPA properties
log4j.xml, persistence.xml, application.properties, ... mỗi file 20-50 dòng cấu hình mặc định.
Tổng cộng: 60-90 phút setup cho project Spring 4 mới, hoàn toàn boilerplate. Pain này lặp ở mọi project — Pivotal team gọi đó là "snowflake projects" (mỗi project là 1 bông tuyết khác nhau, không reproducible).
2. Spring Boot 1.0 (4/2014) — 1 ý tưởng đơn giản
Phil Webb và Dave Syer ở Pivotal đề xuất ý tưởng: gom 5 thao tác trên thành defaults thông minh, dev chỉ cần override khi cần khác default.
// Spring Boot 1.0 — toan bo 12 dong:
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
@RestController
class HelloController {
@GetMapping("/")
String hello() { return "Hello"; }
}
<!-- pom.xml — 6 dong -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
mvn spring-boot:run # Tomcat embedded auto start, port 8080
18 dòng tổng, không XML, không Tomcat external. Đây là sự thay đổi paradigm.
3. 5 trụ cột của Spring Boot
Spring Boot đóng gói 5 thứ. Bạn cần hiểu rõ vì mỗi thứ giải quyết 1 trong 5 pain ở section 1:
flowchart TB
SB["Spring Boot"]
P1["1. Starter Dependencies<br/>(curated transitive deps)"]
P2["2. Auto-Configuration<br/>(@EnableAutoConfiguration)"]
P3["3. Embedded Server<br/>(Tomcat/Jetty/Undertow/Netty)"]
P4["4. Production-ready Features<br/>(Actuator, Externalized Config)"]
P5["5. Build Plugin + CLI<br/>(spring-boot-maven-plugin)"]
SB --> P1
SB --> P2
SB --> P3
SB --> P4
SB --> P5
style SB fill:#fef3c73.1 Starter Dependencies — giải quyết pain 1.1
Bài toán: version mismatch giữa các module Spring + 3rd-party lib.
Lời giải: Pivotal duy trì 1 BOM (Bill of Materials) — file spring-boot-dependencies định nghĩa version đã test tương thích cho 200+ thư viện phổ biến (Spring, Hibernate, Jackson, Tomcat, Hikari, Logback, Mockito, ...).
Bạn extends spring-boot-starter-parent → tự động lấy BOM. Khai báo dependency không version:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<!-- KHONG can <version> -->
</dependency>
Boot resolve version từ BOM. Upgrade Boot 3.4 → 3.5 = 1 dòng đổi parent version → tất cả 200 lib upgrade nhất quán.
Đây là giá trị lớn nhất của Boot — không phải auto-config. Bài 02 đào sâu starter mechanics.
3.2 Auto-Configuration — giải quyết pain 1.3
Bài toán: mỗi project copy 30-100 dòng XML config.
Lời giải: Boot có 143 auto-config class (Boot 3.4) — mỗi class tự register bean dựa trên classpath + property.
// File: org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java
@AutoConfiguration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource(DataSourceProperties properties) {
return DataSourceBuilder.create()
.driverClassName(properties.getDriverClassName())
.url(properties.getUrl())
.username(properties.getUsername())
.password(properties.getPassword())
.build();
}
}
Logic:
- Có
DataSourceclass trong classpath (HikariCP/JDBC) → register bean. - User chưa register
DataSourcebean của họ (@ConditionalOnMissingBean) → dùng default này. - User set
spring.datasource.url=...→ bind vàoDataSourceProperties.
Hệ quả: bạn chỉ set property, Boot tự setup DataSource bean. Override bằng cách tự register @Bean DataSource yourDs() {...} — Boot tự bỏ default.
Bài 03 đào sâu cơ chế auto-config.
3.3 Embedded Server — giải quyết pain 1.2 + 1.4
Bài toán: cài Tomcat external, web.xml verbose, deploy WAR.
Lời giải: Boot pull Tomcat (hoặc Jetty/Undertow/Netty) như library, đóng gói trong jar:
mvn package
# Tao app.jar — KHONG phai .war
java -jar target/app.jar
# Tomcat tu start trong jar, port 8080
SpringApplication.run() chứa logic:
- Detect classpath có
tomcat-embed-core→ tạoTomcatServletWebServerFactory. - Factory tạo
Tomcatinstance, bind port từserver.port(default 8080). - Register
DispatcherServletprogrammatically (thayweb.xml). - Start Tomcat sau bước 11 của
refresh()(Module 01 bài 03).
Lợi ích:
- 1 artifact: 1 file jar = mọi thứ cần. Deploy Docker, K8s, AWS Lambda đều dễ.
- Reproducible: Tomcat version khớp giữa dev local và prod — không skew.
- Nhanh local:
mvn spring-boot:runstart trong 3-5s, không phải start Tomcat external.
3.4 Production-ready Features — giải quyết pain 1.5
Bài toán: mọi project cần health check, metrics, log config — code lại từ đầu.
Lời giải: Boot có sẵn:
- Actuator: 15+ endpoint (
/actuator/health,/actuator/metrics,/actuator/info,/actuator/env, ...). Chỉ cần thêmspring-boot-starter-actuator. - Externalized Configuration: 14 nguồn property theo thứ tự ưu tiên (Module 01 bài 03).
- Logging: Logback default, với pattern Boot tinh chỉnh sẵn.
- Profiles:
application-\{profile\}.properties— toggle dev/staging/prod.
Bài 04-06 sẽ đào sâu config + profile + logging.
3.5 Build Plugin + CLI
spring-boot-maven-plugin + spring-boot-gradle-plugin đóng gói app thành executable jar (jar có Main-Class + dependency jar lồng nhau, gọi là "fat jar" hoặc "uber jar").
mvn spring-boot:run # Run trong dev, hot reload neu co devtools
mvn spring-boot:build-image # Build Docker image qua Buildpacks (no Dockerfile)
mvn spring-boot:repackage # Tao executable jar
Spring Boot CLI (spring command) — script Groovy chạy trực tiếp, ít dùng cho production. Phổ biến cho prototype/demo.
4. Vòng đời SpringApplication.run()
Khi bạn gọi SpringApplication.run(App.class, args), Boot làm 12 bước trước khi context refresh:
flowchart TB
A["1. Detect application type<br/>(SERVLET / REACTIVE / NONE)"]
B["2. Setup default property sources"]
C["3. Print banner"]
D["4. Load ApplicationContextInitializer<br/>tu spring.factories"]
E["5. Load ApplicationListener<br/>tu spring.factories"]
F["6. Run main class arguments parser"]
G["7. Configure Environment<br/>(profiles, command-line args)"]
H["8. Publish ApplicationStartingEvent"]
I["9. Create ApplicationContext<br/>(Servlet/Reactive/Generic)"]
J["10. Add ApplicationContextInitializers"]
K["11. Publish ApplicationContextInitializedEvent"]
L["12. Refresh context<br/>(12 buoc cua Spring core)"]
M["13. Start CommandLineRunner /<br/>ApplicationRunner"]
N["14. Publish ApplicationReadyEvent"]
A --> B --> C --> D --> E --> F --> G --> H --> I --> J --> K --> L --> M --> N
style L fill:#fef3c7
style N fill:#d1fae5Bước 12 (refresh context) chính là 12 bước của Spring core đã học ở Module 01 bài 03 — Boot wrap thêm 11 bước trước + 2 bước sau.
Output console khi start:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.0)
2026-04-15T10:00:00.123 INFO 12345 --- [main] com.olhub.App : Starting App using Java 21
2026-04-15T10:00:01.456 INFO 12345 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080
2026-04-15T10:00:02.789 INFO 12345 --- [main] com.olhub.App : Started App in 2.345 seconds
Mỗi dòng tương ứng 1 bước:
- "Starting App" →
ApplicationStartingEvent. - "Tomcat initialized" → bước 9 trong refresh (
onRefresh). - "Started App in 2.345 seconds" →
ApplicationReadyEvent.
5. "Opinionated" — không phải "lock-in"
Critic của Boot hay nói: "opinionated" nghe như framework ép buộc. Không phải. Spring Boot có 3 nguyên tắc design:
| Nguyên tắc | Ý nghĩa |
|---|---|
| Convention over Configuration | Có default thông minh, override chỉ khi cần |
| No code generation | Không sinh source code (khác Yeoman, JHipster) — chỉ register bean runtime |
| No XML config required | Mọi config qua properties hoặc Java |
Hệ quả: bạn override bất kỳ default nào bằng cách đăng ký bean của bạn. Boot tự rút lui (@ConditionalOnMissingBean).
Ví dụ override default:
@Configuration
public class CustomConfig {
// Override DataSource default cua Boot
@Bean
public DataSource dataSource() {
var ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:postgresql://custom-host/db");
ds.setMaximumPoolSize(50); // override default 10
// custom hop ly
return ds;
}
// Override embedded server: doi Tomcat sang Jetty
// qua dependency exclude + add jetty starter
}
// Override property qua application.yml
// server.port: 9090
// spring.datasource.url: ...
So sánh với "lock-in":
| Aspect | Boot opinionated | Lock-in (vd Quarkus IO/CDI strict) |
|---|---|---|
| Override default | Đăng ký bean của bạn | Không có cơ chế chính thức |
| Bỏ feature | Exclude autoconfig: @SpringBootApplication(exclude = ...) | Thường rebuild framework |
| Migrate ra | Code Spring vẫn chạy, chỉ bỏ Boot | Khó — bind vào framework API |
Boot đẹp ở chỗ: bạn dùng nó để bootstrap nhanh, nhưng vẫn có thể đào sâu tới Spring core khi cần. Không có "magic curtain" — mọi auto-config là Java code đọc được.
6. Spring Boot version timeline
Hiểu lịch sử version giúp đọc code legacy:
| Version | Năm | Cột mốc |
|---|---|---|
| 1.0 | 2014 | Initial release, Tomcat embedded |
| 1.5 | 2017 | Stabilized, Java 7+ |
| 2.0 | 2018 | Java 8 baseline, Spring 5, WebFlux support |
| 2.3 | 2020 | Layered jar, Buildpacks |
| 2.6 | 2021 | Tắt circular dep mặc định |
| 2.7 | 2022 | Last 2.x — Java 8 sunset |
| 3.0 | 2022 | Java 17 baseline, Spring 6, Jakarta EE 9, GraalVM native |
| 3.1 | 2023 | Docker Compose support, Testcontainers integration |
| 3.2 | 2023 | Virtual Threads (Java 21), RestClient, JdbcClient, CDS |
| 3.3 | 2024 | SBOM endpoint, structured logging preview |
| 3.4 | 2024 | Structured logging GA, @Fallback, AssertJ MockMvc |
| 3.5 | 2025 | SSL metrics, Quartz Actuator |
| 4.0 | 2025 | Spring 7, Java 17+, mới — adoption đang sớm |
Khoá này dùng 3.4.x: LTS-friendly, ecosystem rộng, structured logging GA. Boot 3.5 cũng OK nếu cần feature mới nhất.
6.1 Bảng compatibility matrix
| Spring Boot | Spring Framework | Java tối thiểu | Hibernate | Jackson | Tomcat |
|---|---|---|---|---|---|
| 3.4.x | 6.2.x | 17 | 6.6.x | 2.18.x | 10.1.x |
| 3.5.x | 6.2.x | 17 | 6.6.x | 2.18.x | 10.1.x |
| 4.0.x | 7.0.x | 17 | 7.0.x | 2.19.x | 11.0.x |
Spring Boot quản version 200 lib qua BOM — bạn không cần nhớ chi tiết, chỉ pick Boot version → cả ecosystem tự khớp.
7. So sánh với competitor
flowchart LR
SB["Spring Boot"]
Q["Quarkus"]
M["Micronaut"]
H["Helidon"]
SB ---|"market leader"| Default
Q ---|"native-first"| RH
M ---|"compile-time DI"| OCI
H ---|"Helidon SE/MP"| Oracle
Default["~70% Java backend market share"]
RH["Red Hat backed, native image fast"]
OCI["Oracle Cloud, no reflection"]Bảng so sánh:
| Aspect | Spring Boot 3.x | Quarkus 3.x | Micronaut 4.x |
|---|---|---|---|
| Startup (HelloWorld JVM) | ~1s | ~1s | ~700ms |
| Startup (native) | ~50ms | ~30ms | ~30ms |
| Memory (idle JVM) | ~150MB | ~120MB | ~80MB |
| Memory (native) | ~50MB | ~25MB | ~30MB |
| DI mechanism | Runtime (CGLIB/JDK proxy) | Build-time (annotation processor) | Build-time |
| Reflection | Yes (cần config cho native) | Minimal | Minimal |
| Ecosystem size | Largest | Medium | Medium |
| Learning curve | Moderate | Steeper | Steeper |
Boot dominate vì:
- Inertia: 22 năm tài liệu Spring, 11 năm Boot.
- Ecosystem: starter cho mọi 3rd-party lib phổ biến.
- Boot 3.2+ bắt kịp performance: Virtual Threads, CDS, native image.
- Pivotal/VMware/Broadcom đầu tư full-time team.
Khoá này tập trung Boot. Quarkus/Micronaut là alternative tốt khi cần native serverless, không phải replacement.
8. Pitfall tổng hợp
❌ Nhầm 1: Cho rằng Spring Boot là "framework mới" thay Spring Framework.
✅ Boot build trên Framework. Annotation @Service, @Autowired, @Transactional đều thuộc Framework. Boot chỉ thêm 5 trụ cột.
❌ Nhầm 2: Khai báo dependency với <version> explicit trong Boot project.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0</version> <!-- override BOM, gay version mismatch -->
</dependency>
✅ Bỏ <version> — để BOM resolve. Override chỉ khi có lý do specific.
❌ Nhầm 3: Không dùng spring-boot-starter-parent nhưng vẫn muốn Boot quản version.
✅ Import BOM thủ công:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
❌ Nhầm 4: Tạo file web.xml trong Boot project.
✅ Boot config DispatcherServlet programmatically — web.xml không cần và sẽ bị conflict.
❌ Nhầm 5: Deploy Boot app vào Tomcat external (như WAR truyền thống).
✅ Boot designed cho executable jar (java -jar). Deploy WAR vào Tomcat external được nhưng mất lợi ích reproducibility — chỉ làm khi enterprise constraint bắt buộc.
❌ Nhầm 6: Override default Boot bằng cách sửa Boot source code.
✅ Đăng ký bean của bạn — Boot tự rút lui qua @ConditionalOnMissingBean. Không bao giờ fork Boot.
❌ Nhầm 7: Tin "opinionated" = "framework ép buộc". ✅ Opinionated = có default thông minh nhưng cho phép override. Hoàn toàn khác lock-in. Đọc autoconfig source code 1 lần để verify.
9. 📚 Deep Dive Spring Reference
Spring Boot Reference docs:
- Spring Boot Reference — Introducing Spring Boot — overview chính thức.
- Spring Boot Reference — System Requirements — Java/build tool/server compatibility.
- Spring Boot Reference — First Application — tutorial chính chủ tạo app đầu tiên.
- Spring Boot Reference — Build Systems — Maven/Gradle integration,
spring-boot-starter-parentvs BOM import. - Spring Boot Reference — SpringApplication — chi tiết vòng đời
SpringApplication.run().
Compatibility:
- Spring Boot Compatibility Matrix — version mapping + support timeline.
- Spring Boot Wiki — Release Notes — changelog từng version.
Source code chính chủ để bookmark:
SpringApplication.run()— entry point.spring-boot-dependencies— BOM định nghĩa version 200+ lib. Mở 1 lần để hiểu scope.AutoConfiguration.imports— file liệt kê 143 autoconfig class. Đọc cuối Module 02.
Bài viết & talk kinh điển:
- Phil Webb — The History of Spring Boot — release blog 1.0.
- Dave Syer — Spring Boot Memory Performance — phân tích memory footprint.
Ghi chú: đọc reference Boot không tuyến tính. Mỗi khi gặp annotation @Conditional* lạ, tra reference. Mỗi khi property không có effect, tra "Common Application Properties" appendix. Pattern dùng docs hiệu quả.
10. Tóm tắt
- Spring Boot không phải framework mới — là 5 lớp đóng gói trên Spring Framework.
- 5 trụ cột: Starter Dependencies (BOM curate version), Auto-Configuration (143 autoconfig class register bean conditional), Embedded Server (Tomcat/Jetty/Netty trong jar), Production-ready Features (Actuator + externalized config), Build Plugin (executable fat jar).
- Boot ra đời 2014 để xoá 60-90 phút boilerplate setup mỗi project Spring 4.
- "Opinionated" ≠ "lock-in" — có default nhưng cho phép override qua bean của user.
@ConditionalOnMissingBeanlà cơ chế cốt lõi. SpringApplication.run()có 14 bước, bước 12 làrefresh()của Spring core (12 bước Module 01 bài 03).- Output banner + 3 dòng log chuẩn: "Starting App" → "Tomcat initialized" → "Started App in N seconds".
- BOM
spring-boot-dependenciesquản version 200+ lib. Khai báo dep không version — Boot resolve từ BOM. Upgrade Boot = upgrade ecosystem nhất quán. - Khoá này dùng Boot 3.4.x + Java 21. Boot 3.5/4.0 cũng OK nhưng Boot 3.4 mature nhất hiện tại.
- Boot dominate ~70% Java backend nhờ inertia + ecosystem rộng + perf bắt kịp competitor (Boot 3.2+ Virtual Threads + native image).
11. Tự kiểm tra
Q1Một developer mới nói: "Spring Boot là Spring với Tomcat embedded — tháo Tomcat ra thì không còn lý do dùng Boot." Lý luận này sai ở đâu?▸
Sai ở chỗ chỉ thấy 1 trong 5 trụ cột. Tomcat embedded chỉ là 1 phần (Embedded Server). Bỏ Tomcat đi (vd CLI app, batch job), Boot vẫn cung cấp 4 trụ cột còn lại:
- Starter Dependencies + BOM: vẫn cần để quản version 200 lib nhất quán.
- Auto-Configuration: vẫn register bean conditional cho DataSource, Cache, Kafka, ... dù không có web.
- Externalized Configuration: 14 nguồn property, profile, relax binding — không phụ thuộc web.
- Build Plugin: executable fat jar cho CLI app cũng dùng
spring-boot:repackage.
Bằng chứng: spring-boot-starter (không có web) đủ tạo CLI app Boot full feature. Module 02 bài 02 sẽ bóc starter này.
Q2Bạn upgrade project Boot 3.2 lên 3.4. Khi build Maven, lỗi Could not find spring-context:6.0.13. Vì sao? Cách fix?▸
Could not find spring-context:6.0.13. Vì sao? Cách fix?Vì sao: project có dependency spring-context với <version>6.0.13</version> hardcoded. BOM của Boot 3.4 set spring-context version 6.2.x — Maven respect version explicit user khai báo, nhưng Maven repo có thể không có 6.0.13 stale → fail.
Cách fix:
- Tìm dependency có
<version>explicit:mvn dependency:tree | grep "spring-context" - Bỏ
<version>để Boot BOM resolve:<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <!-- KHONG <version> --> </dependency> - Verify version mới:
mvn dependency:tree | grep "spring-context"→ phải thấy6.2.x.
Bài học: trong Boot project, không bao giờ set <version> cho dep Spring/Spring Boot/transitive lib. BOM lo cho bạn. Override version chỉ khi: (a) cần feature từ version mới hơn BOM, (b) workaround security vuln chưa có trong BOM stable.
Q3Đoạn sau có 3 vấn đề. Liệt kê.@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Bean
public DataSource dataSource() {
var ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:postgresql://localhost:5432/app");
ds.setUsername("postgres");
ds.setPassword("admin123");
ds.setMaximumPoolSize(20);
return ds;
}
}
▸
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Bean
public DataSource dataSource() {
var ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:postgresql://localhost:5432/app");
ds.setUsername("postgres");
ds.setPassword("admin123");
ds.setMaximumPoolSize(20);
return ds;
}
}- Hardcode credential:
password="admin123"nhúng vào source. Lộ git → security incident. Phải dùngapplication.properties+ env var:Boot autoconfigspring.datasource.url=${DB_URL} spring.datasource.username=${DB_USER} spring.datasource.password=${DB_PASS}DataSourcetự đọc property này → bỏ hoàn toàn@Bean DataSourcetự khai. - Override default Boot không có lý do: Boot autoconfig
DataSourceAutoConfigurationđã handle 99% case. Khai báo@Bean DataSource= override → mất lợi ích autoconfig (vd connection pool metrics, health check). - Đặt
@Bean DataSourcetrong cùng class vớimain: trộn application entry point với infrastructure config. Khi config DB phức tạp (read replica, multi-tenant), class này phình lên. TáchDataSourceConfig.javariêng cho clean structure.
Code đúng:
// App.java — chi entry point
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
// application.yml — config
spring:
datasource:
url: ${DB_URL}
username: ${DB_USER}
password: ${DB_PASS}
hikari:
maximum-pool-size: 20Boot autoconfig đọc property → tạo HikariDataSource. Pool size config qua spring.datasource.hikari.* — không cần code.
Q4"Opinionated" của Spring Boot khác gì "lock-in"? Cho 3 ví dụ cụ thể về khả năng override default.▸
Khác nhau cốt lõi: opinionated cung cấp default + cơ chế override formal. Lock-in không có cơ chế override (hoặc override = fork framework).
3 ví dụ override Boot default:
- Override DataSource:Boot autoconfig
@Bean public DataSource dataSource() { return new HikariDataSource(/* custom config */); }DataSourceAutoConfigurationcó@ConditionalOnMissingBean(DataSource.class)→ tự rút lui khi user register bean. - Đổi embedded server từ Tomcat sang Jetty:Boot autoconfig detect Jetty thay Tomcat → tạo
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>JettyServletWebServerFactory. - Tắt 1 autoconfig cụ thể:Spring Boot không register DataSource bean — bạn quản hoàn toàn.
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class App { ... }
Cả 3 cách trên là cơ chế chính thức của Boot, không hack. Đây là khác biệt với lock-in framework — Quarkus có cơ chế tương tự nhưng narrower; framework lock-in (vd EJB 2.x) không có concept "override default".
Q5Bạn muốn Boot app warm cache 1MB JSON từ S3 sau khi startup. 4 cách: (a) @PostConstruct trên service, (b) CommandLineRunner bean, (c) @EventListener(ContextRefreshedEvent), (d) @EventListener(ApplicationReadyEvent). Nên chọn cái nào, vì sao?▸
@PostConstruct trên service, (b) CommandLineRunner bean, (c) @EventListener(ContextRefreshedEvent), (d) @EventListener(ApplicationReadyEvent). Nên chọn cái nào, vì sao?(d) ApplicationReadyEvent là chuẩn cho "post-startup logic".
Phân tích từng cách:
- (a)
@PostConstruct: chạy ở bước 5 lifecycle bean (Module 01 bài 04) — trước khi context refresh xong, trước server bind port. Nếu S3 chậm/down, app không bao giờ ready → liveness probe fail → K8s restart loop. Quá sớm. - (b)
CommandLineRunner: chạy trướcApplicationReadyEvent(bước 13 vs 14 trong vòng đờiSpringApplication.run()). Server đã bind port nhưng readiness probe có thể chưa "UP". Hợp khi muốn warm trước khi expose health check. - (c)
ContextRefreshedEvent: publish ở bước 12 củarefresh(), trước Boot start server (bước 9 onRefresh chạy ngay sau). Quá sớm. Lưu ý: trong test với@DirtiesContext, event này có thể fire nhiều lần — không idempotent. - (d)
ApplicationReadyEvent: publish cuối cùng sau mọi runner. App fully alive — server listen, bean ready, runner xong. Idempotent — fire 1 lần / app instance. Đây là điểm chuẩn cho post-startup logic.
Code mẫu:
@Component
public class CacheWarmer {
private final CacheService cache;
private final S3Client s3;
public CacheWarmer(CacheService cache, S3Client s3) {
this.cache = cache;
this.s3 = s3;
}
@EventListener(ApplicationReadyEvent.class)
public void warm() {
try {
String json = s3.getObject("config-bucket", "warmup.json").asUtf8String();
cache.preload(json);
log.info("Cache warmed from S3");
} catch (Exception e) {
log.error("Cache warm failed, app continues with cold cache", e);
// Khong rethrow — app van live, chap nhan cold start
}
}
}Lý do quan trọng nhất chọn (d): nếu S3 chậm, log warning + tiếp tục. App vẫn "ready" cho user. Cache warm là tối ưu, không phải critical → không nên block startup.
Q6Spring Boot 3.4 baseline Java 17, nhưng khoá này khuyến nghị Java 21. Lợi ích Java 21 cho Boot app cụ thể là gì? Có downside không?▸
Lợi ích Java 21 cho Boot 3.4:
- Virtual Threads (JEP 444): bật
spring.threads.virtual.enabled=true→ mọi servlet request chạy trên virtual thread thay platform thread. App I/O-bound (gọi DB, REST client) scale từ ~200 concurrent request → ~10,000+ concurrent với cùng heap. Đây là feature lớn nhất. - Pattern matching for switch (JEP 441): code service layer clean hơn:
return switch (event) { case OrderPlaced o -> processOrder(o); case OrderCancelled c -> refund(c); case null -> defaultHandle(); default -> throw new IllegalStateException(); }; - Record patterns (JEP 440): destructure record trong
switch,instanceof. - Sequenced Collections (JEP 431): API
SequencedCollection.getFirst(),getLast()uniform cho List/Deque/LinkedHashSet. - ZGC Generational (JEP 439): GC pause <10ms ngay với heap 100GB+ — lợi ích cho app low-latency.
- String Templates (preview): chưa GA, nhưng đáng theo dõi.
Downside:
- Library compatibility: 1 vài lib cũ chưa support Java 21. 95% lib mainstream đã OK 2026, nhưng project legacy có thể gặp NoClassDefFoundError.
- Container image size: JDK 21 image ~200MB vs JDK 17 ~190MB (Eclipse Temurin distroless). Negligible nhưng đo được.
- Tooling delay: 1 số IDE plugin, static analyzer chậm support Java 21 features (vd record patterns). Hết 2026 đã ổn.
Quy tắc thực tế 2026: project mới → Java 21 default. Project legacy migrate Boot 2.7 → 3.4 → giữ Java 17 trước (giảm rủi ro), upgrade Java 21 sau khi stable Boot 3.4.
Q7Console log của 1 Boot app có 4 dòng quan trọng: Starting App using Java 21, Tomcat initialized with port 8080, Started App in 2.345 seconds, Application is now ready. Mỗi dòng tương ứng bước nào trong vòng đời SpringApplication.run()? Ý nghĩa cho debug startup chậm?▸
Starting App using Java 21, Tomcat initialized with port 8080, Started App in 2.345 seconds, Application is now ready. Mỗi dòng tương ứng bước nào trong vòng đời SpringApplication.run()? Ý nghĩa cho debug startup chậm?| Dòng log | Bước trong run() | Ý nghĩa |
|---|---|---|
Starting App using Java 21 | Bước 8 — publish ApplicationStartingEvent | App bắt đầu — chưa load bean. Thời gian từ JVM start đến đây = JVM warm-up + Boot infrastructure setup (banner, environment, ...). |
Tomcat initialized with port 8080 | Bước 12 → bước 9 trong refresh (onRefresh) | Tomcat đã bind port. Trước dòng này là tất cả bean autoconfig + bean app instantiate. |
Started App in 2.345 seconds | Bước 12 cuối — finishRefresh publish ContextRefreshedEvent | Bean ready, server listen. Number "2.345s" là từ JVM start đến thời điểm này. |
Application is now ready | Bước 14 — publish ApplicationReadyEvent | Mọi runner xong. Đây là điểm "live" thực sự cho health check. |
Debug startup chậm — đo từng đoạn:
- JVM start → Starting App: dài bất thường → CDS chưa warm, JIT cold. Fix: dùng CDS (Boot 3.3+
java -Djarmode=tools -jar app.jar extract), hoặc Project Leyden khi GA. - Starting App → Tomcat initialized: dài → bean instantiate chậm. Bật
logging.level.org.springframework=DEBUGhoặc Actuator/startupendpoint để thấy bean nào tốn thời gian. Thường là DataSource (Hibernate validate schema), Kafka consumer (network connect), Redis pool init. - Tomcat initialized → Started App: ngắn (vài trăm ms) — chỉ publish event + post-init.
- Started App → Application ready: dài →
CommandLineRunner/ApplicationRunnerchậm. Audit code các runner.
Tool đo chuẩn 2026: Boot 3 có Actuator endpoint /actuator/startup — list mọi bean với thời gian instantiate. Dùng cho production diagnostics.
Bài tiếp theo: Starter dependencies — bóc tách spring-boot-starter-web
Bài này có giúp bạn hiểu bản chất không?
Bình luận (0)
Đang tải...