Spring Core & Boot
28/41
Bài 28 / 41~12 phútSpring Boot & Auto-configurationLộ trình · chặng 08/21Miễn phí… lượt xem

Starter & BOM: Quản Lý Dependency Đồng Bộ Trong Maven

Tìm hiểu cấu trúc jar rỗng của Starter, vai trò của BOM spring-boot-dependencies và cơ chế dependencyManagement ghi đè giải thuật nearest-wins.

TL;DR: Starter là một tệp JAR hoàn toàn không chứa file bytecode .class — bên trong chỉ có tệp pom.xml liệt kê các transitive dependencies cần thiết cho một tính năng. BOM (spring-boot-dependencies) là một POM file chứa thẻ <dependencyManagement> xác định phiên bản tương thích đã được kiểm thử cho hơn 200 thư viện phổ biến. Cơ chế <dependencyManagement> của BOM ghi đè hoàn toàn giải thuật Nearest-Wins của Maven, xóa bỏ triệt để xung đột phiên bản mà không cần khai báo <version> thủ công.

Nắm vững nguyên lý hoạt động của Starter và BOM giúp bạn tự tin quản lý dependency, hoán đổi linh hoạt các module nhúng và tự tạo starter nội bộ cho doanh nghiệp.

1. Nạn Dependency Hell của Spring 4 từng gây khó khăn thế nào?

Trước khi có Spring Boot, việc bổ sung tính năng Web MVC đòi hỏi lập trình viên phải tự chọn và khai báo từng thư viện riêng rẽ:

XML
<!-- Thoi ky Spring 4: Phai tu chon va doi chieu tung version -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

Vấn đề nan giải nhất là tính tương thích: liệu jackson-databind 2.9.0 có chạy ổn định với spring-webmvc 4.3.9 không? Mỗi lần nâng cấp phiên bản thư viện là một lần đối mặt với nguy cơ lỗi NoSuchMethodError hay ClassNotFoundException lúc runtime.

2. Bản chất vật lý của một gói Spring Boot Starter là gì?

Nếu bạn tải tệp spring-boot-starter-web-3.4.0.jar từ Maven Central và giải nén, bạn sẽ thấy một cấu trúc bất ngờ:

Plain Text
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 duy nhat co y nghia

Gói JAR này hoàn toàn không chứa bất kỳ file .class nào. Nó chỉ đóng vai trò một bản kê khai các thư viện cần thiết:

XML
<project>
    <artifactId>spring-boot-starter-web</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
    </dependencies>
</project>

Tất cả các thẻ dependency bên trong starter đều không khai báo <version>. Mục đích duy nhất của nó là kéo theo khoảng 30 thư viện liên quan chỉ với một dòng khai báo duy nhất.

3. BOM giải quyết xung đột phiên bản thông qua cơ chế nào?

Các phiên bản thư viện thực chất được định nghĩa trong BOM (Bill of Materials) mang tên spring-boot-dependencies. BOM là một file POM chỉ chứa thẻ <dependencyManagement>:

XML
<!-- spring-boot-dependencies (POM thuan) -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>5.1.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>

Hai cách tích hợp BOM vào dự án:

  1. Sử dụng parent (Khuyến nghị cho ứng dụng độc lập):
    XML
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.0</version>
    </parent>
    
  2. Sử dụng scope import (Bắt buộc khi dự án đã kế thừa corporate parent khác):
    XML
    <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>
    

4. Bóc tách spring-boot-starter-web gồm những thành phần gì?

Một khai báo spring-boot-starter-web sẽ tự động kéo theo 4 cụm thư viện chính:

Cụm thư việnCác tệp JAR tiêu biểuTrách nhiệm đảm nhận
spring-boot-starterspring-boot, autoconfigure, logback, snakeyamlĐộng cơ khởi động, auto-config, logging và YAML parser
spring-boot-starter-jsonjackson-databind, jackson-datatype-jsr310Tuần tự hóa JSON, hỗ trợ kiểu thời gian Java 8 Date/Time
spring-boot-starter-tomcattomcat-embed-core, tomcat-embed-websocketMáy chủ Servlet nhúng trực tiếp trong ứng dụng
spring-webmvcspring-web, spring-webmvc, spring-aopDispatcherServlet, Controller và các bộ chuyển đổi bản tin

5. Làm thế nào để loại trừ và hoán đổi máy chủ nhúng qua Exclusions?

