Spring REST API & Data JPA
4/46
Bài 4 / 46~12 phútSpring MVC CoreLộ trình · chặng 15/21Miễn phí… lượt xem

@RestController & Request Mapping: Xây Dựng REST API

Tìm hiểu meta-annotation @RestController, cú pháp @RequestMapping và các shortcut annotation định tuyến HTTP request trong Spring Boot.

TL;DR: @RestController là meta-annotation kết hợp giữa @Controller và @ResponseBody, tự động áp dụng cơ chế tuần tự hóa dữ liệu cho toàn bộ các phương thức bên trong class. Thay vì trả về tên view HTML, các phương thức sẽ chuyển đổi trực tiếp đối tượng Java thành định dạng JSON/XML thông qua HttpMessageConverter. Các shortcut annotation như @GetMapping hay @PostMapping giúp gắn chặt phương thức HTTP ngay tại thời điểm biên dịch, ngăn ngừa lỗi cấu hình sai sót lúc runtime.

Nhớ lại — URL Routing Pipeline

Quá trình tiến hóa của Controller trong Spring qua 3 thế hệ?

Cách thức định nghĩa controller trong Spring Framework đã trải qua ba giai đoạn cải tiến lớn:

Thế hệ 1 (Spring 1.x - 2.x): Mỗi controller bắt buộc phải cài đặt giao diện Controller và cấu hình đường dẫn ánh xạ thủ công trong file web.xml hoặc XML bean. Cách làm này rất cồng kềnh và khó bảo trì.

Thế hệ 2 (Spring 2.5+): Giới thiệu mô hình hướng annotation với @Controller và @RequestMapping:

Java
@Controller
@RequestMapping("/api/orders")
public class LegacyOrderController {

    @RequestMapping(method = RequestMethod.GET, value = "/{id}")
    @ResponseBody
    public OrderDto getOrder(@PathVariable Long id) {
        return orderService.findById(id);
    }
}

Dù đã loại bỏ được file XML, lập trình viên vẫn phải lặp lại annotation @ResponseBody trên từng phương thức nếu muốn trả về JSON.

Thế hệ 3 (Spring 4+ đến nay): Chuẩn hóa với @RestController kết hợp các shortcut annotation chuyên biệt:

Java
@RestController
@RequestMapping("/api/orders")
public class ModernOrderController {

    @GetMapping("/{id}")
    public OrderDto getOrder(@PathVariable Long id) {
        return orderService.findById(id);
    }

    @PostMapping
    public OrderDto createOrder(@RequestBody OrderRequest req) {
        return orderService.create(req);
    }
}

Bản chất @RestController là gì và vì sao cần @ResponseBody?

Khi một phương thức trong controller trả về một đối tượng Java, Spring MVC đứng trước hai lựa chọn xử lý:

Hai cột đối chiếu cùng một dòng return OrderDto: cột có @ResponseBody bỏ qua ViewResolver, đi qua HttpMessageConverter Jackson và trả 200 kèm body JSON; cột quên @ResponseBody coi giá trị trả về là tên view, ViewResolver đi tìm template OrderDto.html và kết thúc bằng lỗi 500

  1. Nếu không có @ResponseBody: Spring xem đối tượng trả về là tên của một giao diện HTML (view name) và chuyển giao cho ViewResolver tìm kiếm file template tương ứng. Nếu không tìm thấy, hệ thống sẽ báo lỗi 500.
  2. Nếu có @ResponseBody: Spring bỏ qua hoàn toàn ViewResolver, trực tiếp gọi HttpMessageConverter (mặc định là Jackson) để tuần tự hóa đối tượng thành chuỗi JSON và ghi vào thân HTTP response.

Mã nguồn định nghĩa của @RestController trong Spring:

Java
// Spring Framework source code snippet
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    String value() default "";
}

Bản chất của @RestController là gắn sẵn @ResponseBody ở cấp độ lớp. Toàn bộ các phương thức bên trong tự động kế thừa hành vi này mà không cần khai báo lặp lại.

Cấu trúc và 7 thuộc tính then chốt của @RequestMapping là gì?

@RequestMapping là annotation nền tảng hỗ trợ cấu hình chi tiết luồng định tuyến:

Java
@RequestMapping(
    value = "/orders",
    method = RequestMethod.GET,
    consumes = MediaType.APPLICATION_JSON_VALUE,
    produces = MediaType.APPLICATION_JSON_VALUE,
    headers = "X-API-Version=2",
    params = "status=active"
)
public List<OrderDto> listOrders() {
    return orderService.findActive();
}

7 thuộc tính cấu hình bao gồm:

  • value (hoặc path): Mẫu đường dẫn URL cần so khớp.
  • method: Mảng các phương thức HTTP được chấp nhận (nếu không khớp trả về 405 Method Not Allowed).
  • consumes: Lọc theo header Content-Type của request gửi lên (không khớp trả về 415 Unsupported Media Type).
  • produces: Lọc theo header Accept mà client mong muốn nhận về (không khớp trả về 406 Not Acceptable).
  • headers: Ràng buộc sự tồn tại hoặc giá trị của một request header cụ thể.
  • params: Ràng buộc sự xuất hiện của query parameter.
  • name: Tên gán định danh để phục vụ việc tạo URL tự động.

Khi đặt @RequestMapping ở cấp độ lớp, chuỗi URL sẽ đóng vai trò tiền tố chung cho tất cả các phương thức con bên trong.

Vì sao nên ưu tiên các shortcut annotation thay cho @RequestMapping?

Từ Spring 4.3, 5 shortcut annotation được giới thiệu nhằm thay thế @RequestMapping(method = ...):

  • @GetMapping: Tương đương HTTP GET (an toàn và idempotent).
  • @PostMapping: Tương đương HTTP POST (tạo mới tài nguyên).
  • @PutMapping: Tương đương HTTP PUT (cập nhật toàn phần, idempotent).
  • @DeleteMapping: Tương đương HTTP DELETE (xóa tài nguyên, idempotent).
  • @PatchMapping: Tương đương HTTP PATCH (cập nhật một phần).

Lợi ích cốt lõi của shortcut annotation không chỉ dừng lại ở việc làm ngắn gọn mã nguồn, mà còn giúp bảo vệ tính đúng đắn ngay tại thời điểm biên dịch. Bạn không thể vô tình truyền tham số method vào @GetMapping vì trình biên dịch Java sẽ phát hiện lỗi ngay lập tức.

Quy tắc so khớp đường dẫn của PathPatternParser diễn ra ra sao?

Spring Boot 3 sử dụng PathPatternParser với các biểu thức đường dẫn đa dạng:

Java
@GetMapping("/orders")              // Exact match
@GetMapping("/orders/{id}")         // Single path variable
@GetMapping("/orders/{id:\\d+}")    // Regex constraint: only numeric digits
@GetMapping("/orders/{*path}")      // Capture all remaining segments

Khi một URL có thể khớp với nhiều handler khác nhau, Spring giải quyết theo nguyên tắc ưu tiên cấu hình cụ thể nhất (most-specific match):

Java
@GetMapping("/orders/latest")       // Handler A: Literal exact match
@GetMapping("/orders/{id}")         // Handler B: Dynamic path variable

Nếu client gửi request GET /orders/latest, Handler A sẽ luôn được chọn vì phân đoạn chuỗi tĩnh (literal segment) có độ ưu tiên cao hơn biến đường dẫn. Thứ tự phân cấp độ đặc hiệu từ cao xuống thấp: Literal > Regex > Path Variable > Wildcard * > Greedy **.

Quá trình đăng ký và tra cứu registry bên dưới diễn ra như thế nào?

Toàn bộ quá trình định tuyến được phân tách làm hai pha tách biệt:

Hai pha thời gian: lúc khởi động Spring quét bean có @RestController, đọc @RequestMapping trên class lẫn method, dựng registry RequestMappingInfo tới HandlerMethod, và ném IllegalStateException nếu hai handler trùng key; mỗi request chỉ tra registry, xếp hạng specificity rồi trả HandlerExecutionChain

  1. Pha khởi động (Startup Phase): RequestMappingHandlerMapping quét toàn bộ các lớp có @RestController, phân tích các thuộc tính và xây dựng bảng bản đồ RequestMappingInfo -> HandlerMethod. Nếu phát hiện hai method có cùng điều kiện khớp hoàn toàn, Spring sẽ ném ngay ngoại lệ IllegalStateException.
  2. Pha xử lý yêu cầu (Request Phase): DispatcherServlet tra cứu bảng bản đồ bằng phép so sánh băm $O(1)$, sắp xếp độ đặc hiệu và trả về HandlerExecutionChain thích hợp.

Những lỗi sai kinh điển nào cần tránh khi định nghĩa Controller?

Dưới đây là các lỗi thường gặp trong quá trình phát triển API:

  1. Dùng @Controller thay vì @RestController: Quên khai báo @ResponseBody khiến Spring chuyển giao dữ liệu cho ViewResolver tìm file template HTML, dẫn đến lỗi 500 Runtime.
  2. Trùng lặp đường dẫn URL giữa các controller: Cố tình khai báo hai phương thức có cùng URL và HTTP method khiến ứng dụng bị crash ngay khi khởi động.
  3. Để dấu gạch chéo cuối ở class prefix: Đặt @RequestMapping("/api/orders/") kết hợp @GetMapping("/{id}") sẽ vô tình tạo ra đường dẫn có hai dấu gạch chéo // không chuẩn mực.

Tóm tắt

  • @RestController là meta-annotation kết hợp @Controller và @ResponseBody, tối ưu cho việc xây dựng REST API.
  • @ResponseBody chỉ định việc chuyển đổi trực tiếp Java object sang JSON thông qua HttpMessageConverter.
  • 5 shortcut annotation (@GetMapping, @PostMapping...) giúp tăng tính an toàn lúc biên dịch và làm sáng tỏ ý định thiết kế.
  • PathPatternParser hỗ trợ các biểu thức đường dẫn mạnh mẽ và luôn ưu tiên handler có mức độ đặc hiệu cao nhất.
  • Bảng định tuyến được tạo cố định lúc khởi động, đảm bảo ứng dụng phát hiện xung đột sớm theo nguyên lý fail-fast.

Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Tại sao Spring 4 giới thiệu annotation @RestController thay vì giữ nguyên cách viết @Controller kết hợp @ResponseBody?
  2. Q2
    Nếu lập trình viên vô tình dùng @Controller cho một REST API và trả về đối tượng OrderDto mà không có @ResponseBody, điều gì sẽ xảy ra?
  3. Q3
    Một ứng dụng có hai endpoint: GET /orders/latest và GET /orders/{id}. Khi client gửi request GET /orders/latest, handler nào sẽ được chọn?
  4. Q4
    Hai thuộc tính consumes và produces trong @RequestMapping lọc request dựa trên các header HTTP nào?
  5. Q5
    Điều gì xảy ra lúc ứng dụng khởi động nếu có hai controller khai báo trùng lặp hoàn toàn cùng một URL pattern và cùng HTTP method?

Bài tiếp theo: Content Negotiation, Versioning & CORS

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 15/21 · REST API với Spring MVC — bài 4/21 của chặng

Thảo luận & góp ý

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

Bài tiếp theo

Content Negotiation, API Versioning Và CORS Spring