Spring Core & Boot/Spring Boot là gì — tại sao ra đời và 5 trụ cột
27/41
Bài 27 / 41~12 phútSpring Boot & Auto-configurationMiễn phí lượt xem

Spring Boot là gì — tại sao ra đời 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 để xoá 60-90 phút boilerplate setup mỗi project. Bài này giải thích bài toán Spring 4, 5 trụ cột Boot, vòng đời SpringApplication.run() kết thúc bằng refresh(), và tại sao 'opinionated' không phải 'lock-in'.

TL;DR: Spring Boot không phải framework mới — là 5 lớp đóng gói trên Spring Framework ra đời 2014 để xoá 60-90 phút boilerplate setup mỗi project Spring 4 (web.xml, applicationContext.xml, Tomcat external, log4j.xml). 5 trụ cột: Starter Dependencies (BOM curate 200+ lib version), Auto-Configuration (143 autoconfig class register bean conditional), Embedded Server (Tomcat trong jar), Production-ready Features (Actuator + externalized config), Build Plugin (executable fat jar). SpringApplication.run() làm 14 bước, bước 12 là refresh() của Spring core — phần còn lại là Boot wrap thêm. "Opinionated" không phải lock-in: mọi default đều override được qua @ConditionalOnMissingBean.

Bài này đặt nền cho cả module: trả lời vì sao Boot tồn tại trước khi bài 02-06 bóc từng trụ cột. Sau bài này, bạn không nhìn @SpringBootApplication như "magic" nữa — bạn biết nó kích hoạt đúng 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:

Khai báo dependency thủ công — 15+ module Spring với version riêng, mỗi lần upgrade phải kiểm tra compatibility matrix tay:

<!-- pom.xml Spring 4 -- 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-webmvc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!-- 13 dependency nua, moi dependency mot version -->
</dependencies>

Pain cụ thể: spring-web 4.0.0spring-context 4.0.5 không tương thích — developer phải biết điều này theo cách thủ công.

Viết web.xml — khai báo ContextLoaderListener, DispatcherServlet, servlet mapping, 30-50 dòng XML dễ typo, IDE không validate runtime behavior.

Viết applicationContext.xml — DataSource, EntityManagerFactory, TransactionManager, component-scan, mỗi project copy từ project trước rồi chỉnh tay.

Cài Tomcat external — download từ apache.org, copy WAR vào webapps/, startup.sh. Version Tomcat trên dev local và prod server dễ skew.

Tạo log4j.xml, persistence.xml, ... — mỗi file 20-50 dòng config mặc định, lặp ở mọi project.

Tổng cộng: 60-90 phút setup hoàn toàn boilerplate cho project Spring 4 mới. Pivotal gọi đây là "snowflake projects" — mỗi project là một bông tuyết khác nhau, không reproducible.

2. Spring Boot 1.0 (tháng 4/2014) — một ý tưởng đơn giản

Phil Webb và Dave Syer ở Pivotal đề xuất: gom 5 thao tác trên thành defaults thông minh, developer chỉ override khi cần khác default.

Cùng app web với Spring Boot 1.0:

// Toan bo Java code -- 7 dong
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}
<!-- 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>
        <!-- KHONG co <version> -->
    </dependency>
</dependencies>
mvn spring-boot:run    # Tomcat embedded tu start, port 8080

13 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ứ. Mỗi trụ cột giải quyết đúng 1 pain ở section 1:

flowchart TB
  SB["Spring Boot"]
  P1["1. Starter Dependencies<br/>(BOM curate 200+ lib version)"]
  P2["2. Auto-Configuration<br/>(143 autoconfig class, @ConditionalOn*)"]
  P3["3. Embedded Server<br/>(Tomcat / Jetty / Undertow / Netty)"]
  P4["4. Production-ready Features<br/>(Actuator, Externalized Config)"]
  P5["5. Build Plugin<br/>(executable fat jar)"]
  SB --> P1
  SB --> P2
  SB --> P3
  SB --> P4
  SB --> P5

3.1 Starter Dependencies — giải quyết pain version mismatch

Bài toán: 15+ module Spring với version riêng, upgrade = kiểm tra compatibility matrix tay.