Nhờ cấu trúc transitive, bạn có thể dễ dàng thay đổi máy chủ web mà không cần sửa đổi bất kỳ dòng mã Java nào:

XML
<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>

Spring Boot sẽ phát hiện jetty-server trên classpath và tự động khởi tạo JettyServletWebServerFactory thay cho Tomcat.

Loại trừ phải luôn đi kèm giải pháp thay thế

Nếu bạn loại trừ Tomcat mà quên khai báo một server nhúng thay thế (như Jetty hoặc Undertow), dự án vẫn build Maven thành công nhưng sẽ sập ngay lúc khởi động với lỗi NoSuchBeanDefinitionException vì thiếu ServletWebServerFactory.

6. Tại sao BOM lại đánh bại được giải thuật Nearest-Wins của Maven?

Trong Maven thông thường, nếu dependency A đòi jackson 2.14 và dependency B đòi jackson 2.18, Maven áp dụng giải thuật Nearest-Wins (ưu tiên dependency nằm ở tầng nông hơn trong cây phân cấp), bất chấp tính tương thích:

Cùng một conflict jackson 2.14 với 2.18 cho hai kết quả: nhánh không có BOM thì Maven so độ sâu trong cây theo nearest-wins, nhánh nào gần root hơn thì thắng, ra 2.14 là tổ hợp chưa ai test; nhánh có BOM thì Maven đọc dependencyManagement trước khi xét cây, BOM đã khai jackson 2.18.1 nên ra đúng tổ hợp Pivotal đã test với spring-webmvc 6.2

BOM giải quyết triệt để rủi ro này vì Maven luôn duyệt qua thẻ <dependencyManagement> trước khi phân giải cây phụ thuộc. Phiên bản được định nghĩa trong BOM sẽ luôn chiến thắng và khóa cố định phiên bản cho toàn bộ dự án.

7. Những cạm bẫy phiên bản phổ biến nhất khi sử dụng Starter?

  • Tự ý ghi đè version của Starter: Khai báo <version> thủ công trên các starter của Spring Boot sẽ phá vỡ tính đồng bộ của BOM và dẫn đến lỗi không tương thích runtime.
  • Nhầm lẫn giữa Starter và Auto-configuration: Starter chỉ là cơ chế đóng gói Maven để kéo file JAR. Còn Auto-configuration là mã nguồn Java bên trong spring-boot-autoconfigure thực hiện việc đăng ký bean vào container.

Tóm tắt

  • Spring Boot Starter là các tệp JAR rỗng không chứa class, chỉ dùng để liệt kê transitive dependencies theo cụm tính năng.
  • BOM (spring-boot-dependencies) định nghĩa phiên bản an toàn cho hơn 200 thư viện qua <dependencyManagement>.
  • Cơ chế <dependencyManagement> của BOM luôn ghi đè giải thuật Nearest-Wins của Maven để đảm bảo tính đồng nhất.
  • Khi dự án đã có corporate parent, hãy tích hợp BOM bằng thẻ <dependency> với type=pom và scope=import.
  • Kỹ thuật Exclusions cho phép hoán đổi các thành phần hạ tầng (như Tomcat sang Jetty) hoàn toàn thông qua cấu hình Maven.

Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Bản chất kỹ thuật bên trong của một file Spring Boot Starter JAR (như spring-boot-starter-web) là gì?
  2. Q2
    Cơ chế nào giúp BOM (spring-boot-dependencies) ngăn chặn lỗi xung đột phiên bản giữa các thư viện?
  3. Q3
    Trong trường hợp dự án công ty bắt buộc phải kế thừa acme-parent, làm thế nào để bạn sử dụng được BOM của Spring Boot?
  4. Q4
    Điều gì xảy ra khi bạn exclude starter-tomcat khỏi starter-web nhưng lại quên khai báo starter-jetty hoặc starter-undertow thay thế?
  5. Q5
    Tại sao lập trình viên KHÔNG nên khai báo thẻ version cho các starter của Spring Boot trong file pom.xml?

Bài tiếp theo: Tự Viết Custom Starter Chuẩn Production

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

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Lộ trình Junior Java Backend Developer

Chặng 8/21 · Maven / Gradle — build & dependency management — bài 1/2 của chặng

Ôn phỏng vấn

Bài này trả lời được các câu phỏng vấn sau — tự trả lời thử trước khi mở đáp án.

Thảo luận & góp ý

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

Bài tiếp theo

Tự Viết Custom Starter: Cấu Trúc 2 Module Và Supply Chain