Máy Chủ Tomcat Nhúng Hoạt Động Như Thế Nào Trong Spring Boot? (Series Java Spring Roadmap)

Chào mừng các bạn quay trở lại với series “Java Spring Roadmap”! Trên hành trình khám phá Spring Framework, chúng ta đã đi qua nhiều cột mốc quan trọng như lý do chọn Spring, các thuật ngữ cốt lõi, kiến trúc, Dependency Injection, Spring IoC, Spring MVC, và gần đây là sức mạnh của Spring Boot StartersAutoconfiguration. Hôm nay, chúng ta sẽ tìm hiểu một tính năng đột phá, giúp đơn giản hóa đáng kể việc phát triển và triển khai ứng dụng web với Spring Boot: Máy chủ nhúng (Embedded Server), cụ thể là Tomcat nhúng mặc định.

Nếu bạn đã từng làm việc với các ứng dụng web Java truyền thống, hẳn không lạ gì quy trình đóng gói ứng dụng thành file .war và triển khai nó lên một máy chủ web (như Tomcat, Jetty, WebSphere, WebLogic…) đã được cài đặt sẵn. Quá trình này thường bao gồm các bước cấu hình máy chủ, quản lý phiên bản máy chủ, và đôi khi gặp phải vấn đề “hoạt động trên máy tôi nhưng không hoạt động trên máy chủ”. Spring Boot đã thay đổi hoàn toàn cục diện này, và máy chủ nhúng chính là trái tim của sự thay đổi đó.

Sự Khác Biệt: Từ WAR và Máy Chủ Riêng Lẻ Đến JAR Tự Chạy

Trước khi đi sâu vào cách hoạt động, hãy cùng nhìn lại sự khác biệt cơ bản:

  • Mô hình truyền thống:
    Ứng dụng của bạn là một file .war chứa mã nguồn, các thư viện, các file cấu hình web (web.xml nếu không dùng annotations), các trang JSP/HTML… File .war này được triển khai (“deploy”) lên một máy chủ web/ứng dụng độc lập đã được cài đặt trên hệ thống. Máy chủ này chịu trách nhiệm lắng nghe các yêu cầu HTTP đến, tìm đúng ứng dụng để xử lý, và quản lý vòng đời của các Servlet, Filter… Việc nâng cấp phiên bản máy chủ là một quá trình riêng biệt và có thể ảnh hưởng đến nhiều ứng dụng chạy trên đó.
  • Mô hình Spring Boot với Máy chủ nhúng:
    Ứng dụng của bạn được đóng gói thành một file .jar thực thi (executable JAR). File .jar này không chỉ chứa mã nguồn và các thư viện của ứng dụng, mà còn chứa cả thư viện của một máy chủ web (mặc định là Tomcat). Khi bạn chạy file .jar này (ví dụ: java -jar your-application.jar), Spring Boot sẽ khởi động JVM, sau đó khởi động bối cảnh Spring (Spring context), và cuối cùng là khởi động máy chủ web nhúng bên trong ứng dụng. Ứng dụng của bạn tự nó trở thành một máy chủ web có khả năng lắng nghe yêu cầu HTTP trực tiếp.

Sự thay đổi này mang lại nhiều lợi ích to lớn, mà chúng ta sẽ thảo luận chi tiết hơn sau.

Máy Chủ Nhúng Là Gì?

Đơn giản mà nói, máy chủ nhúng là một thư viện máy chủ web (như Tomcat, Jetty, Undertow) được đóng gói trực tiếp vào file .jar của ứng dụng. Thay vì dựa vào một máy chủ bên ngoài cung cấp môi trường runtime Servlet, ứng dụng Spring Boot tự mang theo môi trường đó.

Điều này giống như việc bạn không cần phụ thuộc vào một nhà hàng để ăn pizza nữa, mà bạn mua lò nướng, nguyên liệu và tự làm pizza ngay tại nhà. File .jar của bạn là ngôi nhà, còn thư viện Tomcat là cái lò nướng.

Tại Sao Spring Boot Chọn Tomcat Làm Mặc Định?