Lời giải: Pivotal duy trì 1 BOM (Bill of Materials — tài liệu liệt kê version đã kiểm tra tương thích) cho 200+ thư viện phổ biến: Spring, Hibernate, Jackson, Tomcat, HikariCP, Logback, Mockito, ...

Bạn khai báo spring-boot-starter-parent → kế thừa BOM → khai báo dependency không cần <version>:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <!-- Boot BOM resolve version -- khong can khai bao -->
</dependency>

Upgrade Boot 3.4 lên 3.5 = đổi 1 dòng parent version → tất cả 200 lib upgrade nhất quán. Bài 02 — Starter & BOM bóc sâu cơ chế.

3.2 Auto-Configuration — giải quyết pain applicationContext.xml

Bài toán: copy 30-100 dòng XML config cho mỗi project.

Lời giải: Boot có 143 auto-config class (Boot 3.4). Mỗi class tự register bean có điều kiện dựa trên classpath + property:

// DataSourceAutoConfiguration.java (rut gon)
@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DataSourceProperties props) {
        return DataSourceBuilder.create()
            .url(props.getUrl())
            .username(props.getUsername())
            .password(props.getPassword())
            .build();
    }
}

Logic: có DataSource.class trên classpath AND user chưa tự register DataSource bean (@ConditionalOnMissingBean) → Boot register bean mặc định. Bạn chỉ set property:

spring.datasource.url=jdbc:postgresql://localhost:5432/app
spring.datasource.username=app
spring.datasource.password=secret

Override bằng cách tự khai báo @Bean DataSource — Boot tự rút lui. Bài 03 — Auto-Configuration deep dive mổ cơ chế @ConditionalOn*.

3.3 Embedded Server — giải quyết pain Tomcat external

Bài toán: cài Tomcat tách rời, deploy WAR, version skew giữa dev và prod.

Lời giải: Boot pull Tomcat (hoặc Jetty/Undertow/Netty) như library trong jar:

mvn package
# Tao target/app.jar -- KHONG phai .war

java -jar target/app.jar
# Tomcat tu start, port 8080

SpringApplication.run() detect tomcat-embed-core trên classpath → tạo TomcatServletWebServerFactory → khởi động Tomcat → register DispatcherServlet programmatically (thay web.xml). Chi tiết bước này nằm trong vòng đời ở section 4.

Lợi ích: 1 artifact duy nhất (1 jar = mọi thứ cần), reproducible (Tomcat version khớp giữa dev và prod), deploy anywhere (Docker, K8s, Lambda).

3.4 Production-ready Features — giải quyết pain config lặp

Bài toán: mọi project cần health check, metrics, log config — viết lại từ đầu.

Lời giải:

  • Actuator: 15+ endpoint sẵn có (/actuator/health, /actuator/metrics, /actuator/startup). Thêm 1 starter là có ngay.
  • Externalized Configuration: 14 nguồn property theo thứ tự ưu tiên — env var, command-line arg, application.yml, profile, v.v.
  • Logging: Logback default với pattern Boot tinh chỉnh sẵn.
  • Profiles: application-{profile}.yml toggle cấu hình dev/staging/prod.

Bài 04 — Externalized Configuration và bài 05 — Profiles đào sâu.

3.5 Build Plugin — giải quyết pain deploy artifact

spring-boot-maven-plugin đóng gói app thành executable fat jar — jar chứa Main-Class manifest và toàn bộ dependency jar lồng nhau:

mvn spring-boot:run         # Dev mode, hot reload voi devtools
mvn spring-boot:build-image # Build Docker image qua Buildpacks, khong can Dockerfile
mvn package                 # Tao executable jar

Executable jar: java -jar app.jar tự khởi động Tomcat + app, không cần Tomcat external.

4. SpringApplication.run() làm gì — kết thúc bằng refresh()

Đây là cơ chế cốt lõi: khi bạn gọi SpringApplication.run(App.class, args), Boot thực hiện 14 bước:

