Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Controller в Spring
Что такое Controller
Controller — это класс в Spring, который обрабатывает входящие HTTP запросы и возвращает ответы. Это часть представления (Presentation Layer) в архитектуре приложения.
HTTP Request
↓
DispatcherServlet
↓
Controller
↓
Service (бизнес-логика)
↓
Repository (БД)
↓
HTTP Response
Основные аннотации
@Controller
Мечит класс как контроллер (возвращает HTML):
@Controller
public class UserController {
@GetMapping("/users")
public String getUsers(Model model) {
// возвращает имя шаблона (HTML)
model.addAttribute("users", userService.getAllUsers());
return "users/list"; // users/list.html
}
}
@RestController
Мечит класс как REST контроллер (возвращает JSON):
@RestController
@RequestMapping("/api/users")
public class UserRestController {
private final UserService userService;
@GetMapping
public List<UserDto> getAllUsers() {
return userService.getAllUsers();
}
@PostMapping
public ResponseEntity<UserDto> createUser(@RequestBody UserRequest request) {
UserDto user = userService.createUser(request);
return ResponseEntity.status(201).body(user);
}
}
Маршрутизация запросов
@RequestMapping
Основная аннотация для маршрутизации:
@RestController
@RequestMapping("/api/products") // Базовый путь
public class ProductController {
@RequestMapping("/all", method = RequestMethod.GET)
public List<Product> getAllProducts() { }
// Эквивалентно
@GetMapping("/all")
public List<Product> getAllProducts() { }
}
Методы HTTP
@RestController
@RequestMapping("/api/orders")
public class OrderController {
// GET: получить все заказы
@GetMapping
public List<Order> getAllOrders() { }
// GET: получить заказ по ID
@GetMapping("/{id}")
public Order getOrderById(@PathVariable Long id) { }
// POST: создать новый заказ
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
Order order = orderService.create(request);
return ResponseEntity.status(201).body(order);
}
// PUT: полное обновление
@PutMapping("/{id}")
public Order updateOrder(@PathVariable Long id, @RequestBody OrderRequest request) {
return orderService.update(id, request);
}
// PATCH: частичное обновление
@PatchMapping("/{id}")
public Order partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
return orderService.partialUpdate(id, updates);
}
// DELETE: удалить
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteOrder(@PathVariable Long id) {
orderService.delete(id);
return ResponseEntity.noContent().build();
}
}
Параметры запроса
Path Variable
@GetMapping("/users/{userId}/posts/{postId}")
public Post getUserPost(
@PathVariable Long userId,
@PathVariable Long postId
) {
return postService.getPost(userId, postId);
}
// URL: /users/123/posts/456
// userId = 123, postId = 456
Query Parameters
@GetMapping("/users")
public List<User> searchUsers(
@RequestParam(required = false) String name,
@RequestParam(required = false, defaultValue = "0") int page,
@RequestParam(required = false, defaultValue = "10") int size
) {
return userService.search(name, page, size);
}
// URL: /users?name=John&page=1&size=20
Request Body
public class UserRequest {
@NotNull(message = "Name cannot be null")
private String name;
@Email
private String email;
// getters/setters
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody UserRequest request) {
// Автоматическая десериализация JSON → UserRequest
User user = userService.create(request);
return ResponseEntity.status(201).body(user);
}
// Пример запроса:
// POST /users
// Content-Type: application/json
// {"name": "John", "email": "john@example.com"}
Headers
@GetMapping("/data")
public Data getData(
@RequestHeader("Authorization") String authToken,
@RequestHeader(value = "User-Agent", required = false) String userAgent
) {
// Используем заголовки
return dataService.getData(authToken);
}
// Пример:
// GET /data
// Authorization: Bearer token123
// User-Agent: Mozilla/5.0
Response
ResponseEntity
Полный контроль над ответом:
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity
.ok()
.header("X-Custom-Header", "value")
.body(user);
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody UserRequest request) {
User user = userService.create(request);
return ResponseEntity
.created(URI.create("/users/" + user.getId()))
.body(user);
}
Коды ответов
// 200 OK
ResponseEntity.ok(user);
ResponseEntity.status(200).body(user);
// 201 Created
ResponseEntity.created(URI.create("/resource/" + id)).body(user);
// 204 No Content
ResponseEntity.noContent().build();
// 400 Bad Request
ResponseEntity.badRequest().body("Invalid input");
// 401 Unauthorized
ResponseEntity.status(401).build();
// 403 Forbidden
ResponseEntity.status(403).build();
// 404 Not Found
ResponseEntity.notFound().build();
// 500 Internal Server Error
ResponseEntity.status(500).body("Server error");
Валидация
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody UserRequest request) {
// @Valid активирует валидацию
User user = userService.create(request);
return ResponseEntity.status(201).body(user);
}
// Если валидация не пройдена, Spring вернёт 400 Bad Request
// с деталями ошибок
Content Negotiation
@GetMapping("/users")
public List<User> getUsers() {
return userService.getAllUsers();
}
// Клиент может запросить разный формат:
// JSON (по умолчанию)
// GET /users
// Accept: application/json
// XML (если настроено)
// GET /users
// Accept: application/xml
Exception Handling
@ExceptionHandler
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException e) {
return ResponseEntity
.status(404)
.body(new ErrorResponse("USER_NOT_FOUND", e.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationError(MethodArgumentNotValidException e) {
String message = e.getBindingResult()
.getFieldErrors()
.stream()
.map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
.collect(Collectors.joining(", "));
return ResponseEntity
.status(400)
.body(new ErrorResponse("VALIDATION_ERROR", message));
}
}
// Или глобальный Exception Handler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
return ResponseEntity
.status(500)
.body(new ErrorResponse("INTERNAL_ERROR", e.getMessage()));
}
}
Практический пример
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
// GET все товары с пагинацией
@GetMapping
public ResponseEntity<Page<ProductDto>> getAllProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String search
) {
Page<ProductDto> products = productService.search(search, page, size);
return ResponseEntity.ok(products);
}
// GET товар по ID
@GetMapping("/{id}")
public ResponseEntity<ProductDto> getProductById(@PathVariable Long id) {
return productService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// POST создать товар
@PostMapping
public ResponseEntity<ProductDto> createProduct(@Valid @RequestBody ProductRequest request) {
ProductDto product = productService.create(request);
return ResponseEntity
.created(URI.create("/api/v1/products/" + product.getId()))
.body(product);
}
// PUT обновить товар
@PutMapping("/{id}")
public ResponseEntity<ProductDto> updateProduct(
@PathVariable Long id,
@Valid @RequestBody ProductRequest request
) {
ProductDto product = productService.update(id, request);
return ResponseEntity.ok(product);
}
// DELETE удалить товар
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteProduct(@PathVariable Long id) {
productService.delete(id);
return ResponseEntity.noContent().build();
}
// POST логика, не CRUD операция
@PostMapping("/{id}/publish")
public ResponseEntity<ProductDto> publishProduct(@PathVariable Long id) {
ProductDto product = productService.publish(id);
return ResponseEntity.ok(product);
}
}
Best Practices
✅ Хорошие практики:
- Используй @RestController для REST API
- Возвращай ResponseEntity для полного контроля
- Валидируй входные данные с @Valid
- Используй методы существительные (не глаголы):
/products/{id}/publish, а не/publish-product - Обрабатывай исключения с @ExceptionHandler
- Документируй с Swagger/SpringDoc
- Разделяй DTO от Entity
❌ Плохие практики:
- Бизнес-логика в контроллере (должна быть в Service)
- Прямое использование Entity (должны быть DTO)
- Игнорирование валидации
- Не обработанные исключения
- Глобальный стейт в контроллере
Controller — это тонкий слой, который только маршрутизирует запросы и делегирует работу Service слою.