Tomcat là máy chủ Servlet container phổ biến nhất trong cộng đồng Java. Nó trưởng thành, ổn định, hiệu suất cao, có cộng đồng hỗ trợ lớn, và giấy phép Apache License cho phép sử dụng rộng rãi. Việc chọn Tomcat làm mặc định giúp Spring Boot dễ dàng tiếp cận và được chấp nhận bởi phần lớn các nhà phát triển Java đã quen thuộc với nó.

Tuy nhiên, điều quan trọng cần nhấn mạnh là Spring Boot không bị “khóa chặt” với Tomcat. Bạn có thể dễ dàng chuyển sang sử dụng Jetty hoặc Undertow bằng cách loại bỏ dependency của Tomcat và thêm dependency của máy chủ khác vào file cấu hình build (pom.xml hoặc build.gradle).

Cách Spring Boot Nhúng và Khởi Động Tomcat

Đây là phần “phép thuật” chính của Spring Boot, liên quan mật thiết đến hai khái niệm chúng ta đã học: Spring Boot StartersAutoconfiguration.

1. Vai trò của Starters

Khi bạn tạo một ứng dụng web Spring Boot, bạn thường thêm dependency spring-boot-starter-web vào file pom.xml (với Maven) hoặc build.gradle (với Gradle). Starters là các bộ dependency tiện lợi giúp bạn nhanh chóng thêm các chức năng cần thiết. spring-boot-starter-web mang theo các dependency sau (đơn giản hóa):

  • spring-boot-starter (Starter cốt lõi, bao gồm logging, YAML…)
  • spring-web (Module Spring MVC web support)
  • spring-webmvc (Module Spring MVC framework)
  • Validation libraries
  • Và quan trọng nhất: spring-boot-starter-tomcat (dependency mặc định cho máy chủ Tomcat nhúng)

Chính dependency spring-boot-starter-tomcat này sẽ kéo theo thư viện lõi của Tomcat (như tomcat-core, tomcat-embed-websocket, v.v.) vào classpath của ứng dụng.

2. Vai trò của Autoconfiguration

Khi ứng dụng Spring Boot khởi động (qua phương thức SpringApplication.run()), quá trình autoconfiguration sẽ diễn ra. Autoconfiguration kiểm tra classpath và môi trường cấu hình của ứng dụng để tự động cấu hình các thành phần Spring dựa trên những gì nó tìm thấy.

Cụ thể, Spring Boot Autoconfiguration bao gồm một class cấu hình tự động tên là ServletWebServerFactoryAutoConfiguration (hoặc tương tự trong các phiên bản mới hơn, nằm trong module spring-boot-autoconfigure). Class này có điều kiện kích hoạt (@Conditional annotation) dựa trên sự có mặt của các class liên quan đến Servlet và một implementation của WebServerFactory trên classpath.

spring-boot-starter-tomcat đã kéo thư viện Tomcat vào, Spring Boot sẽ tìm thấy các class của Tomcat và các implementation của WebServerFactory dành cho Tomcat (ví dụ: TomcatServletWebServerFactory).

3. Quá trình Khởi động Máy chủ

Quá trình khởi động máy chủ nhúng diễn ra trong phương thức run() của SpringApplication:

  1. SpringApplication.run() bắt đầu khởi tạo bối cảnh Spring (IoC container).
  2. Quá trình autoconfiguration được kích hoạt.
  3. ServletWebServerFactoryAutoConfiguration nhận thấy sự có mặt của Tomcat trên classpath và các điều kiện khác được thỏa mãn.
  4. Nó tự động tạo ra một bean kiểu TomcatServletWebServerFactory (hoặc một implementation của WebServerFactory phù hợp). Bean factory này biết cách tạo ra một instance của Tomcat server.
  5. Spring Boot sử dụng bean factory này để tạo và cấu hình instance của máy chủ Tomcat.
  6. Spring Boot đăng ký DispatcherServlet (trái tim của Spring MVC, xử lý các request web) với máy chủ Tomcat nhúng.
  7. Máy chủ Tomcat được khởi động. Nó bắt đầu lắng nghe các kết nối HTTP trên cổng mặc định (thường là 8080).
  8. Ứng dụng của bạn đã sẵn sàng nhận và xử lý các yêu cầu web.
public static void main(String[] args) {
    SpringApplication.run(YourSpringBootApplication.class, args);
}

Chính dòng code SpringApplication.run(...) đơn giản này đã làm tất cả công việc phức tạp phía sau để khởi động cả bối cảnh Spring và máy chủ web nhúng.

Cấu Hình Máy Chủ Tomcat Nhúng

Mặc dù Spring Boot cung cấp cấu hình mặc định rất tốt, bạn hoàn toàn có thể tùy chỉnh máy chủ nhúng thông qua file cấu hình application.properties hoặc application.yml. Đây là cách tiếp cận phổ biến nhất và dễ dàng nhất.

Một số cấu hình phổ biến:

  • Thay đổi cổng (Port):
# application.properties
server.port=8081
# application.yml
server:
  port: 8081
  • Thay đổi Context Path:
# application.properties
server.servlet.context-path=/my-app
# application.yml
server.servlet:
  context-path: /my-app

Với cấu hình trên, ứng dụng sẽ lắng nghe ở http://localhost:8080/my-app thay vì http://localhost:8080/ (với cổng mặc định).

  • Cấu hình SSL:
# application.properties
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=password
server.ssl.keyStoreType=PKCS12
server.ssl.keyAlias=tomcat

Còn rất nhiều tùy chọn cấu hình khác cho Tomcat nhúng mà bạn có thể tìm thấy trong tài liệu chính thức của Spring Boot (ví dụ: cấu hình connection pool, compression, các tùy chọn cho request/response headers…). Spring Boot ánh xạ hầu hết các tùy chọn cấu hình phổ biến của Tomcat sang các thuộc tính trong file application.properties/.yml dưới prefix server..

Ngoài cách cấu hình dựa trên thuộc tính, bạn cũng có thể cấu hình nâng cao hơn bằng cách tạo bean customizer cho WebServerFactory (ví dụ: TomcatServletWebServerFactory) và lập trình cấu hình nó. Tuy nhiên, với hầu hết các trường hợp, cấu hình qua properties là đủ.

Chuyển Sang Máy Chủ Khác (Jetty hoặc Undertow)

Nếu bạn muốn sử dụng Jetty hoặc Undertow thay vì Tomcat, quá trình rất đơn giản nhờ cơ chế của Spring Boot Starters và Autoconfiguration. Bạn chỉ cần loại bỏ dependency của Tomcat starter và thêm dependency của starter tương ứng:

Ví dụ với Maven (pom.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>

<!-- Thêm Jetty hoặc Undertow -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

<!-- Hoặc Undertow -->
<!--
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
-->

Khi bạn build lại ứng dụng, Spring Boot sẽ tự động phát hiện sự có mặt của Jetty hoặc Undertow starter trên classpath và autoconfigure máy chủ tương ứng thay vì Tomcat.

Lợi Ích của Máy Chủ Nhúng

Việc sử dụng máy chủ nhúng, đặc biệt là Tomcat nhúng mặc định trong Spring Boot, mang lại nhiều lợi ích thiết thực:

  • Đơn giản hóa việc đóng gói và triển khai: Chỉ cần một file .jar duy nhất có thể chạy mọi nơi có JVM. Không cần cài đặt và cấu hình máy chủ web riêng biệt.
  • Tính di động cao: Ứng dụng là một đơn vị độc lập, dễ dàng sao chép và chạy trên các môi trường khác nhau (máy phát triển, staging, production).
  • Phát triển nhanh hơn: Thời gian build và chạy ứng dụng thường nhanh hơn so với việc tạo file WAR và triển khai thủ công. Việc hot-swapping code cũng hiệu quả hơn trong môi trường phát triển.
  • Quản lý dependency dễ dàng: Phiên bản của máy chủ web được quản lý như một dependency trong file build, nhất quán với các thư viện khác của ứng dụng.
  • Tự động cấu hình: Spring Boot autoconfigure phần lớn các thiết lập mặc định, giảm thiểu công sức cấu hình ban đầu.
  • Phù hợp với Microservices: Mỗi microservice có thể là một ứng dụng Spring Boot tự chạy với máy chủ nhúng riêng, giúp cách ly và quản lý độc lập.
  • Tích hợp tốt với Cloud: Dễ dàng đóng gói ứng dụng thành Docker container hoặc triển khai lên các nền tảng Cloud (như Cloud Foundry, Heroku, AWS Elastic Beanstalk…) vốn ưu tiên các ứng dụng self-contained.

Máy chủ nhúng vs. Triển khai WAR truyền thống: Bảng So Sánh

Để thấy rõ hơn sự khác biệt, hãy xem bảng so sánh sau:

Đặc điểm Mô hình truyền thống (WAR + Máy chủ riêng) Mô hình Spring Boot (JAR + Máy chủ nhúng)
Đóng gói File .war File .jar thực thi
Triển khai Copy file .war vào thư mục deploy của máy chủ đã cài đặt Chạy file .jar (java -jar ...)
Yêu cầu môi trường Cần cài đặt và cấu hình máy chủ web/ứng dụng riêng biệt Chỉ cần JVM
Quản lý phiên bản máy chủ Riêng biệt với ứng dụng, có thể ảnh hưởng đến nhiều ứng dụng Là dependency của ứng dụng, quản lý độc lập cho từng ứng dụng
Tính di động Thấp hơn, phụ thuộc vào cấu hình máy chủ Cao, ứng dụng self-contained
Độ phức tạp ban đầu Cao hơn (cài đặt, cấu hình máy chủ) Thấp hơn (chỉ cần thêm starter)
Phù hợp với Microservices Kém phù hợp Rất phù hợp
Tích hợp Cloud/Container Cần môi trường runtime riêng Tự đóng gói runtime, dễ dàng đóng gói vào container
Tùy biến máy chủ Làm việc với cấu hình máy chủ bên ngoài Cấu hình qua file properties hoặc lập trình trong ứng dụng

Mặc dù mô hình JAR + máy chủ nhúng là phổ biến và là mặc định của Spring Boot, bạn vẫn có thể đóng gói ứng dụng Spring Boot thành file WAR và triển khai lên máy chủ ngoài nếu có lý do cụ thể (ví dụ: yêu cầu của môi trường doanh nghiệp, tích hợp với các dịch vụ quản lý máy chủ tập trung…). Để làm điều này, bạn cần thêm dependency spring-boot-starter-tomcat với scope provided và extends class SpringBootServletInitializer trong class main của ứng dụng.

Kết Luận

Máy chủ Tomcat nhúng (và các máy chủ nhúng khác) là một trong những tính năng cốt lõi làm nên sự thành công của Spring Boot. Nó thay đổi cách chúng ta nghĩ về việc đóng gói và triển khai ứng dụng web Java, chuyển từ mô hình “deploy lên server” sang “run the application”. Bằng cách tự động nhúng và cấu hình máy chủ, Spring Boot giảm đáng kể gánh nặng cấu hình, giúp nhà phát triển tập trung vào logic nghiệp vụ và tăng tốc độ phát triển.

Việc hiểu rõ cách Spring Boot sử dụng Starters và Autoconfiguration để kích hoạt và cấu hình máy chủ nhúng là một bước quan trọng trên lộ trình Java Spring Boot của bạn. Nó giải thích tại sao ứng dụng Spring Boot của bạn có thể chạy ngay lập tức và lắng nghe các yêu cầu HTTP chỉ với vài dòng code.

Trong các bài viết tiếp theo của series “Java Spring Roadmap”, chúng ta sẽ tiếp tục khám phá các khía cạnh khác của Spring Boot, giúp bạn ngày càng làm chủ framework mạnh mẽ này.

Bạn có bất kỳ câu hỏi nào về máy chủ nhúng trong Spring Boot không? Hay bạn đã có trải nghiệm thực tế nào khi chuyển từ mô hình WAR sang JAR tự chạy? Hãy chia sẻ ở phần bình luận bên dưới nhé!

Chỉ mục