flowchart TB
  A["1. Detect app type<br/>(SERVLET / REACTIVE / NONE)"]
  B["2-7. Setup Environment<br/>(property sources, profiles, command-line)"]
  C["8. Publish ApplicationStartingEvent"]
  D["9. Create ApplicationContext<br/>(Servlet / Reactive / Generic)"]
  E["10-11. Apply ApplicationContextInitializer"]
  F["12. refresh()<br/>(12 buoc Spring core)"]
  G["13. Run CommandLineRunner / ApplicationRunner"]
  H["14. Publish ApplicationReadyEvent"]
  A --> B --> C --> D --> E --> F --> G --> H
  style F fill:#fef3c7
  style H fill:#d1fae5

Bước 12 (refresh()) chính là 12 bước của Spring core mà bạn đã học ở bài 02 — refresh() lifecycle: scan component, load BeanDefinition, instantiate singleton, gọi @PostConstruct, publish ContextRefreshedEvent. Boot wrap thêm 11 bước trước (detect type, setup environment, tạo context, ...) và 2 bước sau (CommandLineRunner, ApplicationReadyEvent).

Hiểu điều này giúp debug startup: nếu app chậm ở "Tomcat initialized with port 8080" → bước 12 (refresh()) chậm → bean nào đó khởi tạo lâu (DataSource connect, Hibernate validate schema). Nếu chậm sau "Started App" → CommandLineRunner chậm.

Log console tương ứng từng bước:

2026-04-15 10:00:00 INFO  com.olhub.App : Starting App using Java 21
# ^ Buoc 8 -- ApplicationStartingEvent

2026-04-15 10:00:01 INFO  o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080
# ^ Ben trong buoc 12 (refresh -> onRefresh -> TomcatWebServer.start())

2026-04-15 10:00:02 INFO  com.olhub.App : Started App in 2.345 seconds (process running for 2.8)
# ^ Ket thuc buoc 12

2026-04-15 10:00:02 INFO  com.olhub.App : Application is ready
# ^ Buoc 14 -- ApplicationReadyEvent

Annotation @SpringBootApplication là composite của 3 annotation:

@SpringBootConfiguration   // = @Configuration: class nay la bean config
@EnableAutoConfiguration   // bat 143 autoconfig class
@ComponentScan             // scan @Component/@Service/@Repository trong package hien tai
public @interface SpringBootApplication { ... }

Và annotation được khai báo từ Resource & @SpringBootApplication — ba annotation đó kích hoạt đúng luồng refresh() ở trên.

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 vậy. Spring Boot có 3 nguyên tắc thiết kế:

Nguyên tắcÝ nghĩa
Convention over ConfigurationCó default thông minh; override chỉ khi cần khác
No code generationKhông sinh source code — chỉ register bean runtime
No XML requiredMọi config qua properties hoặc Java

Vì sao chọn "opinionated"? Pivotal nhận ra 80% project dùng cùng stack (Tomcat + HikariCP + Hibernate + Logback). Viết default cho 80% đó, để developer tập trung vào 20% business logic khác biệt. Nếu default không phù hợp, override nó — cơ chế chính thức là @ConditionalOnMissingBean: Boot tự rút lui khi bạn đăng ký bean cùng kiểu.

Opinionated khác lock-in như thế nào:

Boot opinionatedLock-in
Override defaultĐăng ký bean của bạn — Boot tự rútKhông có cơ chế override chính thức
Bỏ feature@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)Thường phải rebuild/fork framework
Migrate ra khỏi BootCode Spring core vẫn chạy, chỉ bỏ Boot dependencyKhó — bị bind vào API độc quyền
// Overide DataSource default -- Boot tu rut lui
@Configuration
public class CustomDataSourceConfig {

    @Bean                        // user dang ky DataSource
    public DataSource dataSource() {
        var ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:postgresql://custom-host/db");
        ds.setMaximumPoolSize(50);
        return ds;
    }
    // DataSourceAutoConfiguration thay @ConditionalOnMissingBean gap DataSource ban vua khai bao
    // -> tu skip, khong register bean mac dinh
}
// Tat han 1 autoconfig
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class App { ... }

