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

Что такое HandlerMapping?

2.0 Middle🔥 131 комментариев
#Spring Framework

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

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

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

HandlerMapping в Spring Framework

Что это такое

HandlerMapping — это интерфейс в Spring Framework, который отвечает за маппинг входящих HTTP запросов на обработчики (handlers). Это один из ключевых компонентов DispatcherServlet, который определяет, какой контроллер должен обработать конкретный запрос.

Назначение

  • Сопоставление URL с контроллерами — найти нужный обработчик для запроса
  • Поддержка различных стратегий маппинга — разные типы маршрутизации
  • Кэширование — оптимизация производительности
  • Перехват запросов — обработка перехватчиков до попадания в контроллер

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

HTTP Request
     ↓
DispatcherServlet
     ↓
HandlerMapping.getHandler(request)
     ↓
HandlerExecutionChain (Handler + Interceptors)
     ↓
ControllerMethod

Главные реализации HandlerMapping

1. RequestMappingHandlerMapping

Самая часто используемая — маппит URL на методы с аннотацией @RequestMapping:

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    // Маппится на GET /api/users
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }
    
    // Маппится на GET /api/users/{id}
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.findById(id);
    }
    
    // Маппится на POST /api/users
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
    
    // Маппится на PUT /api/users/{id}
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        return userService.update(id, user);
    }
    
    // Маппится на DELETE /api/users/{id}
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.delete(id);
    }
}

2. SimpleUrlHandlerMapping

Паттерн-базированное маппирование через конфигурацию:

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    
    @Bean
    public SimpleUrlHandlerMapping simpleUrlHandlerMapping(
        UserController userController) {
        
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        
        Map<String, Object> urlMap = new HashMap<>();
        urlMap.put("/users", userController);
        urlMap.put("/products", productController);
        urlMap.put("/orders", orderController);
        
        mapping.setUrlMap(urlMap);
        mapping.setOrder(0);
        
        return mapping;
    }
}

3. BeanNameUrlHandlerMapping

Маппирует URL на имена beans в контексте:

@Configuration
public class AppConfig {
    
    // Bean с именем /home маппится на URL /home
    @Bean("/home")
    public Controller homeController() {
        return new HomeController();
    }
    
    @Bean("/about")
    public Controller aboutController() {
        return new AboutController();
    }
}

HandlerExecutionChain

Результат работы HandlerMapping включает обработчик и перехватчики:

public class HandlerMappingExample {
    
    // HandlerMapping возвращает:
    // HandlerExecutionChain {
    //   handler: UserController.getUserById
    //   interceptors: [LoggingInterceptor, SecurityInterceptor]
    // }
    
    @Configuration
    public class InterceptorConfig implements WebMvcConfigurer {
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // Добавляем перехватчики
            registry.addInterceptor(new LoggingInterceptor())
                .addPathPatterns("/**");
            
