← Назад к вопросам

Что такое Controller в Spring?

1.0 Junior🔥 241 комментариев
#Spring Framework

Комментарии (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 слою.

Что такое Controller в Spring? | PrepBro