Command Palette

Search for a command to run...

[Spring Boot Basics] Spring Framework và Spring Boot khác nhau thế nào: Auto-Configuration, Starter và Embedded Server

Spring Framework và Spring Boot không cạnh tranh với nhau, và Boot cũng không phải một phiên bản Spring mới hơn. Boot là một lớp default nằm trên Framework, quyết định xem một application thông thường nhiều khả năng cần gì, rồi config sẵn phần đó cho bạn. Bỏ lớp này đi thì mọi khái niệm Spring bên dưới vẫn y nguyên — chỉ là bạn phải tự viết toàn bộ phần config.

Nhờ vậy câu hỏi "hai cái khác nhau chỗ nào" có một câu trả lời cụ thể và đo được: đó chính là tập hợp những việc Boot làm hộ. Bài này đo từng việc một. Mọi version, dependency tree và dòng log bên dưới đều lấy ra từ một build thật.

Bên trái là chồng thứ phải config bằng tay, bên phải là một dòng starter thay thế tất cả

Toàn bộ bài chạy trên OpenJDK 21.0.6 với Spring Boot 4.1.1Gradle 9.7.1, dùng một project sinh từ Spring Initializr và build bằng Gradle wrapper đi kèm.

Spring Framework là container, Spring Boot là những lựa chọn mặc định

Spring Framework là phần runtime: nó khởi tạo object của bạn, inject các collaborator vào, proxy chúng để làm transaction và security, và cung cấp web stack. Nó cũ, ổn định và cố tình không áp đặt gì cả — bạn config sao thì nó chạy vậy, và trước đây bạn phải config tất cả.

Spring Boot thêm đúng bốn thứ lên trên, không có khái niệm nào mới về mặt lý thuyết:

Boot đóng gópNó bỏ đi việc gì
Starterđi tìm đủ mười lăm artifact cho một tính năng
Dependency management (một BOM)tự chọn version sao cho khớp nhau
Auto-configurationviết các class @Configuration mà app nào cũng phải viết
Embedded server và launchercài servlet container rồi deploy WAR vào đó

Mỗi bản Boot gắn chặt với một bản Framework, và bạn không chọn riêng từng cái. Boot 4.1.1 chính là Framework 7.0.9, và BOM spring-boot-dependencies nói thẳng điều đó:

XML
<spring-framework.version>7.0.9</spring-framework.version>

Dependency tree sau khi resolve cũng khớp. Mọi artifact org.springframework trên classpath của một project Boot 4.1.1 đều về 7.0.9 mà không ai phải yêu cầu:

Text
\--- org.springframework.boot:spring-boot-starter-webmvc -> 4.1.1
     +--- org.springframework.boot:spring-boot-starter:4.1.1
     |    +--- org.springframework.boot:spring-boot-autoconfigure:4.1.1
     |    |    \--- org.springframework.boot:spring-boot:4.1.1
     |    |         +--- org.springframework:spring-core:7.0.9
     |    |         \--- org.springframework:spring-context:7.0.9

Nhánh 4.x có hai hệ quả thực tế. Boot 4 được compile cho Java 17 — class file trong spring-boot-4.1.1.jar mang major version 61 — nên 17 là mức sàn, còn thực tế bạn sẽ chọn 21 hoặc 25. Và Boot 3.5 đã hết open-source support từ 2026-06-30, đó là lý do một project mới bắt đầu hôm nay nên khởi động thẳng từ 4.x.

Một web application Spring Framework thuần bắt bạn viết những gì

Trước khi có Boot, "một web application Spring" nghĩa là tự lắp ráp từng mảnh. Đoạn dưới mang tính minh hoạ chứ không phải thứ đáng chạy lại hôm nay, nhưng từng mục trong đó đều là việc thật.

Đầu tiên là một class bootstrap để báo cho servlet container biết Spring tồn tại và nó quản những URL nào — hoặc bản web.xml tương đương:

WebInit.java
public class WebInit implements WebApplicationInitializer {
 
