HandlerMapping

Spring MVC HandlerMapping 详解:处理器映射器的原理与实战

HandlerMapping 是 Spring MVC 中连接 “HTTP 请求” 与 “处理器(Handler,即 Controller)” 的核心组件,其核心职责是 根据请求的 URL 或其他属性,找到对应的 Handler 及拦截器(Interceptor),并封装为 HandlerExecutionChain 对象返回给 DispatcherServlet。从 “核心定义→实现类对比→配置方式→源码解析” 四个维度,彻底讲透 HandlerMapping 的工作机制。

HandlerMapping 核心定义与作用

HandlerMapping 是一个接口,定义了所有处理器映射器的核心行为,其本质是 “请求→Handler” 的映射器,相当于 MVC 中的 “路由表”。

1. 核心接口方法

public interface HandlerMapping {
    /**
     * 根据 HTTP 请求,查找对应的 Handler 及拦截器,封装为 HandlerExecutionChain
     * @param request 当前 HTTP 请求
     * @return HandlerExecutionChain(包含 Handler + 拦截器),无匹配则返回 null
     */
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
  • 返回值 HandlerExecutionChain:包含两部分核心内容:
    1. Handler:具体的业务处理器(如 @Controller 类的方法,封装为 HandlerMethod);
    2. HandlerInterceptor 数组:与当前请求匹配的拦截器,按配置顺序存储。

2. 核心数据结构:handlerMap

几乎所有 HandlerMapping 实现类内部都会维护一个 Map<String, Object> 类型的 handlerMap,用于存储 “URL 路径→Handler” 的映射关系,确保请求到来时能快速查找。例如:

// 以 AbstractUrlHandlerMapping 为例,维护 URL 到 Handler 的映射
private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();
  • Key:URL 路径(如 /user/list/api/login);
  • Value:Handler 对象(如 UserController 实例、HandlerMethod 实例)。

HandlerMapping 主要实现类与适用场景

HanderMapping继承关系

Spring MVC 提供了多种 HandlerMapping 实现,支持不同的映射策略(如按 Bean 名、按类名、按注解),以下是常用实现类的对比:

实现类 核心映射逻辑 适用场景 优势 缺点
BeanNameUrlHandlerMapping 将 Controller Bean 的名称(需以 / 开头)作为 URL 路径 简单项目、XML 配置 Controller(如实现 Controller 接口的类) 配置简单,无额外依赖 不支持注解,URL 与 Bean 名强耦合
ControllerClassNameHandlerMapping 将 Controller 类名转为 URL(如 UserController/user 快速开发,无需手动配置 URL 零配置,类名即 URL URL 灵活性差,无法自定义路径
SimplerUrlHandlerMapping 通过 XML/Java 配置显式指定 “URL→Handler” 映射 需要精确控制 URL 与 Handler 映射关系的场景 映射关系清晰,易于维护 配置繁琐,不支持注解
DefaultAnnotationHandlerMapping 解析 @RequestMapping 注解,匹配 URL 与 Controller 类 Spring MVC 3.1 之前的注解驱动项目 支持注解,简化配置 仅支持类级别注解,不支持方法级别精确映射
RequestMappingHandlerMapping 解析类 + 方法级别的 @RequestMapping 注解,返回 HandlerMethod Spring MVC 3.1+ 注解驱动项目(主流) 支持精确路径、请求方法、参数等匹配 依赖注解配置,无注解则无法使用

关键区别:DefaultAnnotationHandlerMapping vs RequestMappingHandlerMapping

  • DefaultAnnotationHandlerMapping(过时):
    • 仅能解析类级别的 @RequestMapping,方法级别的映射需依赖 AnnotationMethodHandlerAdapter
    • 返回的 Handler 是整个 Controller 实例,而非具体方法。
  • RequestMappingHandlerMapping(推荐):
    • 整合了 “类级别 + 方法级别” 的 @RequestMapping 解析,直接返回封装了具体方法的 HandlerMethod
    • 支持更丰富的匹配规则(如请求方法 method = RequestMethod.GET、请求参数 params = "id");
    • mvc:annotation-driven 自动注册的默认 HandlerMapping。

HandlerMapping 配置方式

HandlerMapping 的配置分为 “手动配置” 和 “自动配置” 两种,现代项目推荐使用自动配置(mvc:annotation-driven)。

1. 自动配置:mvc:annotation-driven(推荐)

mvc:annotation-driven 是 Spring MVC 3.1+ 提供的简化配置,会自动注册 RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver 三个核心 Bean,无需手动配置 HandlerMapping:

<!-- springmvc.xml 中配置 -->
<mvc:annotation-driven/>
mvc:annotation-driven 的核心作用:
  1. 自动注册核心组件:
    • RequestMappingHandlerMapping:注解驱动的 HandlerMapping;
    • RequestMappingHandlerAdapter:适配 HandlerMethod 的处理器适配器;
    • ExceptionHandlerExceptionResolver:处理 @ExceptionHandler 注解的异常解析器。
  2. 支持类型转换与格式化:
    • 注册 ConversionService,支持自定义类型转换器;
    • 支持 @NumberFormat(数字格式化)、@DateTimeFormat(日期格式化)注解。
  3. 支持数据校验:
    • 集成 JSR-380 校验(如 @Valid 注解),需依赖 hibernate-validator
  4. 支持 JSON 交互:
    • 自动注册 MappingJackson2HttpMessageConverter(需 jackson-databind 依赖),支持 @RequestBody/@ResponseBody

2. 手动配置(适用于特殊场景)

若需自定义 HandlerMapping(如 SimplerUrlHandlerMapping),可手动在 XML 或 Java 配置中声明:

示例:手动配置 SimplerUrlHandlerMapping
<!-- 1. 定义 Controller Bean -->
<bean id="userController" class="com.example.controller.UserController"/>

<!-- 2. 配置 SimplerUrlHandlerMapping,指定 URL→Handler 映射 -->
<bean class="org.springframework.web.servlet.handler.SimplerUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/user/list">userController</prop> <!-- /user/list → userController -->
            <prop key="/user/detail">userController</prop> <!-- /user/detail → userController -->
        </props>
    </property>
    <!-- 优先级:值越小,优先级越高(若多个 HandlerMapping 匹配,优先使用此映射) -->
    <property name="order" value="1"/>
</bean>

核心实现类源码解析:RequestMappingHandlerMapping

RequestMappingHandlerMapping 是当前注解驱动项目的默认 HandlerMapping,负责解析 @RequestMapping 注解,构建 “URL→HandlerMethod” 的映射,并处理拦截器整合。解析其核心流程。

1. 初始化流程:扫描并构建映射关系

RequestMappingHandlerMapping 实现了 ApplicationContextAwareInitializingBean 接口,在 Spring 容器初始化时完成映射关系的构建,核心步骤:

步骤 1:setApplicationContext → 初始化上下文
// 继承链:RequestMappingHandlerMapping → AbstractHandlerMapping → ApplicationObjectSupport
// ApplicationObjectSupport#setApplicationContext 触发初始化
@Override
public final void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    super.setApplicationContext(applicationContext);
    // 调用 AbstractHandlerMapping#initApplicationContext
    initApplicationContext();
}

// AbstractHandlerMapping#initApplicationContext
protected void initApplicationContext() throws BeansException {
    // 1. 扩展拦截器(空实现,供子类扩展)
    extendInterceptors(this.interceptors);
    // 2. 扫描容器中所有 MappedInterceptor 类型的 Bean,添加到拦截器列表
    detectMappedInterceptors(this.adaptedInterceptors);
    // 3. 初始化拦截器(将 interceptors 转为 HandlerInterceptor)
    initInterceptors();
}
步骤 2:afterPropertiesSet → 扫描 Handler 并构建映射

InitializingBean 接口的 afterPropertiesSet 方法触发 initHandlerMethods,扫描所有带 @Controller@RequestMapping 的 Bean,解析注解并构建映射:

// AbstractHandlerMethodMapping#afterPropertiesSet(RequestMappingHandlerMapping 的父类)
@Override
public void afterPropertiesSet() {
    // 核心:初始化 HandlerMethods(扫描并构建映射)
    initHandlerMethods();
}

// AbstractHandlerMethodMapping#initHandlerMethods
protected void initHandlerMethods() {
    // 1. 获取容器中所有 Bean 的名称(支持扫描父容器)
    String[] beanNames = detectHandlerMethodsInAncestorContexts 
        ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class)
        : getApplicationContext().getBeanNamesForType(Object.class);

