VieTopik
Tiếng HànTiếng AnhIT
  • Góc học tập
Tải app
  • Thư viện
  • Luyện thi
  • Cẩm nang
  • Góc học tập
Spring Boot Web APINền tảng web
Bài 3/19
5 phút

Controller và routing

Nội dung bài · 5 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt

Project mới chưa trả lời URL nào, còn cửa hàng cần API sản phẩm. Bài này hướng dẫn tự viết controller đầu tiên và xem Spring chọn method nào của controller để chạy khi có request tới.

Khái niệm

🎮 Controller: class đánh dấu @RestController, gom các method xử lý request cho cùng một tài nguyên.

⚡ Handler method: method trong controller gắn @GetMapping, @PostMapping…, mỗi method ứng với một cặp HTTP method và URL.

🗺️ Routing: cơ chế Spring dựa vào HTTP method và URL của request để chọn handler method sẽ chạy.

🏷️ Annotation: nhãn bắt đầu bằng @ đặt phía trên class, method hay tham số để compiler hoặc framework đọc và xử lý theo, ví dụ @Override, @GetMapping.

Ví dụ

Tạo hai file ProductsController.java và Product.java cạnh ShopApiApplication.java:

// ProductsController.java
package com.shop.api;

import java.util.List;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/products")
class ProductsController {
    private final List<Product> products = List.of(
            new Product(1, "Pen", 5000),
            new Product(2, "Notebook", 12000));

    @GetMapping
    public List<Product> getAll() {
        return products;
    }

    @GetMapping("/{id}")
    public Product getById(@PathVariable int id) {
        return products.stream()
                .filter(p -> p.getId() == id)
                .findFirst()
                .orElse(null);
    }
}

// Product.java
package com.shop.api;

class Product {
    private int id;
    private String name;
    private long price;

    public Product(int id, String name, long price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public int getId() { return id; }
    public String getName() { return name; }
    public long getPrice() { return price; }
}
  • import org.springframework.web.bind.annotation.*; mở package chứa các annotation của controller, thiếu thì compiler báo không tìm thấy tên.
  • @RequestMapping("/api/products") đặt đường dẫn chung cho cả controller.
  • @GetMapping nối GET /api/products tới getAll.
  • @GetMapping("/{id}") nối GET /api/products/2 tới getById. Phần {id} trong URL được gán vào tham số có @PathVariable cùng tên.
  • @RestController báo cho Spring đây là controller, và giá trị trả về được gửi thẳng cho client dưới dạng JSON.
  • Spring chỉ tạo một object controller dùng cho mọi request, nên field products là dữ liệu chung của cả server.
  • Danh sách sản phẩm để tạm trong bộ nhớ. Chương 4 sẽ chuyển sang database.

Routing ghép đường dẫn của controller với đường dẫn của method để chọn handler method:

/api/products /api/products + /{id} GET /api/products getAll() GET /api/products/2 getById(id = 2)
Request được nối tới handler method nào

Thử ngay

Bấm Ctrl+C rồi chạy lại server, sau đó gọi lần lượt:

curl -i http://localhost:5000/api/products
curl -i http://localhost:5000/api/products/2
curl -i http://localhost:5000/api/products/99

Đoán trước khi chạy: sản phẩm số 99 không có. Lần gọi cuối trả status code nào?

Xem kết quả
HTTP/1.1 200
[{"id":1,"name":"Pen","price":5000},{"id":2,"name":"Notebook","price":12000}]

HTTP/1.1 200
{"id":2,"name":"Notebook","price":12000}

HTTP/1.1 200
Content-Length: 0

Không phải 404 mà là 200 với body rỗng. Method trả về null, Spring coi đó là "thành công nhưng không có gì để gửi". Bài Trả về kết quả sẽ hướng dẫn trả 404 đúng cách.

Lỗi hay gặp

Controller thiếu @RequestMapping. Method không còn đường dẫn chung phía trước, nên @GetMapping nối vào GET /. Gọi GET /api/orders nhận 404.

// SAI — getAll nằm ở GET /, /api/orders trả 404
// OrdersController.java
package com.shop.api;

import org.springframework.web.bind.annotation.*;

@RestController
class OrdersController {
    @GetMapping
    public String getAll() {
        return "orders";
    }
}
// ĐÚNG
// OrdersController.java
package com.shop.api;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/orders")
class OrdersController {
    @GetMapping
    public String getAll() {
        return "orders";
    }
}

Hai method cùng HTTP method và cùng URL. Spring không biết chọn cái nào, nên ứng dụng dừng ngay lúc khởi động với lỗi Ambiguous mapping.

// SAI — hai method đều là GET /api/customers
// CustomersController.java
package com.shop.api;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/customers")
class CustomersController {
    @GetMapping
    public String getAll() {
        return "all";
    }

    @GetMapping
    public String getActive() {
        return "active";
    }
}

Sửa bằng cách cho method thứ hai một URL riêng.

// ĐÚNG — GET /api/customers/active
// CustomersController.java
package com.shop.api;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/customers")
class CustomersController {
    @GetMapping
    public String getAll() {
        return "all";
    }

    @GetMapping("/active")
    public String getActive() {
        return "active";
    }
}

Tóm tắt

  • Controller đánh dấu @RestController và @RequestMapping.
  • Mỗi handler method có @GetMapping, @PostMapping… kèm phần URL riêng nếu cần.
  • {id} trong URL được gán vào tham số @PathVariable cùng tên.
  • Handler method trả về null thì response là 200 với body rỗng, không phải

Tự kiểm tra

0/3 câu
Câu 1

Controller có @RequestMapping("/api/orders"), method có @GetMapping("/{id}/lines"). URL nào gọi tới method này?

Câu 2

Method getById(@PathVariable int id) trả về null khi không tìm thấy. Client nhận gì?

Câu 3

OrdersController có @RequestMapping("/api/orders") và method getAll() gắn @GetMapping, nhưng quên @RestController. Gọi GET /api/orders nhận gì?

Tạo Web API đầu tiênNhận dữ liệu từ request

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt