Trong quá trình phát triển ứng dụng với Spring MVC, việc sử dụng các annotation là một phần không thể thiếu để định nghĩa cách các controller xử lý yêu cầu và tương tác với dữ liệu. Bài viết này sẽ đi sâu vào một số annotation phổ biến và quan trọng nhất, cung cấp ví dụ minh họa để bạn dễ dàng nắm bắt.
Để bắt đầu, chúng ta thường khai báo một controller và thiết lập đường dẫn gốc (base path) cho nó bằng @RequestMapping:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
// ... các import khác
@Controller
@RequestMapping("/api/data") // Đường dẫn gốc cho tất cả các phương thức trong controller này
public class AppController {
// Các phương thức xử lý yêu cầu sẽ được định nghĩa tại đây
@GetMapping("/hello")
public String sayHello() {
System.out.println("Xin chào từ AppController!");
return "welcome"; // Tên của view (ví dụ: welcome.jsp)
}
}
1. @RequestParam: Lấy Tham Số Từ URL hoặc Form
Annotation @RequestParam được sử dụng để trích xuất các tham số từ chuỗi truy vấn (query string) của URL hoặc dữ liệu gửi từ biểu mẫu (form data) và gán chúng vào các đối số của phương thức trong controller.
Ví dụ 1: Sử dụng mặc định
Khi tên tham số trong phương thức controller giống với tên tham số trong request, bạn không cần chỉ định rõ ràng.
@GetMapping("/user/details")
public String getUserDetails(String userName, String userEmail) {
System.out.println("Tên người dùng: " + userName);
System.out.println("Email người dùng: " + userEmail);
return "display_user_info";
}
Để gọi phương thức này, bạn sẽ truy cập URL tương tự:
<a href="/api/data/user/details?userName=nguyenvana&userEmail=vana@example.com">Xem Thông Tin Người Dùng</a>
Ví dụ 2: Đổi tên tham số request
Bạn có thể sử dụng thuộc tính name (hoặc value) của @RequestParam để ánh xạ một tên tham số khác từ request vào biến của bạn. Thuộc tính required có thể dùng để đánh dấu tham số là bắt buộc hay tùy chọn.
@GetMapping("/search/product")
public String searchProduct(@RequestParam(name = "pName") String productName,
@RequestParam(name = "pCategory", required = false) String category) {
System.out.println("Tìm kiếm sản phẩm theo tên (pName): " + productName);
if (category != null) {
System.out.println("Trong danh mục (pCategory): " + category);
} else {
System.out.println("Không có danh mục được chỉ định.");
}
return "product_search_results";
}
Với cấu hình này, request từ client cần sử dụng tên tham số pName và pCategory:
<a href="/api/data/search/product?pName=LaptopX&pCategory=Electronics">Tìm Kiếm Laptop X</a>
<br/>
<a href="/api/data/search/product?pName=BookA">Tìm Kiếm Sách A (không có danh mục)</a>
2. @RequestBody, @RequestHeader, @CookieValue: Xử Lý Nội Dung & Thông Tin Request
Các annotation này cho phép chúng ta truy cập sâu hơn vào các thành phần của một request HTTP.
@RequestBody: Đọc Nội Dung HTTP Body
@RequestBody được dùng để đọc toàn bộ nội dung của phần thân (body) của một request HTTP và chuyển đổi nó thành kiểu dữ liệu phù hợp (ví dụ: chuỗi, đối tượng Java khi xử lý JSON/XML). Thường dùng cho các request POST/PUT gửi dữ liệu có cấu trúc từ API hoặc form.
@RequestHeader: Truy Cập HTTP Header
Với @RequestHeader, bạn có thể lấy giá trị của một header HTTP cụ thể (ví dụ: User-Agent, Accept, Content-Type) từ request đến phương thức controller.
@CookieValue: Đọc Giá Trị Cookie
@CookieValue cho phép trích xuất giá trị của một cookie cụ thể đã được gửi từ trình duyệt client trong request. Thuộc tính required = false cho phép xử lý khi cookie không tồn tại.
Ví dụ Kết Hợp:
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.CookieValue;
// ... (Controller class setup)
@PostMapping("/submitOrder")
public String handleOrderSubmission(@RequestBody String orderPayload,
@RequestHeader(value = "Accept-Language") String languageHeader,
@CookieValue(value = "userSessionId", required = false) String sessionIdCookie) {
System.out.println("Nội dung Body Request (Order Payload):");
System.out.println(orderPayload);
System.out.println("---------------------");
System.out.println("Ngôn ngữ ưu tiên (Accept-Language): " + languageHeader);
System.out.println("---------------------");
System.out.println("Giá trị Cookie 'userSessionId': " + (sessionIdCookie != null ? sessionIdCookie : "Không tìm thấy"));
return "order_confirmation";
}
Để kiểm thử phương thức này, bạn cần gửi một request POST, ví dụ thông qua biểu mẫu HTML:
<form action="/api/data/submitOrder" method="post">
<h3>Form Đặt Hàng</h3>
<label for="itemName">Tên Mặt Hàng:</label>
<input type="text" id="itemName" name="item.name"><br><br>
<label for="itemQuantity">Số Lượng:</label>
<input type="number" id="itemQuantity" name="item.quantity"><br><br>
<label for="customerName">Tên Khách Hàng:</label>
<input type="text" id="customerName" name="customer.fullName"><br><br>
<input type="submit" value="Xác Nhận Đặt Hàng">
</form>
Khi gửi biểu mẫu, orderPayload sẽ chứa dữ liệu gửi đi dưới dạng item.name=...&item.quantity=...&customer.fullName=.... Các header và cookie sẽ được tự động trích xuất nếu có.
3. @PathVariable: Sử Dụng Biến Trong URL
Annotation @PathVariable cho phép bạn trích xuất các giá trị từ các phần động của URL. Đây là một yếu tố then chốt để xây dựng các API theo phong cách RESTful, nơi các tài nguyên được định danh thông qua URL một cách rõ ràng và ngữ nghĩa.
Ví dụ: Lấy Chi Tiết Tài Nguyên
Hãy xem xét cách lấy thông tin một tài nguyên (ví dụ: bài viết, sản phẩm) dựa trên ID của nó:
// ... (Controller class setup)
// Xử lý request GET /api/data/articles/find?title=...
@GetMapping("/articles/find")
public String findArticleByTitle(@RequestParam("title") String articleTitle) {
System.out.println("Tìm kiếm bài viết theo tiêu đề: " + articleTitle);
// Logic tìm kiếm và trả về bài viết
return "article_list";
}
// Xử lý request GET /api/data/articles/{articleId}
@GetMapping("/articles/{articleId}")
public String getArticleById(@PathVariable("articleId") Long articleId) {
System.out.println("Lấy thông tin bài viết với ID: " + articleId);
// Logic để truy vấn database và trả về thông tin bài viết dựa trên articleId
return "article_detail";
}
Trong ví dụ trên:
- Phương thức
findArticleByTitlesử dụng@RequestParamđể lấy tiêu đề bài viết từ chuỗi truy vấn (ví dụ:/articles/find?title=SpringMVC). - Phương thức
getArticleByIdsử dụng@PathVariableđể lấy ID bài viết trực tiếp từ đường dẫn URL (ví dụ:/articles/101). Biến{articleId}trong@GetMappingsẽ được ánh xạ tới tham sốarticleIdcủa phương thức.
Client có thể truy cập các đường dẫn sau:
<a href="/api/data/articles/find?title=SpringSecurity">Tìm Bài Viết Theo Tiêu Đề</a><br>
<a href="/api/data/articles/205">Xem Bài Viết Có ID 205</a>
4. @ModelAttribute: Chuẩn Bị Dữ Liệu Model
Annotation @ModelAttribute có hai cách sử dụng chính, cả hai đều liên quan đến việc quản lý dữ liệu trong Model của Spring MVC.
4.1. Sử dụng trên Phương Thức (Method-Level)
Khi đặt trên một phương thức, @ModelAttribute sẽ đảm bảo rằng phương thức đó được thực thi trước bất kỳ phương thức handler (có @RequestMapping, @GetMapping, v.v.) nào trong cùng controller. Mục đích là để chuẩn bị dữ liệu và đưa vào Model, giúp các phương thức handler sau đó có thể truy cập dữ liệu này. Phương thức này có thể trả về một đối tượng, hoặc thêm dữ liệu vào một Map được truyền vào.
Giả sử chúng ta có các lớp POJO (Plain Old Java Object) đơn giản cho User và Account:
// User.java
import java.util.Date;
public class User {
private String fullName;
private int age;
private Date dateOfBirth;
// Getters, Setters, toString(), Constructors
public User() {}
public User(String fullName, int age, Date dateOfBirth) {
this.fullName = fullName;
this.age = age;
this.dateOfBirth = dateOfBirth;
}
// ...
}
// Account.java
public class Account {
private String accountName;
private String password;
private User linkedUser;
// Getters, Setters, toString(), Constructors
public Account() {}
public Account(String accountName, String password, User linkedUser) {
this.accountName = accountName;
this.password = password;
this.linkedUser = linkedUser;
}
// ...
}
Ví dụ 1: Phương thức trả về đối tượng
Phương thức @ModelAttribute này sẽ tạo và trả về một đối tượng Account mặc định. Đối tượng này sau đó sẽ được thêm vào Model với tên mà annotation chỉ định.
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestParam;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
// ... (AppController class setup)
@ModelAttribute("defaultAccount") // Tên của thuộc tính trong Model
public Account setupDefaultAccount(@RequestParam(value = "id", required = false) String userId) {
System.out.println("--- Phương thức @ModelAttribute 'setupDefaultAccount' được gọi ---");
// Giả lập lấy thông tin người dùng từ cơ sở dữ liệu hoặc dịch vụ khác
User defaultUser = new User();
defaultUser.setFullName("Nguyễn Văn A");
defaultUser.setAge(28);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date dob = null;
try {
dob = dateFormat.parse("1996-03-20");
} catch (ParseException e) {
e.printStackTrace();
dob = new Date(); // Fallback
}
defaultUser.setDateOfBirth(dob);
if (userId != null && !userId.isEmpty()) {
System.out.println("Đang tìm kiếm người dùng với ID: " + userId);
// Có thể thực hiện logic tìm kiếm người dùng thực tế tại đây
defaultUser.setFullName("Người dùng ID " + userId);
}
Account newAcc = new Account();
newAcc.setLinkedUser(defaultUser);
newAcc.setAccountName("guest_user"); // Giá trị mặc định
newAcc.setPassword("secure_temp_pass");
System.out.println("Tài khoản mặc định được chuẩn bị: " + newAcc);
return newAcc;
}
@PostMapping("/registerAccount")
public String registerAccount(@ModelAttribute("defaultAccount") Account submittedAccount) {
System.out.println("--- Phương thức handler 'registerAccount' được gọi ---");
// Spring sẽ tự động điền các trường từ request (ví dụ: form data) vào 'submittedAccount'
// sau khi phương thức 'setupDefaultAccount' chạy.
// Những thuộc tính không có trong request sẽ giữ giá trị mặc định từ 'setupDefaultAccount'.
System.out.println("Dữ liệu tài khoản nhận được trong handler: " + submittedAccount);
// Lưu tài khoản vào cơ sở dữ liệu, v.v.
return "registration_success";
}
Trong ví dụ trên, setupDefaultAccount chạy trước, tạo một Account với dữ liệu mặc định và đặt nó vào Model. Khi registerAccount được gọi, Spring sẽ cố gắng ánh xạ các tham số từ request vào đối tượng submittedAccount. Các trường không được cung cấp trong request sẽ vẫn giữ giá trị đã được thiết lập bởi setupDefaultAccount.
Biểu mẫu HTML để gửi dữ liệu:
<form action="/api/data/registerAccount?id=123" method="post">
<h3>Đăng Ký Tài Khoản</h3>
<label for="accName">Tên tài khoản:</label>
<input type="text" id="accName" name="accountName"><br><br>
<label for="accPass">Mật khẩu:</label>
<input type="password" id="accPass" name="password"><br><br>
<!-- Các trường khác của User có thể được thêm vào đây để ghi đè giá trị mặc định -->
<label for="userFullname">Họ Tên của bạn:</label>
<input type="text" id="userFullname" name="linkedUser.fullName"><br><br>
<input type="submit" value="Đăng Ký Tài Khoản">
</form>
Ví dụ 2: Phương thức thêm dữ liệu vào Map
Bạn cũng có thể sử dụng @ModelAttribute trên phương thức để điền dữ liệu vào một đối tượng Map (hoặc Model) được truyền vào, thay vì trả về một đối tượng.
import java.util.Map;
// ... (imports)
// ... (AppController class setup)
@ModelAttribute
public void populateCommonData(Map<String, Object> modelAttributes) {
System.out.println("--- Phương thức 'populateCommonData' được gọi ---");
User admin = new User("Quản Trị Viên", 40, new Date());
Account adminAcc = new Account("sys_admin", "super_secret", admin);
modelAttributes.put("adminCredentials", adminAcc); // Thêm vào model với key "adminCredentials"
System.out.println("Đã thêm 'adminCredentials' vào model.");
}
@GetMapping("/viewAdminPanel")
public String showAdminPanel(@ModelAttribute("adminCredentials") Account adminInfo) {
System.out.println("--- Phương thức handler 'showAdminPanel' được gọi ---");
System.out.println("Thông tin quản trị viên từ model: " + adminInfo);
// Có thể dùng adminInfo để hiển thị trên view
return "admin_dashboard";
}
Trong ví dụ này, populateCommonData sẽ thêm một đối tượng Account (cho quản trị viên) vào ModelMap với key "adminCredentials". Phương thức handler showAdminPanel sau đó có thể lấy đối tượng này ra bằng cách sử dụng @ModelAttribute("adminCredentials").
4.2. Sử dụng trên Tham Số Phương Thức (Parameter-Level)
Khi @ModelAttribute được sử dụng trên một tham số của phương thức handler, nó chỉ ra rằng tham số đó nên được ánh xạ tới một thuộc tính đã có trong Model. Nếu không tìm thấy, Spring sẽ tự động khởi tạo một đối tượng mới và thêm nó vào Model. Sau đó, nó sẽ cố gắng điền các thuộc tính của đối tượng này bằng cách ánh xạ từ các tham số request.
Các ví dụ registerAccount và showAdminPanel ở trên đã minh họa cách dùng này khi chúng ta khai báo @ModelAttribute("...") Account submittedAccount trong phương thức handler. Spring sẽ tìm đối tượng với tên đó trong Model (do phương thức @ModelAttribute cấp độ phương thức đã tạo) hoặc tạo mới nếu không có, sau đó điền dữ liệu từ request vào đối tượng này.
Ngoài ra, bạn có thể tiêm trực tiếp đối tượng Model hoặc Map vào phương thức handler để truy cập toàn bộ Model đã được xây dựng từ các phương thức @ModelAttribute cấp độ phương thức:
@GetMapping("/accessModelContent")
public String accessFullModel(Map<String, Object> model) {
System.out.println("--- Phương thức handler 'accessFullModel' được gọi ---");
System.out.println("Toàn bộ nội dung Model: " + model);
// Bạn có thể truy cập các thuộc tính đã được thêm vào bởi các phương thức @ModelAttribute khác ở đây
return "model_content_view";
}