    for (String beanName : beanNames) {
        // 跳过作用域代理 Bean(如 @Scope("request"))
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            Class<?> beanType = null;
            try {
                // 2. 获取 Bean 的类型
                beanType = getApplicationContext().getType(beanName);
            } catch (Throwable ex) {
                // 忽略无法加载类型的 Bean
            }

            // 3. 判断是否为 Handler:类上有 @Controller 或 @RequestMapping 注解
            if (beanType != null && isHandler(beanType)) {
                // 4. 解析当前 Bean 的 HandlerMethod(类+方法级别的 @RequestMapping)
                detectHandlerMethods(beanName);
            }
        }
    }

    // 5. 映射构建完成后的回调(空实现,供子类扩展)
    handlerMethodsInitialized(getHandlerMethods());
}
关键方法 isHandler:判断是否为 Handler
// RequestMappingHandlerMapping#isHandler
@Override
protected boolean isHandler(Class<?> beanType) {
    // 类上有 @Controller 或 @RequestMapping 注解,即为 Handler
    return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) 
        || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class);
}
关键方法 detectHandlerMethods:解析方法级映射
// AbstractHandlerMethodMapping#detectHandlerMethods
protected void detectHandlerMethods(Object handler) {
    Class<?> handlerType = (handler instanceof String) 
        ? getApplicationContext().getType((String) handler) 
        : handler.getClass();

    // 为 Handler 创建一个“方法查找器”,解析方法上的 @RequestMapping
    MethodMappingPredicate predicate = method -> {
        try {
            // 解析方法上的 @RequestMapping,返回映射信息
            return getMappingForMethod(method, handlerType) != null;
        } catch (Throwable ex) {
            throw new IllegalStateException("Failed to introspect method [" + method + "]", ex);
        }
    };

    // 1. 查找所有带 @RequestMapping 的方法
    Map<Method, T> methodMappings = MethodIntrospector.selectMethods(handlerType, predicate);

    // 2. 为每个方法注册映射(URL→HandlerMethod)
    for (Map.Entry<Method, T> entry : methodMappings.entrySet()) {
        Method method = entry.getKey();
        T mapping = entry.getValue();
        // 注册映射:将 URL 与 HandlerMethod 关联
        registerHandlerMethod(handler, method, mapping);
    }
}