Boot đẹp ở chỗ: bạn dùng nó để bootstrap nhanh, nhưng vẫn đào sâu tới Spring core khi cần. Mọi autoconfig là Java code đọc được — không có "magic curtain".

Liên hệ các bài khác

Bài này là nền cho cả module. Ghép với các bài khác để thấy bức tranh hoàn chỉnh:

  • refresh() — 12 bước khởi tạo container: bước 12 của SpringApplication.run() chính là refresh() đó — Boot wrap thêm 13 bước xung quanh. Đọc bài này để hiểu chi tiết từng pha bên trong bước 12.
  • Resource & @SpringBootApplication: @SpringBootApplication là composite annotation; bài đó giải thích vì sao annotation này đủ kích hoạt toàn bộ container.
  • Starter & BOM: trụ cột 1 (Starter Dependencies) — bóc sâu BOM, spring-boot-starter-parent, cách Boot quản version 200 lib và cách override khi cần.
  • Auto-Configuration deep dive: trụ cột 2 — cơ chế @ConditionalOn*, file AutoConfiguration.imports, thứ tự ưu tiên autoconfig.

Tóm tắt

  • Spring Boot không phải framework mới — là 5 lớp đóng gói trên Spring Framework để xoá 60-90 phút boilerplate.
  • 5 trụ cột: Starter Dependencies (BOM 200+ lib), Auto-Configuration (143 class, @ConditionalOn*), Embedded Server (Tomcat trong jar), Production-ready Features (Actuator + config), Build Plugin (executable fat jar).
  • SpringApplication.run() làm 14 bước: bước 12 là refresh() của Spring core — Boot wrap 11 bước trước + 2 bước sau.
  • "Opinionated": có default thông minh + cơ chế override formal (@ConditionalOnMissingBean). Khác lock-in ở chỗ override là tính năng thiết kế, không phải hack.
  • Debug startup: "Starting App" = bước 8, "Tomcat initialized" = giữa bước 12 (refresh()), "Started App" = cuối bước 12, "Application ready" = bước 14.

Tự kiểm tra

Tự kiểm tra
Q1
Spring Boot 3.x được giới thiệu là "opinionated". Hãy giải thích sự khác biệt giữa "opinionated" và "lock-in" theo cơ chế @ConditionalOnMissingBean. Cho 1 ví dụ override cụ thể.

Opinionated nghĩa là Boot có default thông minh cho 80% use case phổ biến — nhưng có cơ chế override chính thức: @ConditionalOnMissingBean. Mọi autoconfig bean đều có annotation này, khiến Boot tự rút lui khi user đăng ký bean cùng kiểu.

Lock-in là trường hợp framework không cung cấp cơ chế override — override = fork hoặc hack. Boot không phải lock-in vì override là tính năng thiết kế, không phải ngoại lệ.

Ví dụ override DataSource: Boot có DataSourceAutoConfiguration với @ConditionalOnMissingBean(DataSource.class). Khi user khai báo @Bean public DataSource dataSource() {...} trong config của họ, Boot phát hiện bean kiểu DataSource đã có → skip autoconfig → dùng bean user cung cấp. Không cần bất kỳ flag đặc biệt nào.

Điều này cũng giải thích tại sao Boot chọn "opinionated": Pivotal nhận ra 80% project dùng cùng stack — viết default cho 80% đó giải phóng developer tập trung vào business logic khác biệt.

Q2
SpringApplication.run(App.class, args) thực hiện 14 bước. Bước nào là trọng tâm? Vì sao khi app khởi động chậm, bước đó thường là nguyên nhân?

Bước 12: refresh() là trọng tâm — đây là 12 bước của Spring core container: scan component, load BeanDefinition, instantiate toàn bộ singleton non-lazy, gọi @PostConstruct, publish ContextRefreshedEvent.

Bước 12 thường chậm nhất vì nó instantiate mọi singleton bean cùng lúc: DataSource (kết nối DB, connection pool warm-up), EntityManagerFactory (Hibernate validate schema), Kafka consumer (network connect), Redis pool init, ... Mỗi bean có thể tốn hàng trăm ms.