    @Override
    public void onStartup(ServletContext ctx) {
        var ac = new AnnotationConfigWebApplicationContext();
        ac.register(WebConfig.class);
 
        var reg = ctx.addServlet("dispatcher", new DispatcherServlet(ac));
        reg.setLoadOnStartup(1);
        reg.addMapping("/");
    }
}

Tiếp theo là một class config để bật hạ tầng MVC và khai báo response được serialize ra sao:

WebConfig.java
@Configuration
@EnableWebMvc
@ComponentScan("com.example.demo")
public class WebConfig implements WebMvcConfigurer {
 
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new MappingJackson2HttpMessageConverter());
    }
}

Rồi đến một build file liệt kê từng artifact kèm version riêng, một bước đóng gói WAR, và một Tomcat đã cài sẵn trên máy sẽ chạy cái WAR đó.

Phần đo được ở đây là dependency graph. Chỉ khai spring-webmvc — với version gõ tay, vì chẳng có gì quản lý nó — sẽ resolve ra mười một jar và 8.3 MiB:

Text
\--- org.springframework:spring-webmvc:7.0.9
     +--- org.springframework:spring-aop:7.0.9
     +--- org.springframework:spring-beans:7.0.9
     +--- org.springframework:spring-context:7.0.9
     |    \--- io.micrometer:micrometer-observation:1.16.7
     +--- org.springframework:spring-expression:7.0.9
     \--- org.springframework:spring-web:7.0.9

Đó là mốc trung thực: chỉ có MVC stack, không gì khác. Không servlet container, không thư viện JSON, không logging backend, không cơ chế bind config, và không có cách nào khởi động nó lên. Tất cả những thứ đó là các quyết định riêng, với những version riêng.

Spring Boot thay thế bằng cái gì

Cũng application đó, trên Boot, chỉ là một class:

src/main/java/com/example/demo/DemoApplication.java
package com.example.demo;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class DemoApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Mười ba dòng, trong đó bốn dòng là package và import. Không có initializer, không @EnableWebMvc, không phải đăng ký DispatcherServlet, không danh sách message converter, và không phải cài servlet container nào. Build file đi kèm dài hai mươi ba dòng không trống, và khối dependency chiếm bốn dòng:

build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webmvc'
    testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

Bảy việc mà một web application phải làm, với câu trả lời của Spring Framework thuần và của Spring Boot nằm cùng một dòng

Đọc từng dòng một sẽ thấy: không có gì ở phía Boot là khái niệm Spring mới. DispatcherServletAutoConfiguration đăng ký đúng cái DispatcherServletWebInit ở trên đã đăng ký. Khác biệt nằm ở chỗ ai là người viết đoạn đăng ký đó.

⚠️ Trên Boot 4, web starter là spring-boot-starter-webmvc chứ không phải spring-boot-starter-web. Tên cũ vẫn resolve được — thậm chí ra đúng classpath 39 jar y hệt — nhưng POM của nó trên Maven Central giờ tự mô tả là "Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container (deprecated in favor of spring-boot-starter-webmvc)". Về phía test, Initializr giờ sinh ra spring-boot-starter-webmvc-test hẹp hơn thay cho spring-boot-starter-test tổng quát, cái vẫn tồn tại và không hề bị deprecated. Gần như mọi tutorial bạn tìm được vẫn ghi -web, vì đó là tên dùng suốt cả thập kỷ.

Một Spring Boot starter thực chất là gì?

Starter là một POM không chứa code. Nó compile ra không class nào, không mang annotation nào và không tự thêm hành vi gì. Việc duy nhất nó làm là gọi tên một nhóm artifact đã được chọn lọc, để bạn thêm một coordinate vào build là có cả nhóm đó.

spring-boot-starter-webmvc khai báo năm dependency trực tiếp. Resolve hết transitive ra được 39 jar và 18.8 MiB trên runtime classpath:

Một dòng starter mở ra thành năm dependency trực tiếp và cây transitive bên dưới

Đây là report thật, cắt ở tầng đầu tiên:

Text
\--- org.springframework.boot:spring-boot-starter-webmvc -> 4.1.1
     +--- org.springframework.boot:spring-boot-starter:4.1.1
     +--- org.springframework.boot:spring-boot-starter-jackson:4.1.1
     +--- org.springframework.boot:spring-boot-starter-tomcat:4.1.1
     +--- org.springframework.boot:spring-boot-http-converter:4.1.1
     \--- org.springframework.boot:spring-boot-webmvc:4.1.1

Ba trong năm cái đó bản thân cũng là starter. spring-boot-starter là nền mà mọi starter khác đều phụ thuộc vào — core container cộng logging cộng phần đọc YAML. spring-boot-starter-tomcat là embedded servlet container. spring-boot-starter-jackson là JSON. Chính cách ghép này giải thích vì sao một dòng lại cho bạn một HTTP server chạy được và nói được JSON.

Boot 4.1.1 publish 170 starter. Đây là những cái bạn gặp đầu tiên, kèm mô tả lấy từ chính POM của chúng:

StarterDùng để làm gì
spring-boot-starterstarter nền: auto-configuration, logging và YAML
spring-boot-starter-webmvcSpring MVC và Tomcat
spring-boot-starter-webfluxWebFlux và Reactor Netty, cho stack reactive
spring-boot-starter-data-jpaSpring Data JPA với Hibernate
spring-boot-starter-jdbcJDBC với connection pool HikariCP
spring-boot-starter-securitySpring Security
spring-boot-starter-validationJava Bean Validation với Hibernate Validator
spring-boot-starter-actuatorcác endpoint sẵn sàng cho production để monitor và quản lý
spring-boot-starter-thymeleaftemplate HTML render phía server
spring-boot-starter-restclientcác HTTP client blocking — RestClient, RestTemplate, HTTP service client
spring-boot-starter-cachelớp cache abstraction của Spring
spring-boot-starter-testJUnit Jupiter, Hamcrest và Mockito, đã ghép sẵn cho Spring

Có một quy ước đặt tên đáng nhớ, vì nhìn vào là biết ai đang maintain một dependency. spring-boot-starter-* được dành riêng cho starter do team Spring publish. Bên thứ ba thì đặt tên mình lên trước: mybatis-spring-boot-starter, mybatis-plus-spring-boot3-starter. Nếu có lúc nào bạn publish một starter, hãy theo đúng quy ước đó — một coordinate mở đầu bằng spring-boot-starter- là đang tự nhận mình chính chủ.

Các version number đến từ đâu?

Nhìn lại khối dependency ở trên: không có một version nào. Đó không phải Boot plugin làm gì thông minh lúc runtime, mà là một bill of materials — một POM có nhiệm vụ duy nhất là tuyên bố "nếu bạn dùng artifact này thì dùng version này". spring-boot-dependencies:4.1.1 quản 652 dependency qua 195 version property.

Bên Gradle, nó được áp bởi plugin io.spring.dependency-management, cái mà Initializr đặt sẵn trong build file cạnh Boot plugin:

build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '4.1.1'
    id 'io.spring.dependency-management' version '1.1.7'
}

Hai plugin này làm hai việc khác nhau, đừng nhầm. Boot plugin build ra executable jar và dựng task bootRun. Plugin dependency-management import BOM để những coordinate không có version resolve được. Xoá dòng thứ hai đi là build chết ngay:

Text
* What went wrong:
Execution failed for task ':compileJava'.
> Could not resolve all files for configuration ':compileClasspath'.
   > Could not find org.springframework.boot:spring-boot-starter-webmvc:.
     Required by:
         root project 'demo'

Dấu hai chấm cuối câu không có gì đứng sau chính là version rỗng — Gradle hỏi version, mà BOM thì không có ở đó để trả lời.

Thứ BOM mang lại là sự đồng bộ, chứ không chỉ là tiện. Một nửa số jar trên classpath 39 jar kia mang version mà bạn chưa gõ ở đâu cả:

ArtifactVersionĐến từ đâu
tomcat-embed-core11.0.24tomcat.version trong BOM
logback-classic1.5.38logback.version trong BOM
jackson-databind3.1.5jackson-bom.version trong BOM
snakeyaml2.6snakeyaml.version trong BOM
slf4j-api2.0.18slf4j.version trong BOM

Chỗ thấy rõ nhất tác dụng của sự đồng bộ là khi hai thư viện không thống nhất. spring-webmvc:7.0.9 khai phụ thuộc vào micrometer-observation:1.16.7. Ở build thuần, bạn nhận đúng con số đó. Dưới Boot, BOM nâng nó lên và Gradle in ra phép thay thế:

Text
io.micrometer:micrometer-observation:1.16.7 -> 1.17.1

Không ai yêu cầu 1.17.1 cả. BOM quyết định đó mới là bản Micrometer mà toàn bộ Boot 4.1.1 đã được test cùng, và giờ mọi module trên classpath đều thống nhất ở đó. Làm việc này bằng tay trên 652 artifact chính là phần công sức mà Boot đang tiết kiệm cho bạn.

Auto-configuration thực sự làm gì

Ý tưởng gọn đến mức nói được trong một câu: lúc khởi động, Boot nhìn xem có gì trên classpath và trong config của bạn, rồi tạo ra những bean mà một application thông thường lẽ ra phải tự tạo — nhưng chỉ khi bạn chưa tạo chúng.

Phần khiến nó dùng được là cơ chế nhường chỗ. Các class auto-configuration canh bean của mình bằng @ConditionalOnMissingBean, nên ngay khi bạn khai một bean cùng type, bản của Boot lặng lẽ lùi lại và bản của bạn thắng. Không có gì phải disable, không có cuộc chiến nào phải thắng; khai bean chính là cách override.

Chạy application với --debug sẽ in ra một CONDITIONS EVALUATION REPORT liệt kê từng quyết định. Trên project trần này, nó báo 52 match dương và 39 match âm, trong đó có cái đăng ký đúng servlet mà class WebInit ngày xưa phải tự đăng ký:

Text
DispatcherServletAutoConfiguration matched:
   - @ConditionalOnClass found required class
     'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition)
   - found 'session' scope (OnWebApplicationCondition)

Đọc kỹ report đó — và cả họ annotation @Conditional* đứng sau nó — là nội dung của riêng một chương, và series này sẽ tới đó sau. Còn bây giờ, mô hình đủ dùng là: classpath vào, bean ra, và thứ gì bạn tự khai thì thứ đó được ưu tiên.

Embedded server hay WAR: hai hình dạng deploy

Cách deploy Spring cổ điển tạo ra một WAR mà bạn copy vào một servlet container do người khác cài và vận hành. Boot lật ngược quan hệ chứa: build tạo ra một jar duy nhất, và chính jar đó chứa server.

Bên trái là WAR nằm trong một Tomcat đã cài sẵn, bên phải là fat jar chứa tomcat-embed-core

Cái jar đó tự chứa thật sự. Manifest của nó trỏ vào launcher của Boot chứ không phải class của bạn, và bố cục lồng nhau bên trong là chuẩn:

Text
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: com.example.demo.DemoApplication
Spring-Boot-Version: 4.1.1
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/

BOOT-INF/lib/ chứa 35 jar, trong đó có tomcat-embed-core-11.0.24.jar, và cả artifact nặng 19.0 MiB. Chạy nó chỉ cần một JVM, không cần gì thêm:

Bash
java -jar build/libs/demo-0.0.1-SNAPSHOT.jar
Text
INFO 12815 --- [demo] [main] o.s.boot.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
INFO 12815 --- [demo] [main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/11.0.24]
INFO 12815 --- [demo] [main] o.s.boot.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
INFO 12815 --- [demo] [main] com.example.demo.DemoApplication : Started DemoApplication in 0.561 seconds (process running for 0.744)

Con số thời gian khởi động đó chỉ mang tính tham khảo — máy dev với cache đã nóng, không phải một benchmark.

