99 Ngày Spring — Ngày 11: REST controller đầu tiên
Mười ngày đầu ta mài giũa "nội công": container, bean, DI, cấu hình. Hôm nay bắt đầu Giai đoạn 2 — xây REST API: làm thứ mà trình duyệt, app di động hay service khác thật sự gọi được. Nhân vật chính là @RestController — và bạn sẽ thấy chỉ cần vài dòng: không XML, không servlet tay, không cấu hình server — mọi nền móng Ngày 02–10 giờ gặt quả.

REST 30 giây: tài nguyên & động từ
REST nói gọn: mọi thứ là tài nguyên định danh bằng URL (
/api/posts,/api/posts/1), thao tác bằng động từ HTTP:GETđọc,POSTtạo,PUTsửa,DELETExóa.Dữ liệu trao đổi mặc định là JSON — nhẹ, mọi ngôn ngữ đọc được; server và client nhờ đó tách rời hoàn toàn (web, mobile, service khác đều xài chung một API).
Hôm nay ta tập trung
GET— các động từ còn lại lần lượt xuất hiện trong tuần này.
Controller đầu tiên: 5 dòng là chạy
@RestController // = @Controller + @ResponseBodypublic class HelloController {
@GetMapping("/hello") // GET /hello → chạy method này
public String hello() {
return "Xin chào từ Spring Boot!"; // chuỗi trả thẳng vào response body
}
}
// Chạy app (Ngày 02) rồi mở trình duyệt / curl:// GET http://localhost:8080/hello// → 200 OK, body: Xin chào từ Spring Boot!@RestControllerlà stereotype (họ hàng@Component— Ngày 05): component scan nhặt nó thành bean, kiêm lời hứa "giá trị method trả về đi thẳng vào response body".@GetMapping("/hello")khắc biển chỉ đường: GET tới /hello thì gọi method này. Nó là dạng gọn của@RequestMapping(method = GET).Không cần
mainnào khác, không đăng ký gì thêm — chạy app là endpoint sống. So với thời Java EE phải viết servlet + web.xml, đây chính là lý do Spring Boot thắng cuộc (Ngày 01).
Trả JSON: Jackson lo phần nặng nhọc
Chuỗi thô mới là khởi động — API thật trả dữ liệu có cấu trúc. Điều tuyệt nhất: bạn cứ trả object Java, phần còn lại đã có người lo:
public record Post(Long id, String title, boolean published) { }
@RestControllerpublic class PostController {
@GetMapping("/api/posts")
public List<Post> list() { // trả OBJECT — không phải String!
return List.of(
new Post(1L, "Ngày 11: REST controller", true),
new Post(2L, "Bản nháp ngày mai", false)
);
}
}
// GET /api/posts → Jackson TỰ serialize thành JSON:// [// { "id": 1, "title": "Ngày 11: REST controller", "published": true },// { "id": 2, "title": "Bản nháp ngày mai", "published": false }// ]// Content-Type: application/json — không viết một dòng chuyển đổi nào!Jackson — thư viện JSON đi kèm starter web — tự serialize mọi object/record/list thành JSON, đặt luôn
Content-Type: application/json.Dùng
record(Java 21) làm kiểu trả về rất hợp: gọn, bất biến, tên field thành tên key JSON. Java thuần sẽ mổ xẻ record kỹ ở Ngày 20 của series Java.Vì sao Jackson "tự nhiên" có mặt? Auto-configuration Ngày 10: classpath có Jackson →
@ConditionalOnClassđậu →HttpMessageConverterJSON được dựng sẵn.
Hành trình một request
// Hành trình một request GET /api/posts://// Trình duyệt/khách hàng// → Tomcat (server nhúng — starter web mang theo, Ngày 02)// → DispatcherServlet (bưu điện trung tâm của Spring MVC)// → HandlerMapping: "/api/posts" thuộc về PostController.list()// → gọi method — controller là BEAN (Ngày 03!), được DI đầy đủ// → giá trị trả về → Jackson (HttpMessageConverter) → JSON// ← 200 OK + application/json//// DispatcherServlet & bạn bè đều do AUTO-CONFIGURATION dựng (Ngày 10!)// — thấy spring-boot-starter-web trên classpath là cả dây chuyền mọc raNắm sơ đồ này là nắm xương sống Spring MVC: DispatcherServlet nhận mọi request rồi phân phát cho controller khớp đường dẫn — vì thế gọi là mô hình front controller. Từng mắt xích (mapping, converter, xử lý lỗi) đều là bean thay được — nhưng mặc định của Boot đã đủ tốt để ta chỉ viết controller.
Controller mỏng, service dày
Thói quen vàng nên tập từ ngày đầu tiên: controller chỉ nhận request và trả response — nghiệp vụ đẩy xuống tầng @Service (Ngày 05), nối nhau bằng constructor injection (Ngày 04):
@RestControllerpublic class PostController {
private final PostService postService; // controller MỎNG: nhận & trả
PostController(PostService postService) { // constructor injection — Ngày 04!
this.postService = postService;
}
@GetMapping("/api/posts")
public List<Post> list() {
return postService.findAll(); // nghiệp vụ ở tầng service
}
}
@Servicepublic class PostService {
public List<Post> findAll() { // mai mốt: gọi repository, cache…
return List.of(new Post(1L, "Bài đầu tiên", true));
}
}Controller mỏng dễ đọc, dễ test; service không dính gì tới HTTP nên tái sử dụng được ở scheduler, listener, CLI…
Chuỗi phân tầng đầy đủ controller → service → repository sẽ khép kín khi gặp Spring Data JPA (Ngày 21+).
Bài tập nhỏ
Tạo
HelloControllervớiGET /hellotrả tên bạn — chạy và gọi bằng trình duyệt lẫncurl.Tạo record
Book(Long id, String title, double price)và endpointGET /api/bookstrả 3 cuốn — ngắm JSON tự sinh.Tách
BookServicerồi tiêm vào controller bằng constructor — xác nhận kết quả không đổi.Gọi một đường dẫn không tồn tại (
GET /nope) — xem JSON lỗi 404 mặc định của Boot; Ngày 16 ta sẽ tự may trang lỗi đẹp hơn.
Kết luận
Ứng dụng của bạn đã nói chuyện được với thế giới: @RestController + @GetMapping mở endpoint trong vài dòng, Jackson tự đổi object thành JSON, DispatcherServlet điều phối sau cánh gà, và kiến trúc controller-mỏng-service-dày đặt nền cho mọi ngày sau. Nhưng API mới chỉ biết trả một danh sách cứng — Ngày 12 ta học nhận tham số: @PathVariable cho /api/posts/1, @RequestParam cho ?page=2, và nghệ thuật thiết kế URL tử tế. Hẹn gặp lại!
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