Cách đo: Boot 3.x có Actuator endpoint /actuator/startup — list từng bean với thời gian instantiate. Hoặc bật logging.level.org.springframework=DEBUG để thấy từng bean được tạo. Khi bước 12 chiếm 80% startup time, thường do bean nào đó kết nối external system.

Boot wrap thêm 11 bước trước (detect app type, setup environment, tạo context, ...) và 2 bước sau (CommandLineRunner, ApplicationReadyEvent) — nhưng 11+2 bước đó thường tổng cộng dưới 200ms.

Q3
Vì sao Spring Boot ra đời năm 2014 giải quyết được "snowflake project" problem? "Snowflake project" là gì và Boot dùng cơ chế nào để xoá nó?

"Snowflake project" là thuật ngữ Pivotal dùng cho tình trạng mỗi project Spring 4 có cấu hình hoàn toàn khác nhau — mỗi team tự chọn version Tomcat, tự viết XML config theo style riêng, tự quyết định logging framework. Kết quả: không có project nào reproduce được từ project khác, knowledge transfer khó, bug từ version mismatch thường xuyên.

Boot xoá snowflake bằng 2 cơ chế kết hợp:

Thứ nhất, BOM (Starter Dependencies): Pivotal kiểm tra và curate 200+ lib version tương thích nhau, publish thành spring-boot-dependencies. Mọi project extends BOM này → cùng version stack → không còn version mismatch.

Thứ hai, Auto-Configuration: 143 autoconfig class register bean mặc định dựa trên classpath. Mọi project Boot dùng cùng DataSource config, cùng Tomcat config, cùng Logback config — cho đến khi user override. Điểm xuất phát là giống nhau, khác biệt chỉ ở phần user thực sự cần khác.

Kết hợp lại: từ một project Boot mới, developer khác có thể reproduce environment bằng cách đọc pom.xml + application.yml — không cần wiki riêng hướng dẫn setup.

Q4
@SpringBootApplication là composite của 3 annotation. Liệt kê 3 annotation đó và giải thích vai trò của từng cái trong việc kích hoạt Spring container.

@SpringBootApplication gộp 3 annotation:

1. @SpringBootConfiguration (alias của @Configuration): đánh dấu class này là nguồn bean definition — Spring sẽ đọc các @Bean method trong class này và register vào beanDefinitionMap.

2. @EnableAutoConfiguration: kích hoạt cơ chế autoconfig — Boot đọc file META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, load 143 autoconfig class, và trong bước 12 (refresh()) các class này register bean conditional dựa trên classpath.

3. @ComponentScan: quét toàn bộ package hiện tại (và sub-package) tìm @Component, @Service, @Repository, @Controller — đăng ký chúng vào beanDefinitionMap trong bước 12.

Ba annotation cộng lại = khai báo bean config + bật autoconfig + scan business bean. Đủ để SpringApplication.run() tạo một container đầy đủ.

Q5
Một developer viết Boot app và thêm @Bean public DataSource ds() {...} trong class có @SpringBootApplication. App có DataSource nào được dùng: của developer hay của DataSourceAutoConfiguration? Giải thích theo cơ chế @ConditionalOnMissingBean.

DataSource của developer được dùng.

Cơ chế: trong bước 12 (refresh()), Spring load cả bean definition từ @Bean method của developer lẫn DataSourceAutoConfiguration. Khi xử lý DataSourceAutoConfiguration, Spring thấy annotation @ConditionalOnMissingBean(DataSource.class) trên @Bean method bên trong nó.

Spring kiểm tra: "Có bean nào kiểu DataSource đã được register vào beanDefinitionMap chưa?" — Có, bean ds() của developer đã ở đó. Condition fail → autoconfig không register bean mặc định của nó.

Hệ quả: container chỉ có 1 bean kiểu DataSource — bean của developer. Autoconfig tự rút lui hoàn toàn, không cần flag hay annotation đặc biệt phía developer.

Lưu ý thực tế: đặt @Bean DataSource trong class main là anti-pattern về tổ chức code — nên tách ra class DataSourceConfig.java riêng, nhưng cơ chế @ConditionalOnMissingBean hoạt động đúng bất kể bean ở đâu.

Bài tiếp theo: Starter & BOM

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên