Chào mừng trở lại với loạt bài Lộ trình Java Spring! Nếu bạn đã theo dõi từ đầu, bạn đã đi từ việc hiểu bức tranh tổng quan về Spring Boot và lý do Spring là framework hàng đầu, thông qua các thuật ngữ cốt lõi, cách Spring hoạt động bên dưới, làm chủ cấu hình với XML và annotations, đi sâu vào Dependency Injection (DI) và IoC trong thực tế, và thậm chí chạm đến Lập trình Hướng Khía cạnh (AOP).
Bây giờ, đã đến lúc áp dụng những khái niệm nền tảng đó để xây dựng một thứ gì đó cụ thể: một ứng dụng web. Và công cụ chính cho việc đó trong hệ sinh thái Spring chính là Spring MVC.
Đối với nhiều nhà phát triển, đặc biệt là những người mới bắt đầu, lớp web có vẻ đáng sợ. Các yêu cầu, phản hồi, bộ điều khiển, chế độ xem, mẫu… tất cả phù hợp ở đâu? Spring MVC cung cấp một cách có cấu trúc và thanh lịch để xử lý sự phức tạp này. Trong bài viết này, chúng ta sẽ giải mã Spring MVC, hiểu các nguyên tắc cốt lõi của nó và từng bước xây dựng ứng dụng web đầu tiên của bạn với nó.
Mục lục
Spring MVC là gì?
Ở cốt lõi, Spring MVC là một mô-đun trong Spring Framework lớn hơn giúp bạn xây dựng các ứng dụng web bằng cách sử dụng mẫu thiết kế Model-View-Controller (MVC). Đây là một framework mạnh mẽ cung cấp sự phân tách rõ ràng các mối quan tâm, giúp ứng dụng web của bạn dễ phát triển, hiểu và bảo trì hơn.
Hãy nhanh chóng tóm tắt lại mẫu MVC:
- Model: Đại diện cho dữ liệu và logic nghiệp vụ của ứng dụng. Nó độc lập với giao diện người dùng. Nó có thể là một POJO (Plain Old Java Object) đơn giản hoặc một cấu trúc phức tạp đại diện cho các thực thể cơ sở dữ liệu hoặc dữ liệu nghiệp vụ.
- View: Chịu trách nhiệm trình bày dữ liệu từ Model cho người dùng. Đây là lớp giao diện người dùng. Đây có thể là một trang HTML được tạo bởi công cụ mẫu, phản hồi JSON, v.v.
- Controller: Hoạt động như một trung gian giữa Model và View. Nó nhận đầu vào từ người dùng (các yêu cầu), xử lý chúng (thường sử dụng Model) và quyết định View nào sẽ hiển thị cho người dùng.
Tại sao sử dụng MVC? Nó thúc đẩy sự phân tách các mối quan tâm, là nền tảng của thiết kế phần mềm tốt. Sự phân tách này dẫn đến:
- Khả năng bảo trì: Các thay đổi đối với phần trình bày (View) không nhất thiết ảnh hưởng đến logic nghiệp vụ (Model) hoặc cách xử lý các yêu cầu (Controller).
- Khả năng kiểm thử: Bạn có thể kiểm tra logic Model và Controller độc lập với View.
- Tổ chức: Codebase được cấu trúc một cách hợp lý dựa trên trách nhiệm.
- Cộng tác: Các thành viên khác nhau trong nhóm có thể làm việc trên các thành phần khác nhau (ví dụ: nhà phát triển frontend trên Views, nhà phát triển backend trên Controllers/Models) với ít xung đột hơn.
Spring MVC cung cấp các công cụ và cơ sở hạ tầng để dễ dàng triển khai mẫu này trong môi trường Java.
Kiến trúc Spring MVC: Theo dòng yêu cầu
Hiểu cách một yêu cầu chảy qua Spring MVC là rất quan trọng. Đây là nơi nhiều phần kết hợp với nhau, được điều phối bởi một servlet trung tâm do Spring cung cấp.
Phần trung tâm của kiến trúc Spring MVC là DispatcherServlet
. Đây là một bộ điều khiển phía trước, một mẫu thiết kế ứng dụng web phổ biến trong đó một servlet duy nhất xử lý tất cả các yêu cầu đến. Hãy coi nó như cảnh sát giao thông chính cho ứng dụng web của bạn.
Dưới đây là luồng đơn giản của một yêu cầu trong Spring MVC:
- Yêu cầu đến: Một máy khách (như trình duyệt web) gửi yêu cầu HTTP đến máy chủ web.
- DispatcherServlet chặn: Máy chủ web chuyển tiếp yêu cầu đến
DispatcherServlet
.DispatcherServlet
được cấu hình (thường thông quaweb.xml
trong các triển khai WAR truyền thống hoặc tự động cấu hình bởi Spring Boot) để xử lý các mẫu URL cụ thể. - Ánh xạ trình xử lý:
DispatcherServlet
cần tìm ra bộ điều khiển và phương thức nào chịu trách nhiệm xử lý URL yêu cầu cụ thể này. Nó tham khảo một hoặc nhiều đối tượngHandlerMapping
. Các ánh xạ này thường dựa trên các chú thích như@RequestMapping
,@GetMapping
,@PostMapping
trên các lớp và phương thức điều khiển của bạn. - Thực thi bộ điều khiển:
HandlerMapping
được chọn trả về mộtHandlerExecutionChain
(bao gồm Bộ điều khiển/Trình xử lý và bất kỳ bộ chặn liên quan nào).DispatcherServlet
sau đó chuyển yêu cầu đến phương thức Controller thích hợp. - Xử lý (Controller, Service, Repository): Phương thức Controller xử lý yêu cầu. Điều này thường liên quan đến việc tương tác với logic nghiệp vụ (ví dụ: gọi các phương thức trong lớp Dịch vụ) và có thể là logic truy cập dữ liệu (ví dụ: sử dụng Kho lưu trữ để tương tác với cơ sở dữ liệu). Đây là nơi dữ liệu ‘Model’ thường được chuẩn bị hoặc truy xuất. Bộ điều khiển sẽ chuẩn bị dữ liệu cần thiết cho chế độ xem và có thể thêm nó vào một đối tượng
Model
. - Giải quyết tên chế độ xem: Phương thức Controller trả về tên chế độ xem logic (ví dụ: “home”, “user/details”). Nó không trực tiếp trả về HTML.
DispatcherServlet
nhận tên chế độ xem logic này. - Giải quyết chế độ xem:
DispatcherServlet
tham khảoViewResolver
.ViewResolver
ánh xạ tên chế độ xem logic được trả về bởi bộ điều khiển đến triển khai View thực tế (ví dụ: tệp JSP, mẫu Thymeleaf, v.v.). - Kết xuất chế độ xem: Chế độ xem được chọn được hướng dẫn để kết xuất phản hồi. Nó sử dụng dữ liệu Model được chuẩn bị bởi Bộ điều khiển để tạo đầu ra cuối cùng (ví dụ: HTML).
- Phản hồi được gửi: Phản hồi được tạo (ví dụ: trang HTML) được gửi lại thông qua
DispatcherServlet
đến trình duyệt của máy khách.
Luồng này làm nổi bật tính mô-đun. Mỗi thành phần có một vai trò cụ thể, làm cho hệ thống linh hoạt và có thể mở rộng.
Thành phần | Vai trò | Triển khai/Chú thích điển hình |
---|---|---|
DispatcherServlet |
Bộ điều khiển phía trước; nhận tất cả các yêu cầu và điều phối quá trình xử lý. | Được cấu hình trong web.xml hoặc tự động cấu hình bởi Spring Boot. |
HandlerMapping |
Ánh xạ các yêu cầu đến đến các phương thức Controller thích hợp. | RequestMappingHandlerMapping (cho các chú thích như @RequestMapping ). |
Controller |
Xử lý các yêu cầu đến, xử lý đầu vào, tương tác với Model, chọn View. | Các lớp được chú thích bằng @Controller hoặc @RestController . |
Service |
Lớp logic nghiệp vụ (thường được sử dụng bởi Controllers). | Các lớp được chú thích bằng @Service . |
Repository |
Lớp truy cập dữ liệu (thường được sử dụng bởi Dịch vụ hoặc Bộ điều khiển). | Các lớp được chú thích bằng @Repository . |
ViewResolver |
Ánh xạ tên chế độ xem logic được trả về bởi Bộ điều khiển đến các triển khai View thực tế. | InternalResourceViewResolver (cho JSP), ThymeleafViewResolver , v.v. |
View |
Chịu trách nhiệm kết xuất phản hồi bằng cách sử dụng dữ liệu Model. | Tệp JSP, mẫu Thymeleaf, mẫu Freemarker, v.v. |
Thiết lập dự án Spring MVC đầu tiên của bạn
Mặc dù bạn *có thể* thiết lập Spring MVC theo cách thủ công trong một dự án WAR truyền thống, cách dễ nhất hiện nay chắc chắn là sử dụng Spring Boot. Spring Boot đơn giản hóa đáng kể cấu hình bằng cách cung cấp cấu hình tự động cho hầu hết các kịch bản phổ biến, bao gồm cả Spring MVC.
Nếu bạn chưa làm như vậy, hãy xem lại bài viết Lộ trình Spring Boot để hiểu cách Boot đơn giản hóa quá trình phát triển. Đối với một ứng dụng web, bạn thường cần phụ thuộc spring-boot-starter-web
. Starter này mang đến Spring MVC, một máy chủ web nhúng (như Tomcat) và các thành phần cần thiết khác.
Sử dụng Maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Chúng tôi cũng đã thêm spring-boot-starter-thymeleaf
vì Thymeleaf là một công cụ mẫu phổ biến và được khuyến nghị cho Spring Boot, giúp dễ dàng tạo Views của chúng tôi.
Cấu trúc dự án của bạn thường sẽ bao gồm:
src/main/java
: Cho mã Java của bạn (Controllers, Services, v.v.)src/main/resources
: Cho các tệp cấu hình (nhưapplication.properties
hoặcapplication.yml
) và tài nguyên tĩnh.src/main/resources/templates
: Theo quy ước, các mẫu Thymeleaf được đặt ở đây.src/main/resources/static
: Cho các tài nguyên tĩnh như CSS, JavaScript, hình ảnh.
Lớp ứng dụng chính của bạn sẽ trông giống như thế này (ứng dụng Spring Boot tiêu chuẩn):
package com.example.mywebapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyWebAppApplication {
public static void main(String[] args) {
SpringApplication.run(MyWebAppApplication.class, args);
}
}
Chú thích @SpringBootApplication
là một tiện ích bao gồm @EnableAutoConfiguration
, sẽ cấu hình Spring MVC cho bạn khi nó phát hiện phụ thuộc spring-webmvc
trên classpath.
Xây dựng bộ điều khiển đầu tiên của bạn
Bộ điều khiển là các điểm vào để xử lý các yêu cầu web. Chúng thường là các POJO đơn giản được chú thích bằng @Controller
. Bên trong một bộ điều khiển, bạn định nghĩa các phương thức xử lý các URL và phương thức HTTP cụ thể (GET, POST, v.v.) bằng cách sử dụng các chú thích như @RequestMapping
, @GetMapping
, @PostMapping
, v.v.
Hãy tạo một bộ điều khiển đơn giản để xử lý yêu cầu đến URL gốc (“/”) và hiển thị trang chào mừng.
package com.example.mywebapp.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller // Đánh dấu lớp này là một Controller
public class HomeController {
@GetMapping("/") // Ánh xạ các yêu cầu HTTP GET đến URL gốc ("/") đến phương thức này
public String homePage(Model model) {
// Thêm dữ liệu vào model để sử dụng bởi view
model.addAttribute("message", "Chào mừng đến với ứng dụng Spring MVC đầu tiên của chúng tôi!");
// Trả về tên logic của view
return "index"; // Điều này sẽ giải quyết thành src/main/resources/templates/index.html (với Thymeleaf)
}
}
Hãy phân tích điều này:
@Controller
: Nói với Spring rằng lớp này là một bộ điều khiển web và nên được quét để tìm các phương thức xử lý yêu cầu. Spring sẽ quản lý bộ điều khiển này như một Bean trong vùng chứa IoC của nó.@GetMapping("/")
: Đây là một chú thích tổng hợp (một phím tắt cho@RequestMapping(value="/", method=RequestMethod.GET)
). Nó ánh xạ các yêu cầu HTTP GET đến đường dẫn gốc “/” đến phương thứchomePage()
. Spring MVC sử dụngHandlerMapping
để tìm phương thức này khi một yêu cầu cho “/” đến.homePage(Model model)
: Đây là phương thức xử lý yêu cầu. Nó nhận một đối tượngModel
làm tham số. Spring MVC tự động tiêm đối tượng này (nhờ DI!). Đối tượngModel
được sử dụng để truyền dữ liệu từ bộ điều khiển đến chế độ xem.model.addAttribute("message", "...")
: Chúng tôi thêm một cặp khóa-giá trị vào Model. View sẽ có thể truy cập dữ liệu này bằng khóa “message”.return "index";
: Phương thức trả về mộtString
, là tên chế độ xem logic.ViewResolver
của Spring MVC sẽ sử dụng tên này để tìm tệp mẫu view thực tế. Với Thymeleaf và các mặc định của Spring Boot, “index” thường sẽ ánh xạ đếnsrc/main/resources/templates/index.html
.
Tạo View (với Thymeleaf)
Bây giờ chúng ta cần mẫu view mà bộ điều khiển trả về. Như đã đề cập, chúng tôi đang sử dụng Thymeleaf và theo quy ước, tệp mẫu của chúng tôi sẽ là src/main/resources/templates/index.html
.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Spring MVC Demo</title>
</head>
<body>
<h1 th:text="${message}">Văn bản này sẽ được thay thế bởi Thymeleaf</h1>
<p>Xin chào từ ứng dụng Spring MVC đầu tiên của chúng tôi!</p>
</body>
</html>
Đây là những gì đang xảy ra trong view:
xmlns:th="http://www.thymeleaf.org"
: Khai báo không gian tên Thymeleaf, cho phép chúng tôi sử dụng các thuộc tính Thymeleaf.<h1 th:text="${message}">...</h1>
: Đây là cú pháp Thymeleaf. Thuộc tínhth:text
yêu cầu Thymeleaf thay thế nội dung của thẻ<h1>
bằng giá trị của biến có tênmessage
từ Model. Hãy nhớ chúng tôi đã thêm"message", "..."
trong bộ điều khiển? Đây là nơi nó được sử dụng! Văn bản “Văn bản này sẽ được thay thế bởi Thymeleaf” chỉ là nội dung giữ chỗ cho các nhà thiết kế hoặc xem tĩnh.
Thymeleaf xử lý mẫu này, tìm thuộc tính message
trong Model và kết xuất HTML cuối cùng được gửi đến trình duyệt.
Tổng hợp tất cả: Chạy ứng dụng
Với Spring Boot, chạy ứng dụng của bạn cực kỳ đơn giản. Chỉ cần chạy lớp ứng dụng chính (MyWebAppApplication.java
) như một ứng dụng Java. Spring Boot sẽ khởi động máy chủ Tomcat nhúng và ứng dụng của bạn sẽ có thể truy cập được, theo mặc định, tại http://localhost:8080
.
Mở trình duyệt của bạn và điều hướng đến http://localhost:8080
. Bạn sẽ thấy một trang hiển thị thông báo “Chào mừng đến với ứng dụng Spring MVC đầu tiên của chúng tôi!”.
Chúc mừng! Bạn vừa xây dựng và chạy ứng dụng web đầu tiên của mình bằng Spring MVC.
Xử lý biểu mẫu và đầu vào của người dùng
Một ứng dụng web thực sự cần xử lý đầu vào của người dùng, thường thông qua biểu mẫu. Hãy mở rộng ứng dụng của chúng tôi để có một biểu mẫu đơn giản.
Đầu tiên, cập nhật src/main/resources/templates/index.html
để bao gồm một biểu mẫu:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Spring MVC Demo</title>
</head>
<body>
<h1 th:text="${message}">Văn bản này sẽ được thay thế bởi Thymeleaf</h1>
<p>Xin chào từ ứng dụng Spring MVC đầu tiên của chúng tôi!</p>
<h2>Gửi lời chào</h2>
<form action="#" th:action="@{/greet}" method="post">
<label for="name">Nhập tên của bạn:</label>
<input type="text" id="name" name="name"><br><br>
<button type="submit">Chào tôi</button>
</form>
<div th:if="${greeting != null}">
<h3 th:text="${greeting}">Giữ chỗ cho lời chào</h3>
</div>
</body>
</html>
Các phần tử mới trong HTML:
- Một biểu mẫu đơn giản với một đầu vào văn bản có tên
name
và một nút gửi. th:action="@{/greet}"
: Cú pháp Thymeleaf này tạo URL chính xác cho đường dẫn/greet
. Biểu mẫu sẽ gửi bằng phương thức POST.<div th:if="${greeting != null}">...</div>
: Phần này sẽ chỉ được kết xuất nếu biếngreeting
tồn tại trong Model (mà chúng tôi sẽ thêm sau khi xử lý biểu mẫu).<h3 th:text="${greeting}">...</h3>
: Hiển thị thông báo chào mừng từ Model.
Bây giờ, hãy thêm một phương thức bộ điều khiển để xử lý việc gửi biểu mẫu.
package com.example.mywebapp.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class HomeController {
@GetMapping("/")
public String homePage(Model model) {
model.addAttribute("message", "Chào mừng đến với ứng dụng Spring MVC đầu tiên của chúng tôi!");
// Không có thuộc tính greeting ban đầu
return "index";
}
@PostMapping("/greet") // Ánh xạ các yêu cầu HTTP POST đến "/greet"
public String handleGreetingForm(@RequestParam("name") String name, Model model) {
// Xử lý đầu vào 'name'
String greetingMessage = "Xin chào, " + name + "!";
// Thêm lời chào vào model
model.addAttribute("message", "Chào mừng trở lại!"); // Tùy chọn cập nhật thông báo chào mừng
model.addAttribute("greeting", greetingMessage); // Thêm lời chào để hiển thị
// Trả về cùng tên view để kết xuất trang đã cập nhật
return "index";
}
}
Phương thức bộ điều khiển mới:
@PostMapping("/greet")
: Ánh xạ các yêu cầu POST cụ thể đến URL/greet
.@RequestParam("name") String name
: Chú thích này yêu cầu Spring MVC trích xuất tham số yêu cầu có tên “name” (đến từ đầu vào biểu mẫu vớiname="name"
) và liên kết nó với tham sốString name
của phương thức. Đây là một cách phổ biến để lấy dữ liệu biểu mẫu đơn giản.- Phương thức xử lý tên và tạo
greetingMessage
. model.addAttribute("greeting", greetingMessage)
: Lời chào được tạo được thêm vào Model.return "index";
: Chúng tôi trả về cùng tên view (“index”). Spring MVC sẽ kết xuất lạiindex.html
, nhưng lần này thuộc tínhgreeting
sẽ có trong Model, khiến<div>
chào mừng được hiển thị.
Khởi động lại ứng dụng của bạn. Truy cập http://localhost:8080
, nhập tên vào trường đầu vào và nhấp vào “Chào tôi”. Bạn sẽ thấy thông báo chào mừng xuất hiện bên dưới biểu mẫu mà không cần điều hướng đến một trang mới (vì chúng tôi đã kết xuất cùng một view). Điều này minh họa cách một bộ điều khiển xử lý đầu vào và cập nhật dữ liệu hiển thị trong view.
Vượt ra ngoài những điều cơ bản: Tiếp theo là gì?
Ví dụ đơn giản này chỉ chạm vào bề mặt của những gì Spring MVC có thể làm. Khi bạn tiếp tục hành trình, bạn sẽ khám phá:
- Xác thực biểu mẫu: Sử dụng các chú thích và hỗ trợ xác thực của Spring để xác thực đầu vào của người dùng.
- Xử lý các biến đường dẫn và biến ma trận: Trích xuất dữ liệu trực tiếp từ đường dẫn URL (ví dụ:
/users/{userId}
). - Sử dụng
@ModelAttribute
: Liên kết dữ liệu biểu mẫu trực tiếp với một đối tượng Java thay vì các tham số riêng lẻ. - Làm việc với phiên và cookie: Quản lý trạng thái giữa các yêu cầu.
- Xử lý tải lên tệp.
- Xây dựng API RESTful: Sử dụng
@RestController
(một chuyên môn hóa của@Controller
) để trả về các định dạng dữ liệu như JSON hoặc XML thay vì tên view. Đây là một lĩnh vực lớn mà chúng tôi sẽ đề cập trong một bài viết tương lai! - Quốc tế hóa (i18n) và bản địa hóa (l10n).
- Xử lý lỗi: Tùy chỉnh các trang lỗi và xử lý ngoại lệ.
Tất cả các khả năng này được xây dựng dựa trên các khái niệm nền tảng của DispatcherServlet
, Handler Mappings, Controllers, Models, Views và View Resolvers mà chúng tôi đã thảo luận ở đây.
Kết luận
Spring MVC là một framework mạnh mẽ, linh hoạt và trưởng thành để xây dựng các ứng dụng web trong Java. Bằng cách tuân thủ mẫu MVC và tận dụng các tính năng cốt lõi của Spring như IoC và DI, nó cung cấp một cách có cấu trúc để xử lý các phức tạp của lớp web.
Hiểu luồng yêu cầu thông qua DispatcherServlet
và vai trò của Controller, Model và View là chìa khóa để làm chủ Spring MVC. Với các công cụ như Spring Boot, việc bắt đầu dễ dàng hơn bao giờ hết, cho phép bạn tập trung vào logic ứng dụng của mình thay vì cấu hình mẫu.
Bạn đã thực hiện một bước quan trọng trong Lộ trình Java Spring bằng cách xây dựng ứng dụng web đầu tiên của mình. Trong các bài viết tiếp theo, chúng tôi sẽ đi sâu hơn vào các lĩnh vực cụ thể, bao gồm tính bền vững dữ liệu (kết nối ứng dụng của bạn với cơ sở dữ liệu) và xây dựng API REST.
Tiếp tục thử nghiệm, tiếp tục xây dựng và tiếp tục theo dõi lộ trình!