            registry.addInterceptor(new SecurityInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/public/**");
        }
    }
}

Пример с Custom HandlerMapping

@Component
public class CustomHandlerMapping implements HandlerMapping {
    
    private static final Logger logger = LoggerFactory.getLogger(
        CustomHandlerMapping.class);
    
    @Override
    public HandlerExecutionChain getHandler(HttpServletRequest request) 
        throws Exception {
        
        String requestURI = request.getRequestURI();
        String method = request.getMethod();
        
        logger.info("Mapping request: {} {}", method, requestURI);
        
        // Логика маппинга
        if (requestURI.startsWith("/api/")) {
            ApiController handler = new ApiController();
            return new HandlerExecutionChain(handler, 
                new LoggingInterceptor(),
                new SecurityInterceptor()
            );
        } else if (requestURI.startsWith("/web/")) {
            WebController handler = new WebController();
            return new HandlerExecutionChain(handler, 
                new LoggingInterceptor()
            );
        }
        
        // Если маппинг не найден
        return null;
    }
    
    @Override
    public int getOrder() {
        // Порядок выполнения (ниже = выше приоритет)
        return 0;
    }
}

Порядок поиска HandlerMapping

Spring ищет подходящий HandlerMapping в определённом порядке (по order):

@Configuration
public class MappingOrderConfig {
    
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping mapping = 
            new RequestMappingHandlerMapping();
        mapping.setOrder(0);  // Проверяется первым
        return mapping;
    }
    
    @Bean
    public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setOrder(1);  // Проверяется вторым
        return mapping;
    }
    
    @Bean
    public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
        BeanNameUrlHandlerMapping mapping = 
            new BeanNameUrlHandlerMapping();
        mapping.setOrder(2);  // Проверяется третьим
        return mapping;
    }
}

Полный процесс DispatcherServlet

// Упрощённая версия работы DispatcherServlet
public class DispatcherServlet extends HttpServlet {
    
    private List<HandlerMapping> handlerMappings;
    
    protected void doDispatch(HttpServletRequest request,
                             HttpServletResponse response) throws Exception {
        
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        
        try {
            // 1. Получить HandlerExecutionChain
            mappedHandler = getHandler(processedRequest);
            
            if (mappedHandler == null) {
                // Ошибка 404
                response.sendError(404);
                return;
            }
            
            // 2. Выполнить pre-handle перехватчики
            for (HandlerInterceptor interceptor : 
                mappedHandler.getInterceptors()) {
                if (!interceptor.preHandle(processedRequest, 
                    response, mappedHandler.getHandler())) {
                    return;  // Отклонили запрос
                }
            }
            
            // 3. Выполнить основной обработчик
            Object handler = mappedHandler.getHandler();
            ModelAndView mv = handleRequest(handler, processedRequest, 
                response);
            
            // 4. Выполнить post-handle перехватчики
            for (HandlerInterceptor interceptor : 
                mappedHandler.getInterceptors()) {
                interceptor.postHandle(processedRequest, response, 
                    handler, mv);
            }
            
            // 5. Отправить ответ
            response.getWriter().write(mv.toString());
            
        } finally {
            // 6. Выполнить after-completion перехватчики
            if (mappedHandler != null) {
                for (HandlerInterceptor interceptor : 
                    mappedHandler.getInterceptors()) {
                    interceptor.afterCompletion(processedRequest, 
                        response, mappedHandler.getHandler(), null);
                }
            }
        }
    }
    
    protected HandlerExecutionChain getHandler(
        HttpServletRequest request) throws Exception {
        
        // Пройти по всем HandlerMappings в порядке приоритета
        for (HandlerMapping mapping : handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;  // Найден!
            }
        }
        return null;  // Не найден
    }
}

Практический пример с логированием

@Component
public class LoggingHandlerMapping implements HandlerMapping, 
    Ordered {
    
    private final RequestMappingHandlerMapping delegate;
    private static final Logger logger = LoggerFactory.getLogger(
        LoggingHandlerMapping.class);
    
    public LoggingHandlerMapping(
        RequestMappingHandlerMapping delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public HandlerExecutionChain getHandler(HttpServletRequest request) 
        throws Exception {
        
        HandlerExecutionChain chain = delegate.getHandler(request);
        
        if (chain != null) {
            Object handler = chain.getHandler();
            logger.info("Mapped {} {} to {}",
                request.getMethod(),
                request.getRequestURI(),
                handler.getClass().getSimpleName()
            );
        } else {
            logger.warn("No mapping found for {} {}",
                request.getMethod(),
                request.getRequestURI()
            );
        }
        
        return chain;
    }
    
    @Override
    public int getOrder() {
        return delegate.getOrder();
    }
}

Лучшие практики

  1. Используй @RequestMapping с явным методом HTTP

    @RequestMapping(value = "/users", method = RequestMethod.GET)
    // или проще:
    @GetMapping("/users")
    
  2. Понимай порядок HandlerMappings

    • RequestMappingHandlerMapping выполняется первым
    • Более специфичные маппинги должны иметь ниже order
  3. Используй перехватчики через WebMvcConfigurer

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/public/**");
        }
    }
    
  4. Избегай создания собственных HandlerMappings — обычно стандартные достаточно

Заключение

HandlerMapping — это фундаментальный компонент Spring, который связывает HTTP запросы с обработчиками. В большинстве случаев ты работаешь с ним через @RequestMapping и @RestController, не задумываясь о деталях, но понимание того, как это работает, помогает при отладке и настройке сложных сценариев маршрутизации.