2. 请求处理流程:查找 Handler 并封装执行链

当 HTTP 请求到达时,DispatcherServlet 调用 getHandler(request) 方法,RequestMappingHandlerMapping 按以下步骤查找 Handler 并封装 HandlerExecutionChain

步骤 1:getHandler → 入口方法
// AbstractHandlerMapping#getHandler(所有 HandlerMapping 的父类实现)
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 1. 查找匹配的 Handler(核心:URL→HandlerMethod)
    Object handler = getHandlerInternal(request);
    // 2. 若未找到,使用默认 Handler(可配置)
    if (handler == null) {
        handler = getDefaultHandler();
    }
    // 3. 若仍未找到,返回 null(DispatcherServlet 会返回 404)
    if (handler == null) {
        return null;
    }

    // 4. 若 Handler 是字符串(如 Bean 名),从容器中获取实际 Bean
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = getApplicationContext().getBean(handlerName);
    }

    // 5. 封装 HandlerExecutionChain(添加匹配的拦截器)
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

    // 6. 跨域请求处理(若为 CORS 请求,添加 CORS 拦截器)
    if (CorsUtils.isCorsRequest(request)) {
        CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        CorsConfiguration config = (globalConfig != null) ? globalConfig.combine(handlerConfig) : handlerConfig;
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    return executionChain;
}
步骤 2:getHandlerInternal → 查找 HandlerMethod
// AbstractHandlerMethodMapping#getHandlerInternal
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 1. 获取请求的 lookupPath(标准化 URL 路径,如 /user/list)
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    
    // 2. 加读锁,确保线程安全(映射关系构建时加写锁)
    this.mappingRegistry.acquireReadLock();
    try {
        // 3. 根据 lookupPath 查找对应的 HandlerMethod
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        // 4. 解析 HandlerMethod 中的 Bean(若为代理,获取目标 Bean)
        return (handlerMethod != null) ? handlerMethod.createWithResolvedBean() : null;
    } finally {
        // 5. 释放读锁
        this.mappingRegistry.releaseReadLock();
    }
}

// 核心:根据 lookupPath 查找 HandlerMethod
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    // 1. 查找精确匹配的映射
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, lookupPath, request);
    }
    // 2. 若无精确匹配,查找模式匹配(如 /user/*)
    if (matches.isEmpty()) {
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, lookupPath, request);
    }

    // 3. 选择最优匹配(如精确匹配优先于模式匹配)
    if (!matches.isEmpty()) {
        Match bestMatch = matches.get(0);
        // 4. 若有多个匹配,根据请求方法、参数等进一步筛选
        if (matches.size() > 1) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            matches.sort(comparator);
            bestMatch = matches.get(0);
        }
        // 5. 返回最优匹配的 HandlerMethod
        return bestMatch.handlerMethod;
    } else {
        // 无匹配,返回 null
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}
步骤 3:getHandlerExecutionChain → 添加拦截器
// AbstractHandlerMapping#getHandlerExecutionChain
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    // 1. 创建 HandlerExecutionChain(若 handler 已为链,直接使用;否则新建)
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain)
        ? (HandlerExecutionChain) handler
        : new HandlerExecutionChain(handler);

    // 2. 获取请求的 lookupPath
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);

    // 3. 遍历所有拦截器,添加与 lookupPath 匹配的拦截器
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            // 拦截器的路径匹配(如 /user/** 匹配 /user/list)
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        } else {
            // 非 MappedInterceptor,直接添加(全局拦截器)
            chain.addInterceptor(interceptor);
        }
    }

    return chain;
}

HandlerMapping 工作流程总结

HandlerMapping 的生命周期分为 “初始化” 和 “请求处理” 两个阶段,核心流程如下:

1. 初始化阶段(Spring 容器启动时)

  1. 扫描 HandlerRequestMappingHandlerMapping 扫描容器中所有带 @Controller@RequestMapping 的 Bean;
  2. 解析注解:解析类和方法上的 @RequestMapping 注解,提取 URL 路径、请求方法、参数等映射信息;
  3. 构建映射表:将 “URL 路径→HandlerMethod” 的映射关系存入 handlerMap,并加锁确保线程安全;
  4. 初始化拦截器:扫描容器中的拦截器,分类存入 adaptedInterceptors,供后续请求匹配。

2. 请求处理阶段(HTTP 请求到来时)

  1. 获取 lookupPath:标准化请求 URL,得到 lookupPath(如 /user/list);
  2. 查找 HandlerMethod:根据 lookupPath 从 handlerMap 中查找匹配的 HandlerMethod,优先精确匹配,再模式匹配;
  3. 封装执行链:创建 HandlerExecutionChain,添加与 lookupPath 匹配的拦截器;
  4. 跨域处理:若为 CORS 请求,添加 CORS 拦截器,处理跨域逻辑;
  5. 返回执行链:将 HandlerExecutionChain 返回给 DispatcherServlet,由后者继续调用 Handler 和拦截器。

