Параметры запроса: @PathVariable, @RequestParam, @RequestBody
Клиент передаёт данные тремя путями: в пути URL, в query-строке и в теле запроса. Spring связывает каждый с параметром метода своей аннотацией.
Суть: @PathVariable — часть пути (/users/7), @RequestParam — query-параметр (?page=2), @RequestBody — JSON-тело запроса (для POST/PUT).
Чтобы построить полезное API, нужно уметь принимать ввод. Spring предлагает три аннотации, и выбор между ними не случаен — каждая отражает семантику REST.
@PathVariable — идентификатор ресурса
Когда данные однозначно идентифицируют ресурс, они идут в путь:
@GetMapping("/api/users/{id}")
public User byId(@PathVariable Long id) {
return service.findById(id);
}
URL /api/users/7 — это «пользователь номер 7». Идентификатор в пути читается как часть адреса ресурса.
@RequestParam — фильтры и опции
Когда данные уточняют запрос (фильтр, сортировка, страница), они идут в query-строку после ?:
@GetMapping("/api/users")
public List<User> search(
@RequestParam(defaultValue = "0") int page,
@RequestParam(required = false) String name) {
return service.search(name, page);
}
URL /api/users?page=2&name=Анна отфильтрует и пролистает список. Параметр defaultValue задаёт значение по умолчанию, required = false делает параметр необязательным.
@RequestBody — данные для создания/изменения
Когда клиент присылает целый объект (создать пользователя), он идёт в тело запроса как JSON:
@PostMapping("/api/users")
public User create(@RequestBody CreateUserRequest request) {
return service.create(request);
}
Spring через Jackson десериализует JSON из тела в объект Java, сопоставляя поля по именам.
Как работает под капотом
Для каждого параметра метода Spring выбирает HandlerMethodArgumentResolver — компонент, умеющий извлечь значение из нужного места запроса. @PathVariable берёт его из шаблона URL, @RequestParam — из query-строки, @RequestBody запускает Jackson на теле запроса.
POST /api/users?notify=true
тело: {"name":"Анна","age":30}
|
v
+-- @RequestParam notify <- query-строка ("true")
+-- @RequestBody request <- тело -> Jackson -> объект
+-- @PathVariable (нет в этом URL)
|
v
метод create(...) получает готовые аргументы
Смоделируем разбор входа на стороне «контроллера»:
# Разбор параметров запроса по трём источникам
import json
from urllib.parse import urlparse, parse_qs
def parse_request(path, query, body):
parts = path.strip("/").split("/")
path_vars = {}
if len(parts) == 3 and parts[0] == "api" and parts[1] == "users":
path_vars["id"] = parts[2] # @PathVariable
params = parse_qs(query) # @RequestParam
page = int(params.get("page", ["0"])[0])
payload = json.loads(body) if body else None # @RequestBody
return {"id": path_vars.get("id"), "page": page, "body": payload}
print(parse_request("/api/users/7", "", ""))
print(parse_request("/api/users", "page=2", ""))
print(parse_request("/api/users", "", '{"name": "Анна"}'))
Нажмите «Попробуй сам ▶» — каждый источник данных разбирается отдельно, как делает Spring.
Частые ошибки
- Несовпадение имён. Если имя параметра метода не совпадает с именем в URL, нужно указать его явно:
@RequestParam("user_name"). - @RequestBody на GET. GET-запросы по семантике не несут тела — данные передавайте через query-параметры.
- Несколько @RequestBody. В одном методе может быть только одно тело запроса.
Best practices
- Идентификатор ресурса — в путь, фильтры — в query, объекты — в тело.
- Задавайте
defaultValueдля пагинации, чтобы клиент мог не передаватьpage. - Принимайте в тело отдельный DTO запроса, а не сущность БД (подробнее — в разделе про DTO).
Итог: три аннотации — три источника данных. @PathVariable идентифицирует ресурс, @RequestParam уточняет запрос, @RequestBody принимает объект. Выбор отражает смысл данных в REST.