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

Как работает Router в Java?

2.3 Middle🔥 171 комментариев
#REST API и микросервисы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Как работает Router в Java

Определение

Router в Java — это компонент, который маршрутизирует HTTP запросы на правильные обработчики (controllers) на основе URL пути, метода HTTP и других параметров.

Front Controller паттерн

В Spring используется Front Controller:

Клиент → HTTP запрос GET /api/users/123
          ↓
      DispatcherServlet (главный entry point)
          ↓
      HandlerMapping (найти подходящий handler)
          ↓
      UserController.getById(123)
          ↓
      HTTP ответ

RequestMapping аннотации

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping  // GET /api/users
    public List<User> getAll() {
        return userService.findAll();
    }
    
    @GetMapping("/{id}")  // GET /api/users/123
    public User getById(@PathVariable Long id) {
        return userService.findById(id);
    }
    
    @PostMapping  // POST /api/users
    public User create(@RequestBody User user) {
        return userService.save(user);
    }
    
    @PutMapping("/{id}")  // PUT /api/users/123
    public User update(@PathVariable Long id, @RequestBody User user) {
        return userService.update(id, user);
    }
    
    @DeleteMapping("/{id}")  // DELETE /api/users/123
    public void delete(@PathVariable Long id) {
        userService.delete(id);
    }
}

DispatcherServlet — сердце маршрутизации

Это main servlet, который обрабатывает ВСЕ запросы:

public class DispatcherServlet extends HttpServlet {
    
    @Override
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
        // 1. Получить HTTP метод и путь
        String method = request.getMethod();  // GET, POST, ...
        String path = request.getRequestURI();  // /api/users/123
        
        // 2. Найти подходящий handler в карте
        // Карта создаётся при старте приложения
        HandlerExecutionChain handler = getHandler(request);
        // Результат: UserController.getById
        
        // 3. Извлечь path variables
        // {id} = 123 (из /users/{id})
        
        // 4. Вызвать метод контроллера
        Object result = handler.getHandler().invoke(
            userController, 
            123L  // параметр id
        );
        // Результат: User object
        
        // 5. Сериализовать в JSON
        String json = objectMapper.writeValueAsString(result);
        
        // 6. Вернуть ответ
        response.setContentType("application/json");
        response.getWriter().write(json);
    }
}

RequestMappingHandlerMapping

Это компонент, который создаёт карту всех routes при старте приложения:

public class RequestMappingHandlerMapping {
    
    // Spring сканирует все @RestController и @RequestMapping
    // И создаёт эту карту:
    private Map<RequestMappingInfo, HandlerMethod> mappingRegistry = new HashMap<>();
    
    // После инициализации:
    // GET /api/users          → UserController.getAll()
    // GET /api/users/{id}     → UserController.getById(Long)
    // POST /api/users         → UserController.create(User)
    // PUT /api/users/{id}     → UserController.update(Long, User)
    // DELETE /api/users/{id}  → UserController.delete(Long)
    
    // Когда приходит запрос
    public HandlerExecutionChain getHandler(HttpServletRequest request) {
        String method = request.getMethod();
        String path = request.getRequestURI();
        
        // Поиск по методу и пути
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : mappingRegistry.entrySet()) {
            if (entry.getKey().matches(method, path)) {
                return new HandlerExecutionChain(entry.getValue());
            }
        }
        
        throw new 404 Not Found;
    }
}

Path Variable Extraction

@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(
    @PathVariable Long userId,
    @PathVariable Long orderId
) {
    return orderService.findByUserAndOrder(userId, orderId);
}

// Запрос: GET /users/123/orders/456

// DispatcherServlet делает:
// 1. Сравнивает путь: /users/123/orders/456 ~ /users/{userId}/orders/{orderId}
// 2. Извлекает: {userId} = 123, {orderId} = 456
// 3. Вызывает: getOrder(123L, 456L)

Route Priority (специфичность)

@RestController
public class ItemController {
    
    // ✅ Более специфичный путь — приоритет выше
    @GetMapping("/items/latest")
    public Item getLatest() {
        return itemService.getLatest();
    }
    
    // Менее специфичный путь — приоритет ниже
    @GetMapping("/items/{id}")
    public Item getById(@PathVariable Long id) {
        return itemService.findById(id);
    }
}

// Запрос: GET /items/latest
// Маршрутизируется в getLatest(), а не getById("latest")

ContentNegotiation — выбор формата

@RestController
public class ReportController {
    
    @GetMapping("/report")
    public Report getReport() {
        return new Report(...);
    }
}

// На основе Accept header выбирается формат:
// Accept: application/json      → JSON
// Accept: application/xml       → XML
// Accept: text/csv              → CSV (если настроено)

RequestMapping параметры

@RestController
public class OrderController {
    
    // По пути
    @RequestMapping(path = "/orders", method = RequestMethod.GET)
    public List<Order> getOrders() { ... }
    
    // По параметрам запроса
    @RequestMapping("/search")
    @RequestParam("status") String status  // ?status=pending
    public List<Order> search() { ... }
    
    // По headers
    @RequestMapping("/export")
    @RequestHeader("X-Format") String format  // X-Format: csv
    public String export() { ... }
    
    // По content type
    @RequestMapping(
        path = "/data",
        consumes = "application/json",  // Принимает JSON
        produces = "application/json"   // Возвращает JSON
    )
    public Data handleData(@RequestBody Data data) { ... }
}

Обработка 404

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(NoHandlerFoundException e) {
        // Если path не найден в маппинге
        return ResponseEntity
            .status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse(
                "Route not found: " + e.getRequestURL()
            ));
    }
}

Как это работает под капотом

1. Приложение стартует
   ↓
2. Spring сканирует все @RestController и @RequestMapping
   ↓
3. RequestMappingHandlerMapping создаёт карту routes
   ↓
4. DispatcherServlet регистрируется в Tomcat
   ↓
5. Клиент отправляет HTTP запрос
   ↓
6. Tomcat → DispatcherServlet.doDispatch()
   ↓
7. Ищет matching route в карте (по методу и пути)
   ↓
8. Извлекает path variables и query parameters
   ↓
9. Вызывает соответствующий метод контроллера
   ↓
10. Сериализует результат в JSON (ContentNegotiation)
   ↓
11. Возвращает ответ клиенту

На собеседовании

"Router в Spring маршрутизирует HTTP запросы на правильные контроллеры.

Как работает:

  1. DispatcherServlet — главный entry point
  2. RequestMappingHandlerMapping — создаёт карту всех routes
  3. При запросе ищется matching route по методу HTTP и пути
  4. Извлекаются path variables и query parameters
  5. Вызывается метод контроллера
  6. Результат сериализуется (JSON/XML)
  7. Возвращается клиенту

Аннотации: @RequestMapping, @GetMapping, @PostMapping, @PathVariable, @RequestParam"