Còn đây là những gì thực sự thay đổi giữa hai hình dạng:

WAR trong container bên ngoàiExecutable jar
Version của serverdo người vận hành server chọn và vághim trong build file, đi theo artifact
Config của serverserver.xml, áp cho cả containerapplication.properties, riêng từng application
Lệnh khởi độngscript riêng của containerjava -jar app.jar
Portcủa container, dùng chung cho mọi app đã deploycủa bạn, mỗi application một process
Testcần một container, hoặc một bản mockserver thật chạy in-process trên một port ngẫu nhiên
Phạm vi ảnh hưởng khi hỏngmột app lỗi có thể kéo sập hàng xómmột process, một application

Đổi embedded server

Tomcat là mặc định vì spring-boot-starter-webmvc kéo theo spring-boot-starter-tomcat. Exclude nó ra rồi thêm cái khác vào:

build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webmvc'
    implementation('org.springframework.boot:spring-boot-starter-webmvc') { 
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    } 
    implementation 'org.springframework.boot:spring-boot-starter-jetty'
}

Không sửa code, không sửa config. Cũng application đó khởi động trên Jetty:

Text
INFO 14295 --- [demo] [main] org.eclipse.jetty.server.Server : Started oejs.Server@799ed4e8{STARTING}[12.1.12,sto=0] @44ms
INFO 14295 --- [demo] [main] o.s.boot.jetty.JettyWebServer   : Jetty started on port 8080 (http/1.1) with context path '/'

Một đính chính cho gần như mọi bài viết về chủ đề này: Boot 4 không còn starter cho Undertow. spring-boot-starter-undertow có đến 3.5.x và biến mất khỏi 4.x — nó không nằm trong BOM spring-boot-dependencies:4.1.1, và coordinate đó trả về 404 trên Maven Central. Trên Boot 4, embedded servlet container chỉ còn Tomcat và Jetty, cộng thêm Reactor Netty cho stack reactive.

Nếu vẫn buộc phải ship WAR

Boot vẫn hỗ trợ. Yêu cầu Initializr packaging=war thì project sinh ra sẽ thêm plugin war, chuyển container sang providedRuntime để nó không bị gói vào archive, và kèm thêm một class initializer nhỏ:

build.gradle
plugins {
    id 'java'
    id 'war'
    id 'org.springframework.boot' version '4.1.1'
    id 'io.spring.dependency-management' version '1.1.7'
}
 
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webmvc'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat-runtime'
}
ServletInitializer.java
public class ServletInitializer extends SpringBootServletInitializer {
 
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(WarpApplication.class);
    }
}

Class đó chính là hậu duệ hiện đại của WebApplicationInitializer ở phần đầu bài — container bên ngoài tìm thấy nó, rồi nó trao quyền lại cho Boot.

Hệ sinh thái Spring mà bạn sẽ gặp

"Spring" là cái ô che cho hàng chục project. Đây là những cái mà một developer đi làm sẽ đụng tới, kèm version mà BOM của Boot 4.1.1 quản:

ProjectDùng để làm gìVersion dưới Boot 4.1.1
Spring Frameworkcontainer, DI, AOP, transaction, web stack7.0.9
Spring Bootstarter, auto-configuration, embedded server, BOM4.1.1
Spring Datalớp repository abstraction cho JPA, MongoDB, Redis, JDBC và nhiều loại khácBOM 2026.0.1
Spring Securityauthentication, authorization, OAuth2, method security7.1.1
Spring Batchbatch job theo chunk, có restart và retry6.0.5
Spring Integrationcác enterprise integration pattern dưới dạng message channel7.1.1
Spring for Apache Kafkaproducer, consumer và listener container cho Kafka4.1.1
Spring AMQPcũng vậy nhưng cho RabbitMQ4.1.1
Spring GraphQLmột endpoint GraphQL đặt trên web stack của Spring2.0.5
Spring Sessionlưu HTTP session vào Redis hoặc JDBC thay vì trong server4.1.1
Spring Web ServicesSOAP contract-first, nơi nào bạn còn cần tới5.0.2
Spring Cloudconfig server, service discovery, gateway, resilienceBOM riêng
Spring AImodel client, embedding, vector store, phần khung cho RAGBOM riêng
Spring Modulithép ranh giới module bên trong một deployableBOM riêng

