Starter dependencies — bóc tách spring-boot-starter-web
Starter là 'meta-dependency' rỗng pull theo curated transitive jars. Bài này bóc spring-boot-starter-web ra từng jar, giải thích BOM (spring-boot-dependencies), spring-boot-starter-parent vs import scope, cách Pivotal quản 200+ lib version, và cách tự viết starter custom cho team.
Bài 01 đã chỉ ra Spring Boot có 5 trụ cột, trong đó Starter Dependencies giải quyết pain version mismatch của Spring 4 era. Bài này bóc tầng tiếp theo: starter là gì cụ thể, spring-boot-starter-web pull theo những jar nào, BOM hoạt động ra sao, và bạn có thể tự viết starter cho team không.
Starter không phải tính năng "thêm cho vui" — nó là đóng góp lớn nhất của Boot về mặt thực dụng. 90% giá trị Boot mang lại đến từ ecosystem 200 lib được Pivotal test tương thích version. Hiểu starter là hiểu vì sao Boot upgrade dễ và migrate giữa Boot version khả thi.
1. Starter là gì — về mặt vật lý
Mở Maven Central, search spring-boot-starter-web. Tải file spring-boot-starter-web-3.4.0.jar. Mở ra (jar = zip):
spring-boot-starter-web-3.4.0.jar
├── META-INF/
│ ├── MANIFEST.MF
│ ├── maven/
│ │ └── org.springframework.boot/
│ │ └── spring-boot-starter-web/
│ │ ├── pom.properties
│ │ └── pom.xml <-- file QUAN TRONG
│ └── spring.provides
└── (KHONG CO .class file)
Jar rỗng class — chỉ có pom.xml bên trong. Mở pom.xml:
<project>
<artifactId>spring-boot-starter-web</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
</project>
Đó là toàn bộ. Starter chỉ là 1 file pom.xml đóng gói thành jar — không có Java code. Mục đích duy nhất: liệt kê transitive dependencies mà bạn cần để dùng feature web.
Khi bạn khai báo trong project:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Maven download spring-boot-starter-web.jar (rỗng), đọc pom.xml của nó, recurse pull 5 transitive dep. Mỗi transitive lại pull tiếp:
flowchart TB
SBW["spring-boot-starter-web"]
SB["spring-boot-starter<br/>(core: logging, autoconfig)"]
SBJ["spring-boot-starter-json<br/>(Jackson)"]
SBT["spring-boot-starter-tomcat<br/>(Tomcat embedded)"]
SW["spring-web<br/>(HTTP foundations)"]
SWMVC["spring-webmvc<br/>(DispatcherServlet)"]
SBW --> SB
SBW --> SBJ
SBW --> SBT
SBW --> SW
SBW --> SWMVC
SB --> Logging["spring-boot, spring-boot-autoconfigure,<br/>logback-classic, log4j-to-slf4j, jul-to-slf4j,<br/>jakarta.annotation-api, snakeyaml"]
SBJ --> Jackson["jackson-databind, jackson-datatype-jdk8,<br/>jackson-datatype-jsr310, jackson-module-parameter-names"]
SBT --> Tomcat["tomcat-embed-core, tomcat-embed-el,<br/>tomcat-embed-websocket"]
SW --> Beans["spring-beans, spring-core,<br/>jakarta.servlet-api"]
SWMVC --> Aspects["spring-aop, spring-context,<br/>spring-expression"]
style SBW fill:#fef3c7Total: ~30 transitive jar từ 1 starter. Maven dependency:tree xem được toàn bộ:
mvn dependency:tree
# [INFO] org.olhub:demo:jar:0.0.1-SNAPSHOT
# [INFO] +- org.springframework.boot:spring-boot-starter-web:jar:3.4.0:compile
# [INFO] | +- org.springframework.boot:spring-boot-starter:jar:3.4.0:compile
# [INFO] | | +- org.springframework.boot:spring-boot:jar:3.4.0:compile
# [INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:3.4.0:compile
# [INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:3.4.0:compile
# [INFO] | | | +- ch.qos.logback:logback-classic:jar:1.5.12:compile
# [INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.24.1:compile
# [INFO] | | | \- org.slf4j:jul-to-slf4j:jar:2.0.16:compile
# [INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:2.1.1:compile
# [INFO] | | +- org.springframework:spring-core:jar:6.2.0:compile
# ... 25 dong nua
2. BOM — spring-boot-dependencies
Câu hỏi quan trọng: version từ đâu? Pom của starter không có <version> cho transitive dep — Maven biết version nào tương thích?
Đáp: từ BOM (Bill of Materials) spring-boot-dependencies.
2.1 BOM là gì
BOM là 1 pom file chỉ chứa dependencyManagement — không có dependencies. Mục đích: định nghĩa version cho lib mà không pull lib đó.
Mở spring-boot-dependencies-3.4.0.pom (Maven Central):
<project>
<artifactId>spring-boot-dependencies</artifactId>
<packaging>pom</packaging>
<properties>
<spring-framework.version>6.2.0</spring-framework.version>
<hibernate.version>6.6.3.Final</hibernate.version>
<jackson-bom.version>2.18.1</jackson-bom.version>
<tomcat.version>10.1.33</tomcat.version>
<hikaricp.version>5.1.0</hikaricp.version>
<logback.version>1.5.12</logback.version>
<!-- 200+ properties -->
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
</dependency>
<!-- 600+ entries quan version cho 200+ lib -->
</dependencies>
</dependencyManagement>
</project>
spring-boot-dependencies quản version cho 200+ lib. Pivotal test mọi combination tương thích trước khi release. Bạn extends BOM → tự động lấy version đã verify.
2.2 2 cách extend BOM
Cách 1 — spring-boot-starter-parent (hay dùng nhất):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0</version>
</parent>
spring-boot-starter-parent là 1 pom inherit từ spring-boot-dependencies + thêm:
- Java version default (17).
- UTF-8 encoding.
- Maven plugin defaults (Surefire, Failsafe, ...).
- Resource filtering pattern.
- Manifest attribute defaults.
Lợi ích: 1 dòng <parent> → setup toàn bộ project.
Hạn chế: project bạn không thể có <parent> khác. Nếu team đã có corporate parent (vd acme-parent cho enterprise lib management), không dùng được.
Cách 2 — Import BOM (cho project có parent khác):
<parent>
<groupId>com.acme</groupId>
<artifactId>acme-parent</artifactId> <!-- corporate parent -->
<version>1.0</version>
</parent>
<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>
scope=import + type=pom = "import dependencyManagement của BOM vào đây". Lúc này version từ BOM apply như parent.
Hạn chế: không inherit Java version, plugin defaults, encoding. Phải config thủ công.
2.3 Cơ chế version override
BOM không "khoá" version. Bạn vẫn override được:
<!-- Override property -->
<properties>
<hibernate.version>6.5.0.Final</hibernate.version> <!-- thap hon BOM -->
</properties>
<!-- Hoac override <dependencyManagement> truc tiep -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.5.0.Final</version> <!-- override -->
</dependency>
</dependencies>
</dependencyManagement>
Khi nào nên override?
- Security CVE: BOM stable chưa có version vá lỗi → override tạm.
- Critical bug fix: bạn cần feature commit gần đây.
- Compatibility với lib khác: rare.
Cảnh báo: override = bạn tự nhận trách nhiệm test compat. Pivotal không guarantee. Override 1-2 lib cụ thể OK; override 10+ lib = bạn nên upgrade Boot version thay.
3. Bóc starter cụ thể — spring-boot-starter-web
Đi qua từng transitive jar để hiểu role:
3.1 spring-boot-starter — core của mọi starter
spring-boot-starter (core)
├── spring-boot (SpringApplication, banner, args parser)
├── spring-boot-autoconfigure (143 autoconfig class + @EnableAutoConfiguration)
├── spring-boot-starter-logging (Logback default)
│ ├── logback-classic (logger impl)
│ ├── log4j-to-slf4j (bridge log4j → slf4j)
│ └── jul-to-slf4j (bridge java.util.logging → slf4j)
├── jakarta.annotation-api (@PostConstruct, @PreDestroy)
├── spring-core (util, classloader, IO)
└── snakeyaml (parse application.yml)
spring-boot-starter (no suffix) là core — mọi starter khác đều pull theo. Cung cấp:
- Bootstrap (
SpringApplication). - Auto-configuration engine.
- Logging stack default (Logback + 2 bridge cho log4j/JUL).
- JSR-250 annotation.
3.2 spring-boot-starter-json — Jackson JSON
spring-boot-starter-json
├── jackson-databind
│ ├── jackson-core
│ └── jackson-annotations
├── jackson-datatype-jdk8 (Optional, Stream — cho Java 8+)
├── jackson-datatype-jsr310 (LocalDate, LocalDateTime, Instant)
└── jackson-module-parameter-names (auto-detect param name → JSON property)
Spring Boot dùng Jackson default cho @RequestBody và @ResponseBody. Module này pull đầy đủ Jackson + 3 datatype module:
- jdk8: Optional, Stream serialize/deserialize.
- jsr310: java.time API (LocalDate, ZonedDateTime, ...). Quan trọng — không có thì serialize
LocalDatera string ISO bị fail. - parameter-names: cần để Jackson detect tên parameter trong constructor (cho immutable record/class).
3.3 spring-boot-starter-tomcat — embedded server
spring-boot-starter-tomcat
├── tomcat-embed-core (servlet container)
├── tomcat-embed-el (EL parser cho JSP — hiem dung)
└── tomcat-embed-websocket (WebSocket support)
3 jar Tomcat đủ cho servlet container chạy embedded. Không có jar Tomcat full (không có tomcat-juli, tomcat-coyote-ajp, ...) — chỉ subset cần thiết.
3.4 spring-web + spring-webmvc — Spring MVC
spring-web (HTTP abstraction, message converter, multipart)
├── spring-beans
├── spring-core
└── jakarta.servlet-api
spring-webmvc (DispatcherServlet, @Controller, ViewResolver)
├── spring-aop
├── spring-context
├── spring-expression
└── (transitive cua spring-web)
spring-webmvc chứa DispatcherServlet, HandlerMapping, @RequestMapping annotation, MessageConverter infrastructure. Đây là module Module 03 sẽ đào sâu.
4. Starter ecosystem — bảng phổ biến
Boot có ~50 starter chính chủ + nhiều starter từ thirty-party. Bảng top 20 (nhớ thuộc):
| Starter | Pull theo |
|---|---|
spring-boot-starter-web | Spring MVC + Tomcat + Jackson + Validation |
spring-boot-starter-webflux | Spring WebFlux + Netty + Jackson |
spring-boot-starter-data-jpa | Spring Data JPA + Hibernate + JDBC + transaction |
spring-boot-starter-data-jdbc | Spring Data JDBC (lighter alternative to JPA) |
spring-boot-starter-data-redis | Spring Data Redis + Lettuce |
spring-boot-starter-data-mongodb | Spring Data MongoDB |
spring-boot-starter-data-elasticsearch | Spring Data Elasticsearch |
spring-boot-starter-jdbc | spring-jdbc + HikariCP |
spring-boot-starter-security | Spring Security |
spring-boot-starter-oauth2-client | OAuth2 client (login với Google/GitHub/...) |
spring-boot-starter-oauth2-resource-server | Validate JWT từ identity provider |
spring-boot-starter-test | JUnit 5 + Mockito + AssertJ + Spring Test |
spring-boot-starter-actuator | Health check, metrics, info, env, mappings, ... |
spring-boot-starter-validation | Hibernate Validator + Jakarta Validation |
spring-boot-starter-cache | Cache abstraction (cần thêm provider: Caffeine/Redis) |
spring-boot-starter-mail | JavaMail |
spring-boot-starter-thymeleaf | Thymeleaf template engine |
spring-boot-starter-amqp | RabbitMQ |
spring-boot-starter-batch | Spring Batch |
spring-boot-starter-quartz | Quartz scheduler |
3rd-party starter phổ biến: spring-cloud-starter-*, springdoc-openapi-starter-* (Swagger UI), org.springframework.ai:spring-ai-starter-* (Spring AI providers).
5. Excludes — tinh chỉnh transitive
Đôi khi bạn muốn starter-web nhưng đổi Tomcat sang Jetty:
<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>
Boot autoconfig detect Jetty trên classpath → tạo JettyServletWebServerFactory thay Tomcat. Không cần đổi code, chỉ đổi dependency.
Use case khác:
- Đổi Logback sang Log4j2: exclude
spring-boot-starter-logging, addspring-boot-starter-log4j2. - Bỏ Hibernate dùng EclipseLink: rare, exclude
hibernate-coretừstarter-data-jpa, add EclipseLink manually. - Giảm size jar: exclude transitive không dùng (vd
tomcat-embed-elnếu không dùng JSP).
6. Tự viết starter custom
Khi nào cần? Khi team có lib internal cần auto-config: vd acme-tracing-lib cấu hình tracing cho mọi service.
Cấu trúc starter custom:
acme-tracing-spring-boot-starter/ (rong, chi pull dep)
└── pom.xml
acme-tracing-spring-boot-autoconfigure/ (auto-config code)
├── src/main/java/com/acme/tracing/
│ ├── TracingAutoConfiguration.java
│ └── TracingProperties.java
└── src/main/resources/META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
AutoConfiguration.imports — file text liệt kê fully-qualified class name của autoconfig:
com.acme.tracing.TracingAutoConfiguration
Boot scan file này từ tất cả jar trong classpath, register class autoconfig.
TracingAutoConfiguration.java:
@AutoConfiguration
@ConditionalOnClass(Tracer.class)
@EnableConfigurationProperties(TracingProperties.class)
@ConditionalOnProperty(name = "acme.tracing.enabled", havingValue = "true", matchIfMissing = true)
public class TracingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public Tracer tracer(TracingProperties props) {
return Tracer.builder()
.endpoint(props.getEndpoint())
.serviceName(props.getServiceName())
.build();
}
}
@ConfigurationProperties(prefix = "acme.tracing")
public class TracingProperties {
private boolean enabled = true;
private String endpoint = "https://tracing.acme.internal";
private String serviceName;
// getters/setters
}
pom.xml của starter (rỗng class):
<dependencies>
<dependency>
<groupId>com.acme</groupId>
<artifactId>acme-tracing-spring-boot-autoconfigure</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.acme</groupId>
<artifactId>acme-tracing-core</artifactId> <!-- runtime lib -->
<version>1.0.0</version>
</dependency>
</dependencies>
Service team chỉ cần thêm:
<dependency>
<groupId>com.acme</groupId>
<artifactId>acme-tracing-spring-boot-starter</artifactId>
</dependency>
→ tracing tự động enabled. Đây là pattern Pivotal khuyến nghị cho enterprise standardization.
6.1 Naming convention
Spring khuyến nghị tách rõ:
- Official Spring Boot starter:
spring-boot-starter-X. - 3rd-party starter:
X-spring-boot-starter(X đứng đầu, vdspringdoc-openapi-starter-webmvc-ui).
Đảo namespace để tránh nhầm "đây là starter Pivotal" với "đây là community".
7. Vận hành production — supply chain security, CVE scan, SBOM
Starter pull 50-100 transitive deps → mỗi dep là potential CVE. Section này cover supply chain security.
7.1 CVE scanning — OWASP Dependency-Check
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>10.0.4</version>
<configuration>
<failBuildOnCVSS>7</failBuildOnCVSS>
<suppressionFile>dependency-check-suppressions.xml</suppressionFile>
</configuration>
</plugin>
CI run mvn dependency-check:check. Build fail nếu transitive dep có CVE high severity (CVSS vượt 7.0).
GitHub native: Dependabot alerts free cho repo public/private. Snyk paid cho enterprise dashboard.
7.2 SBOM — Software Bill of Materials
Boot 3.3+ generate SBOM tự động:
spring:
boot:
sbom:
enabled: true
Endpoint /actuator/sbom trả CycloneDX format. Audit, compliance (SOC2, ISO 27001).
7.3 Dependency upgrade strategy
Pattern enterprise:
- Renovate / Dependabot tạo PR auto với version bump.
- CI run full test → green → human review.
- Roll out 1 dep / sprint thay batch nhiều — easier rollback.
- Minor version bump (3.4.x → 3.4.y): patch CVE, ít risk.
- Major bump (3.x → 4.x): breaking change, plan 2-3 sprint.
7.4 Excludes — minimize attack surface
Exclude transitive lib không dùng:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<!-- Khong dung XML, exclude jackson-dataformat-xml giam attack surface -->
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
Mỗi dep removed = 1 ít CVE potential + 1 ít MB image size.
7.5 Supply chain attack — lessons
Vụ điển hình:
- Log4Shell (CVE-2021-44228, Dec 2021): Log4j2 RCE, mọi app Java affected. Boot upgrade 2.6.2/2.5.9 patch trong 48h.
- Spring4Shell (CVE-2022-22965, Mar 2022): Spring Framework RCE. Boot 2.5.12/2.6.6 patch.
Lesson: keep Boot version updated trong support window. Boot OSS support 12 tháng từ release minor.
| Version | Initial | OSS support end | Commercial support end |
|---|---|---|---|
| 3.3 | 2024-05 | 2025-05 | 2027-05 |
| 3.4 | 2024-11 | 2025-11 | 2027-11 |
| 3.5 | 2025-05 | 2026-05 | 2028-05 |
Plan upgrade theo timeline, không để app đi vào unsupported.
7.6 BOM hierarchy — nhiều BOM cùng project
Khi import nhiều BOM (Spring Boot + Spring Cloud + Testcontainers):
<dependencyManagement>
<dependencies>
<!-- Spring Cloud BOM TRUOC Spring Boot BOM -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2024.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
Maven import BOM theo thứ tự — BOM khai trước win cho lib chung. Spring Cloud build trên Spring Boot → phải BOM Cloud trước.
8. Pitfall tổng hợp
❌ Nhầm 1: Khai báo dep với <version> explicit trong Boot project.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version> <!-- override BOM, gay version mismatch toan ecosystem -->
</dependency>
✅ Bỏ <version> — để BOM resolve. Override starter version = override toàn bộ Boot ecosystem.
❌ Nhầm 2: Dùng dependencyManagement import nhiều BOM cùng lúc không có thứ tự.
<dependencyManagement>
<dependencies>
<dependency><artifactId>spring-cloud-dependencies</artifactId></dependency>
<dependency><artifactId>spring-boot-dependencies</artifactId></dependency>
</dependencies>
</dependencyManagement>
✅ Maven import BOM theo thứ tự — BOM khai trước thắng. Đặt Spring Cloud BOM trước Spring Boot BOM (Cloud build trên Boot — phải override lib chung). Quy tắc chính thức: parent BOM > child BOM.
❌ Nhầm 3: Tự pull spring-context, spring-web riêng lẻ thay vì starter.
<dependency><artifactId>spring-context</artifactId></dependency>
<dependency><artifactId>spring-web</artifactId></dependency>
<dependency><artifactId>spring-webmvc</artifactId></dependency>
✅ Dùng starter. Starter pull đủ, version khớp, transitive đầy đủ. Tự pull lẻ dễ miss bridge lib (vd Jackson datatype) → bug tinh tế.
❌ Nhầm 4: Không hiểu spring-boot-starter-X vs spring-boot-X vs spring-X.
spring-X(vdspring-context,spring-web): module Spring Framework, chỉ pull khi tự khai.spring-boot-X(vdspring-boot,spring-boot-autoconfigure): module Spring Boot core.spring-boot-starter-X(vdspring-boot-starter-web): meta-dep, pull theo cluster.
✅ Code business luôn dùng spring-boot-starter-X. spring-X và spring-boot-X chỉ pull trực tiếp khi viết library.
❌ Nhầm 5: Override version transitive không nhất quán.
<!-- spring-context override 6.0 nhung spring-web van 6.2 (mismatch) -->
<properties>
<spring-framework.version>6.0.0</spring-framework.version>
</properties>
✅ Override property spring-framework.version đúng cách → BOM tự apply cho cả spring-context, spring-web, spring-webmvc, ... Nếu cần override 1 lib cụ thể, dùng <dependencyManagement> explicit, đừng tinkering property.
❌ Nhầm 6: Cho rằng starter là "magic auto-config".
✅ Starter chỉ pull jar. Auto-configuration là mechanism riêng (bài 03 đào sâu) — chạy nhờ @EnableAutoConfiguration + file AutoConfiguration.imports. Phân biệt rõ.
9. 📚 Deep Dive Spring Reference
Spring Boot Reference docs:
- Spring Boot Reference — Build Systems — chi tiết Maven/Gradle setup, parent vs BOM import.
- Spring Boot Reference — Starters — danh sách official starter + thirty-party recommendation.
- Spring Boot Reference — Creating Your Own Auto-configuration — viết starter custom đầy đủ.
- Spring Boot Reference — Configuration Property Metadata — generate metadata cho IDE autocomplete trong custom starter.
File quan trọng cần biết:
spring-boot-dependenciesBOM source — file build.gradle (không pom.xml) định nghĩa version cho 200+ lib. Đọc 1 lần để thấy scope.spring-boot-starter-parentsource — Java version, plugin config defaults.
Bài viết & blog post:
- Stéphane Nicoll — Behind the scenes of Spring Boot — Boot maintainer giải thích design starter.
- Maven docs — Dependency Mediation — cách Maven resolve version conflict (nearest-wins).
Tool hữu ích:
- Spring Initializr — UI tạo project Boot. Click thấy mỗi starter pull theo gì.
mvn dependency:tree/gradle dependencies— debug dep transitive.- DepShield — scan vuln trong dep tree (security).
Ghi chú: đọc file pom.xml của 1-2 starter (mở jar starter từ Maven Central hoặc xem trên GitHub) — 5 phút thôi nhưng demystify hoàn toàn "starter là gì".
10. Tóm tắt
- Starter là jar rỗng class, chỉ chứa
pom.xmlliệt kê transitive dependencies. spring-boot-starter-webpull ~30 jar transitive: Tomcat embedded, Jackson, Spring MVC, validation, logging.- BOM
spring-boot-dependenciesquản version 200+ lib. Pivotal test mọi combination tương thích. - 2 cách extend BOM:
spring-boot-starter-parent(1 dòng, full inherit) hoặcimport scope(project có corporate parent). - Khai báo dep trong Boot project không có
<version>— BOM resolve. Override version = tự nhận trách nhiệm test compat. - Phân biệt 3 loại artifact:
spring-X(Framework module),spring-boot-X(Boot core),spring-boot-starter-X(meta-dep cluster). - 50+ official starter chính chủ + 3rd-party (Cloud, OpenAPI, AI). Top 20 nhớ thuộc.
- Excludes + add starter khác = tinh chỉnh stack (Tomcat → Jetty, Logback → Log4j2).
- Tự viết starter custom = pattern enterprise: tách
*-starter(rỗng) +*-autoconfigure(autoconfig code) + fileAutoConfiguration.imports. - Naming convention: official
spring-boot-starter-X, 3rd-partyX-spring-boot-starter(đảo namespace).
11. Tự kiểm tra
Q1Bạn mở jar spring-boot-starter-web-3.4.0.jar và ngạc nhiên thấy không có class nào. Vì sao? Mục đích của file jar đó là gì?▸
spring-boot-starter-web-3.4.0.jar và ngạc nhiên thấy không có class nào. Vì sao? Mục đích của file jar đó là gì?Vì starter chỉ là "meta-dependency" — file pom.xml đóng gói thành jar để Maven download và parse. Mục đích duy nhất: liệt kê transitive dep.
Jar starter chứa:
META-INF/maven/.../pom.xml— pom file định nghĩa transitive dep.META-INF/MANIFEST.MF— metadata.- Không class file.
Khi project khai báo spring-boot-starter-web:
- Maven download jar starter (~5KB, hầu như rỗng).
- Đọc
pom.xmlbên trong. - Recurse pull 5 transitive dep liệt kê.
- Mỗi transitive lại pull tiếp → cuối cùng có ~30 jar.
Verify: mvn dependency:tree in ra cả tree, hoặc jar tf spring-boot-starter-web-3.4.0.jar liệt kê content jar.
Bài học: starter không "tự làm gì". Nó là cú pháp đường ngắn để khai báo nhóm dep cùng lúc — Maven mechanic, không phải Spring magic.
Q2BOM spring-boot-dependencies khác spring-boot-starter-parent ở đâu? Khi nào dùng cái nào?▸
spring-boot-dependencies khác spring-boot-starter-parent ở đâu? Khi nào dùng cái nào?| Aspect | spring-boot-dependencies | spring-boot-starter-parent |
|---|---|---|
| Loại | BOM thuần (chỉ dependencyManagement) | Parent pom inherit BOM + thêm config |
| Quản version | Có (200+ lib) | Có (qua inherit BOM) |
| Java version default | Không | Có (17) |
| Encoding default | Không | Có (UTF-8) |
| Plugin defaults (Surefire, Failsafe) | Không | Có |
| Resource filtering | Không | Có (filter application.properties) |
| Cách dùng | scope=import type=pom | <parent> |
Khi dùng cái nào:
- Default —
spring-boot-starter-parent: 1 dòng setup full project. Dùng cho 90% case. spring-boot-dependencies(import BOM): khi project đã có parent khác (vd corporateacme-parentcho enterprise lib management). Bạn không thể có 2<parent>→ dùng import.
Cú pháp import BOM:
<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>Sau import, version từ BOM apply như parent. Phải config thủ công Java version, plugin nếu cần.
Q3Bạn cần Boot 3.4 nhưng dùng Hibernate 6.5 thay vì 6.6 (BOM default). Có nên override không? Nếu có, cách nào? Rủi ro gì?▸
Câu trả lời ngắn: nên cân nhắc kỹ trước khi override. Trong 90% case, upgrade Boot hoặc downgrade Boot để khớp Hibernate version mong muốn — không override lẻ.
Nếu phải override:
<properties>
<hibernate.version>6.5.0.Final</hibernate.version>
</properties>BOM dùng property ${hibernate.version} cho mọi entry liên quan Hibernate (hibernate-core, hibernate-validator, ...). Override property = đồng bộ tất cả module Hibernate.
Rủi ro:
- Compatibility issue: BOM 3.4 test với Hibernate 6.6.3. Hibernate 6.5 có thể có API khác (vd method removed/added) → compile fail hoặc runtime exception.
- Spring Data JPA mismatch: Spring Data 2024.x test với Hibernate 6.6. Override 6.5 có thể gây bug subtle (query DSL behavior khác).
- Pivotal không guarantee support: bug report bị reject "version mismatch — please use BOM default".
- Upgrade Boot future: Boot 3.5 BOM dùng Hibernate 6.7. Override property sẽ stale — không hiệu lực mà bạn không biết.
Cách tốt hơn:
- Tìm Boot version có Hibernate 6.5 (Boot 3.3.x?) → upgrade/downgrade Boot.
- Nếu Hibernate 6.5 có security CVE → upgrade lên 6.6 (BOM default) thay override xuống.
- Override chỉ khi: critical bug fix chưa có trong BOM, document rõ trong README, plan upgrade Boot khi BOM catch up.
Q4Đoạn sau có gì sai? Maven build sẽ làm gì?<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-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>Sai: exclude Tomcat starter nhưng không add embedded server thay thế (Jetty, Undertow, Netty).
Maven build:
- Pull
spring-boot-starter-webnhưng skipspring-boot-starter-tomcat. - Pull các dep còn lại: Jackson, Spring MVC, validation.
- Build success — Maven không kiểm tra "phải có embedded server".
Runtime fail: khi SpringApplication.run() chạy, Boot autoconfig detect classpath → không tìm thấy embedded server class (Tomcat/Jetty/Netty) → throw NoSuchBeanDefinitionException với message "No qualifying bean of type ServletWebServerFactory":
Error creating bean with name 'webServerFactoryCustomizerBeanPostProcessor':
Bean instantiation via factory method failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
... No qualifying bean of type 'ServletWebServerFactory' availableFix đúng — đổi sang Jetty:
<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> <!-- THEM -->
</dependency>Bài học: exclude phải đi kèm replacement. Nếu thực sự muốn không có server (vd convert sang CLI app), dùng spring-boot-starter trần (không suffix), không dùng spring-boot-starter-web.
Q5Team enterprise của bạn có lib internal acme-tracing-core. Mọi service dùng nó cần config tracer endpoint giống nhau. Bạn nên làm gì để standardize? Cấu trúc starter custom ra sao?▸
acme-tracing-core. Mọi service dùng nó cần config tracer endpoint giống nhau. Bạn nên làm gì để standardize? Cấu trúc starter custom ra sao?Giải pháp: tạo starter custom acme-tracing-spring-boot-starter.
Cấu trúc 2 module:
acme-tracing-spring-boot-starter/ (rong, chi pull dep)
└── pom.xml
└── depends on acme-tracing-spring-boot-autoconfigure + acme-tracing-core
acme-tracing-spring-boot-autoconfigure/ (autoconfig logic)
├── pom.xml
└── src/main/
├── java/com/acme/tracing/
│ ├── TracingAutoConfiguration.java
│ └── TracingProperties.java
└── resources/META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
(chua: com.acme.tracing.TracingAutoConfiguration)TracingAutoConfiguration.java:
@AutoConfiguration
@ConditionalOnClass(Tracer.class)
@EnableConfigurationProperties(TracingProperties.class)
@ConditionalOnProperty(name = "acme.tracing.enabled", havingValue = "true", matchIfMissing = true)
public class TracingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public Tracer tracer(TracingProperties props) {
return Tracer.builder()
.endpoint(props.getEndpoint())
.serviceName(props.getServiceName())
.build();
}
}
@ConfigurationProperties(prefix = "acme.tracing")
@Data
public class TracingProperties {
private boolean enabled = true;
private String endpoint = "https://tracing.acme.internal";
private String serviceName;
}Service team chỉ cần:
<!-- pom.xml -->
<dependency>
<groupId>com.acme</groupId>
<artifactId>acme-tracing-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
# application.yml
acme:
tracing:
service-name: order-serviceLợi ích so với "mỗi service tự config":
- Consistency: mọi service tracing đến cùng endpoint, format chuẩn.
- Upgrade: đổi default endpoint → bump starter version, mọi service rebuild lấy default mới.
- Override-friendly: service muốn endpoint khác chỉ set property, không sửa code.
- Conditional: service không có
acme-tracing-core→ autoconfig skip (không pull dep thì không có class →@ConditionalOnClassfalse). - Document via metadata: generate
spring-configuration-metadata.jsontừTracingProperties→ IDE autocomplete property cho dev.
Pattern này phổ biến trong enterprise — Spring Cloud, Spring AI, Springdoc đều dùng cấu trúc này.
Q6Bạn build 1 service Boot và build size jar 60MB — quá lớn cho deploy K8s/Lambda. 4 cách giảm size starter bring in: (a) exclude jar không dùng, (b) đổi sang spring-boot-starter trần (không web), (c) dùng GraalVM native image, (d) jlink JRE custom. Cái nào hiệu quả nhất, vì sao?▸
Phụ thuộc context, nhưng theo hiệu quả:
- (c) GraalVM native image — hiệu quả nhất cho size:
- Compile AOT thành executable native — không cần JVM, không cần JRE.
- App Boot 60MB jar → native image ~30-50MB executable. Container image tổng (distroless base) ~80-100MB.
- Cold start <100ms thay vì 2-5s JVM.
- Memory ~25MB idle thay vì ~150MB JVM.
- Boot 3+ support tốt:
spring-boot:build-imagetự build native image qua Buildpacks. - Trade-off: build chậm (~5 phút), 1 số lib reflection cần config
reflect-config.json, khó debug.
- (d) jlink JRE custom — efficient nếu không dùng native:
- JRE full ~250MB.
jlinktạo runtime chỉ chứa module cần (java.base + java.sql + ...). ~60-80MB. - Container size tổng app + runtime ~120-140MB.
- Dùng được với Boot không cần native compile — preserve full JVM features.
- Trade-off: phải biết chính xác module dùng. Spring Boot có tool
jdepshoặc Buildpacks tự pick.
- JRE full ~250MB.
- (a) Exclude jar không dùng — incremental, ít hiệu quả:
- Mỗi exclude tiết kiệm 1-5MB. Tổng 10 exclude tiết kiệm ~30MB từ 60MB → 30MB. Đáng làm nhưng không drastic.
- Common excludes:
tomcat-embed-el(nếu không JSP),spring-boot-starter-loggingrồi addspring-boot-starter-log4j2(nhỏ hơn nếu cần). - Trade-off: dễ break runtime (như câu trên — exclude Tomcat quên thay).
- (b) Đổi sang
spring-boot-startertrần — chỉ cho CLI app:- Bỏ web stack (~20MB). Phù hợp khi service thực sự không cần HTTP.
- Trade-off: không còn web. Chỉ dùng cho batch job, CLI tool.
Khuyến nghị thực tế 2026:
- Service standard → (c) native image cho deploy serverless/edge.
- Service yêu cầu full JVM (vd dùng JFR profiling, JMX) → (d) jlink custom JRE.
- Không thể native + jlink phức tạp → (a) exclude + chọn slim base image (Eclipse Temurin distroless).
Bài tiếp theo: Auto-configuration deep dive — bóc tách @EnableAutoConfiguration ở mức bytecode
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...