常见问题与最佳实践

1. 多个 HandlerMapping 匹配同一请求,如何确定优先级?

  • 优先级规则:通过 order 属性控制,值越小,优先级越高(Ordered 接口);

  • 默认优先级mvc:annotation-driven 注册的 RequestMappingHandlerMapping 优先级高于手动配置的其他 HandlerMapping;

  • 解决方案:手动配置order属性,确保关键映射器优先执行:

    <bean class="org.springframework.web.servlet.handler.SimplerUrlHandlerMapping">
        <property name="order" value="0"/> <!-- 最高优先级 -->
        <property name="mappings">...</property>
    </bean>

2. 如何自定义 HandlerMapping?

若需扩展映射逻辑(如基于请求头、IP 地址匹配),可继承 AbstractHandlerMappingAbstractHandlerMethodMapping,重写以下方法:

  • getHandlerInternal:自定义 Handler 查找逻辑;
  • isHandler:自定义 Handler 判断规则;
  • getMappingForMethod:自定义方法级映射解析逻辑。
示例:基于 IP 地址的自定义 HandlerMapping
public class IpBasedHandlerMapping extends AbstractHandlerMethodMapping<IpMapping> {
    // 存储 IP 段→HandlerMethod 的映射
    private Map<String, HandlerMethod> ipHandlerMap = new HashMap<>();

    // 注册 IP 映射
    public void registerIpMapping(String ipPattern, HandlerMethod handlerMethod) {
        ipHandlerMap.put(ipPattern, handlerMethod);
    }

    // 重写:判断是否为 Handler(复用 RequestMappingHandlerMapping 的逻辑)
    @Override
    protected boolean isHandler(Class<?> beanType) {
        return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
    }

    // 重写:根据请求 IP 查找 HandlerMethod
    @Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        String clientIp = getClientIp(request); // 获取客户端 IP
        // 根据 IP 匹配 HandlerMethod
        for (Map.Entry<String, HandlerMethod> entry : ipHandlerMap.entrySet()) {
            if (matchesIp(clientIp, entry.getKey())) {
                return entry.getValue();
            }
        }
        return super.getHandlerInternal(request);
    }

    // IP 匹配逻辑(简化:支持 * 通配符)
    private boolean matchesIp(String clientIp, String ipPattern) {
        return clientIp.matches(ipPattern.replace("*", ".*"));
    }

    // 获取客户端 IP
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Real-IP");
        return ip == null ? request.getRemoteAddr() : ip;
    }

    // 其他抽象方法实现(省略)
    @Override
    protected IpMapping getMappingForMethod(Method method, Class<?> handlerType) { return null; }
    @Override
    protected void handleMatch(IpMapping mapping, String lookupPath, HttpServletRequest request) {}
    @Override
    protected HandlerMethod handleNoMatch(Set<IpMapping> mappings, String lookupPath, HttpServletRequest request) { return null; }
}

3. 最佳实践建议

  1. 优先使用 mvc:annotation-driven:自动注册 RequestMappingHandlerMapping,无需手动配置,支持注解驱动开发;
  2. 避免多个 HandlerMapping 冲突:若手动配置多个 HandlerMapping,通过 order 明确优先级,避免同一 URL 被多次匹配;
  3. 合理设计 URL 路径:使用 RESTful 风格 URL(如 /user/{id}),配合 @PathVariable,减少映射配置;
  4. 使用拦截器而非自定义 HandlerMapping:大部分场景下,拦截器(HandlerInterceptor)可实现请求过滤逻辑,无需自定义 HandlerMapping,降低复杂度。

总结

HandlerMapping 是 Spring MVC 路由机制的核心,通过 “初始化时构建映射表,请求时快速查找” 的模式,实现了请求与 Handler 的解耦。RequestMappingHandlerMapping 作为当前主流实现,通过解析 @RequestMapping 注解,支持精确的路径、方法、参数匹配,是注解驱动项目的首选