Ba cái cuối đáng nhắc riêng: Spring Cloud, Spring AI và Spring Modulith không nằm trong BOM của Boot. Mỗi cái publish BOM riêng, và bạn import nó song song với BOM của Boot rồi khớp release train với version Boot đang dùng. Mọi thứ phía trên chúng thì đi kèm sẵn theo version Boot bạn đã chọn.

Khi nào Spring Boot không phải là câu trả lời

Boot là một mặc định, không phải luật, và những lựa chọn mặc định của nó cũng có giá.

  • Một thư viện để người khác nhúng vào. Nếu artifact của bạn là một dependency chứ không phải một thứ để deploy, thì fat jar và auto-configuration là sai hình dạng. Hãy publish một jar thường, dùng Spring Framework thuần hoặc không framework nào cả.
  • Một script chạy một lần. Ba mươi chín jar và một artifact 19 MiB chỉ để parse một file CSV là cái giá không đáng.
  • Một hạ tầng application server dùng chung. Nếu bộ phận vận hành chạy cả một farm Tomcat và bắt buộc deploy WAR vào đó, bạn vẫn dùng Boot được, nhưng mất đi phần đơn giản hoá deploy vốn là điểm cộng lớn nhất.
  • Một application Spring Framework đang chạy tốt. Chuyển một hệ thống XML cũ, ổn định sang Boot tự nó không mang lại gì. Hãy chuyển khi bạn vốn đã phải sửa nó rồi.
  • Trần cứng về thời gian khởi động hoặc bộ nhớ. Quét classpath và reflection không miễn phí. Boot có AOT processing và GraalVM native image cho chuyện này, nhưng nếu bạn đang giành giật từng chục mili giây thì một framework nhỏ hơn có thể hợp hơn.

Với một application phía server do chính bạn sở hữu và deploy — tức là phần lớn những gì được viết ra — cán cân nghiêng hẳn về phía Boot, và đó là lý do phần còn lại của series này mặc định dùng nó.

Câu hỏi thường gặp

Spring Boot có thay thế Spring Framework không?

Không. Boot phụ thuộc vào Framework và không thể tồn tại thiếu nó. Boot 4.1.1 kéo về Framework 7.0.9, và mọi bean, mọi annotation cùng toàn bộ container đều là code của Framework. Boot chỉ quyết định cái gì được config sẵn.

Có dùng Spring Framework mà không dùng Spring Boot được không?

Được, và vẫn được hỗ trợ đầy đủ. Bạn khai thẳng spring-webmvc hoặc spring-context, tự chọn từng version, và tự viết các class config. Cái tree ở trên cho thấy trọn cái giá: mười một jar cho MVC stack, còn server, JSON và logging thì bạn tự thêm.

Vì sao là spring-boot-starter-webmvc chứ không phải spring-boot-starter-web?

Boot 4 chia module mịn hơn và đổi tên web starter để nói rõ nó là web stack nào, vì spring-boot-starter-webflux là lựa chọn còn lại. spring-boot-starter-web vẫn resolve được trên 4.1.1 và cho ra đúng 39 jar y hệt — khác biệt duy nhất trên classpath đã resolve là tên của chính file jar starter — nhưng POM của chính nó đánh dấu deprecated và trỏ sang spring-boot-starter-webmvc.

Auto-configuration có làm mình mất quyền kiểm soát không?

Không — nó được thiết kế để chịu thua. Bean do auto-configuration tạo ra đều được canh bằng @ConditionalOnMissingBean, nên khai một bean cùng type là bản của Boot biến mất. Bạn cũng có thể exclude hẳn một class auto-configuration, và --debug sẽ in ra report về cái gì đã match và cái gì không.

Embedded server có dùng được cho production không?

Có. Nó chính là Apache Tomcat 11.0.24 mà bạn sẽ cài standalone, chỉ khác là khởi động in-process thay vì bằng script. Khác biệt thực tế nằm ở khâu vận hành — mỗi application một process, config nằm trong application.properties, và version của server do build của bạn ghim chứ không do người vá server quyết định.

Làm sao biết Spring Boot sẽ cho mình version nào của một thư viện?

Đọc spring-boot-dependencies của bản Boot đang dùng, hoặc hỏi thẳng build. ./gradlew dependencies --configuration runtimeClasspath in ra cây đã resolve với mọi phép thay thế được đánh dấu bằng mũi tên, và đó chính là cách tìm ra phép nâng micrometer-observation:1.16.7 -> 1.17.1 ở trên.

Kết luận

Khác biệt giữa Spring Framework và Spring Boot không phải một danh sách tính năng, mà là một cách chia việc. Framework chạy bean của bạn; Boot quyết định một application bình thường cần những bean nào, ghim mọi version đi kèm, rồi đóng gói kết quả với một server nằm sẵn bên trong. Một dòng starter, bốn dependency, 39 jar, và một DispatcherServlet bạn chưa từng đăng ký.

Bài tiếp theo: cài đặt môi trường — cài JDK và IDE, tạo project bằng Spring Initializr, và chạy ứng dụng Hello World đầu tiên.

Bài viết liên quan

[Spring Boot Basics] Validation trong Spring Boot: Bean Validation, @Valid và custom validator

Bean Validation trong Spring Boot 4.1.1 với Hibernate Validator 9.1.3, kiểm chứng bằng các lần chạy thật: spring-boot-starter-validation, @NotNull, @NotEmpty và @NotBlank khác nhau ra sao, @Size, @DecimalMin, @Digits, @Email và @Pattern trên DTO record, @Valid với @RequestBody và response 400 mặc định, object lồng nhau và list, validate @PathVariable và @RequestParam cùng cái bẫy 500 của @Validated, validation group, ValidationMessages.properties và Accept-Language, custom ConstraintValidator và constraint liên quan nhiều field, và validation ở service layer.

[Spring Boot Basics] Logging trong Spring Boot: SLF4J, Logback, log level và ghi log ra file

Logging trong Spring Boot 4.1.1 kiểm chứng trên project thật: SLF4J là facade và Logback 1.5.38 là implementation, hai bridge jul-to-slf4j và log4j-to-slf4j, parameterised và fluent logging, ghi log exception, log level, cây logger và log group, --debug so với --trace, pattern dòng log mặc định, logging.file.name kèm rotation, logback-spring.xml với springProfile, MDC và chuyển sang Log4j2.

[Spring Boot Basics] Auto-configuration trong Spring Boot hoạt động ra sao: conditional, back-off và báo cáo --debug

Mổ xẻ cơ chế auto-configuration của Spring Boot 4.1.1 bằng số liệu thật: @EnableAutoConfiguration và AutoConfigurationImportSelector, các file META-INF/spring/…AutoConfiguration.imports mà Boot 4 tách ra nhiều module nhỏ, họ annotation @ConditionalOnClass / @ConditionalOnMissingBean cùng một Condition tự viết, màn demo back-off có số liệu trước và sau, và cách đọc báo cáo CONDITIONS EVALUATION REPORT từ --debug.

[Spring Boot Basics] Cấu trúc project Spring Boot: Gradle và Maven, @SpringBootApplication và file JAR

Toàn bộ file mà Spring Initializr sinh ra trên Spring Boot 4.1.1 và Java 21: cấu trúc thư mục, wrapper của Gradle và Maven, build.gradle đọc từng dòng đặt cạnh pom.xml tương ứng, so sánh Maven và Gradle công bằng, BOM giúp bỏ version number, @SpringBootApplication tách thành ba meta-annotation kèm lỗi 404 do sai base package, và giải phẫu file JAR chạy được.