11

前言

本文基于之前的文章【深入浅出spring】Spring MVC 流程解析 继续从源码角度分析 spring MVC 的原理。Spring MVC 的运行流程在上面的文章中已经介绍了,这里详细介绍 url --> handler 的映射过程,即 hanndler mapping

总流程

回顾下上文介绍的流程1:

首先用户发送请求,DispatcherServlet实现了Servlet接口,整个请求处理流:HttpServlet.service -> FrameworkServlet.doGet -> FrameworkServlet.processRequest -> DispatcherServlet.doService -> DispatcherServlet.doDispatch

相关源码如下:

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerExecutionChain mappedHandler = null;
        ......
        mappedHandler = getHandler(processedRequest);
        ......
    }
    
    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping hm : this.handlerMappings) {
                if (logger.isTraceEnabled()) {
                    logger.trace(
                            "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
                }
                HandlerExecutionChain handler = hm.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }

        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        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;
    }

拆解doDispatch中相关步骤,和 handler mapping 相关的流程可慨括为DispatcherServlet.doDispatch --> DispatcherServlet.getHandler --> AbstractHandlerMapping.getHandler --> xxx.getHandlerInternal,这里的xxxAbstractHandlerMapping的子类或实现类,核心方法getHandlerInternal是抽象方法,具体实现由其子类实现。

spring mvc中默认的HandlerMapping有4种(即此处 handlerMappings 列表中的成员):

  • RequestMappingHandlerMapping
  • BeanNameUrlHandlerMapping
  • SimpleUrlHandlerMapping
  • WelcomePageHandlerMapping

下面依次分析每个HandlerMapping的原理。附录有具体的类关系图

RequestMappingHandlerMapping

处理器获取

由附录中的类关系图可知,此类继承自AbstractHandlerMethodMapping,如上节分析 DispatcherServlet 中的 getHandler 方法最终回调用子类的 getHandlerInternal 方法,RequestMappingHandlerMapping没有重写getHandlerInternal,故这里直接调用AbstractHandlerMethodMapping.getHandlerInternal,源码如下:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        if (logger.isDebugEnabled()) {
            logger.debug("Looking up handler method for path " + lookupPath);
        }
        this.mappingRegistry.acquireReadLock();
        try {
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            if (logger.isDebugEnabled()) {
                if (handlerMethod != null) {
                    logger.debug("Returning handler method [" + handlerMethod + "]");
                }
                else {
                    logger.debug("Did not find handler method for [" + lookupPath + "]");
                }
            }
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<>();
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
            addMatchingMappings(directPathMatches, matches, request);
        }
        ......
    }

上述方法是根据请求url获取对应处理方法,然后封装成 HandlerExecutionChain, 即HandlerExecutionChain 中的 handler 是 HandlerMethod 类型。 这里不得不提一下 MappingRegistry,是 RequestMappingHandlerMapping 中重要的类,lookupHandlerMethod 的核心逻辑就是从 MappingRegistry 中获取 url 对应的 HandlerMethod。

class MappingRegistry {

        private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

        private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

        private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

        private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

        private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

        private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
}

对于 RequestMappingHandlerMapping,这里的 T = RequestMappingInfo。联结 urlLookup 和 mappingLookup 即可实现从 url --> HandlerMethod 的映射,实现获取对应请求的处理方法。
不难发现,这里的 url --> HandlerMethod 的寻找过程本质就是 Map 的 key-value,那么这些 map,即 MappingRegistry 的实例是什么时候初始化的呢?

映射关系写入

RequestMappingHandlerMapping 关系图中可以发现RequestMappingHandlerMapping实现的接口InitializingBeanInitializingBean的作用见Spring Bean 初始化之InitializingBean, init-method 和 PostConstruct,个人感觉还是讲的很清楚了。跟踪源代码可以发现,MappingRegistry的初始化入口是AbstractHandlerMethodMapping.initHandlerMethods,源码如下:

    protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
                obtainApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    beanType = obtainApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
                if (beanType != null && isHandler(beanType)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

可以看到其中过滤 bean 使用了RequestMappingHandlerMapping.isHandler,对注解了@Controller, @RequestMapping的 bean 才会进行后续操作。即核心方法detectHandlerMethods,通过反射的方式,将@RequestMapping中的url和对应的处理方法注册到mappingRegistry中,从而实现了上面所属的映射查找。

    @Override
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }
    
    protected void detectHandlerMethods(final Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            final Class<?> userType = ClassUtils.getUserClass(handlerType);
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            if (logger.isDebugEnabled()) {
                logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
            }
            for (Map.Entry<Method, T> entry : methods.entrySet()) {
                Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
                T mapping = entry.getValue();
                // 注册到mappingRegistry中
                registerHandlerMethod(handler, invocableMethod, mapping);
            }
        }
    }

总结

  • RequestMappingHandlerMapping 没有重写 getHandlerInternal,直接使用父类 AbstractHandlerMethodMappinggetHandlerInternal
  • RequestMappingHandlerMapping 会将 @Controller, @RequestMapping 注解的类方法注册为 handler,每个请求url可以对应一个 handler method 来处理
  • RequestMappingHandlerMapping 的映射关系由 MappingRegistry 维护,通过多个map联结,即可找到请求对应的处理器
  • RequestMappingHandlerMapping 的映射关系初始化入口是 afterProperties 方法,因为其实现了接口 InitializingBean

SimpleUrlHandlerMapping

处理器获取

此类的getHandlerInternal逻辑由其父类AbstractUrlHandlerMapping实现

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        Object handler = lookupHandler(lookupPath, request);
        if (handler == null) {
            // We need to care for the default handler directly, since we need to
            // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
            Object rawHandler = null;
            if ("/".equals(lookupPath)) {
                rawHandler = getRootHandler();
            }
            if (rawHandler == null) {
                rawHandler = getDefaultHandler();
            }
            if (rawHandler != null) {
                // Bean name or resolved handler?
                if (rawHandler instanceof String) {
                    String handlerName = (String) rawHandler;
                    rawHandler = obtainApplicationContext().getBean(handlerName);
                }
                validateHandler(rawHandler, request);
                handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
            }
        }
        if (handler != null && logger.isDebugEnabled()) {
            logger.debug("Mapping [" + lookupPath + "] to " + handler);
        }
        else if (handler == null && logger.isTraceEnabled()) {
            logger.trace("No handler mapping found for [" + lookupPath + "]");
        }
        return handler;
    }

其中核心逻辑是Object lookupHandler(String urlPath, HttpServletRequest request),这个方法的寻找逻辑根据优先级简单概括为如下几步:

  1. 直接从Map<String, Object> handlerMap中根据请求 url 获取对应的处理方法
  2. 通过AntPathMatcherhandlerMap中获取匹配最好的url,并获取对应的处理方法

同样,这里只是一个获取查找的过场,核心点是handlerMap的初始化逻辑。

映射关系写入

从类图关系中可以发现,SimpleUrlHandlerMapping实现了ApplicationContextAware接口,从ApplicationContextAwareProcessor.invokeAwareInterfaces可以发现,此接口的setApplicationContext会在bean初始化的时候被调用

private void invokeAwareInterfaces(Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof EnvironmentAware) {
                ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
            }
            if (bean instanceof EmbeddedValueResolverAware) {
                ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
            }
            if (bean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
            }
            if (bean instanceof ApplicationEventPublisherAware) {
                ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
            }
            if (bean instanceof MessageSourceAware) {
                ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
            }
            if (bean instanceof ApplicationContextAware) {
                ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
            }
        }
    }
    
    
    // SimpleUrlHandlerMapping的具体实现
    public void initApplicationContext() throws BeansException {
        super.initApplicationContext();
        registerHandlers(this.urlMap);
    }
    
    protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
        if (urlMap.isEmpty()) {
            logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
        }
        else {
            urlMap.forEach((url, handler) -> {
                // Prepend with slash if not already present.
                if (!url.startsWith("/")) {
                    url = "/" + url;
                }
                // Remove whitespace from handler bean name.
                if (handler instanceof String) {
                    handler = ((String) handler).trim();
                }
                registerHandler(url, handler);
            });
        }
    }

总结

  • 通过ApplicationContextAware.setApplicationContext注册 url - handler 的映射,这里的 handler 对应了一个处理类,调用方法handleRequest处理,而不像RequestMappingHandlerMapping,是类中的一个方法。
  • SimpleUrlHandlerMapping处理的 url 比如以/开头
  • SimpleUrlHandlerMapping的 url - handler 映射来源来自成员urlMap,故需要通过配置注入,例子如下:
<bean id="helloController" class="com.raistudies.ui.controller.HelloController"/>

  <bean id="urlHandler" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="urlMap">
            <map>
                <entry key="/hello.htm" value-ref="helloController"/>
                <entry key="/sayHello*" value-ref="helloController"/>
                <entry key="/welcome.html" value-ref="helloController"/>
                <entry key="/welcomeUser*" value-ref="helloController"/>
            </map>
        </property>
    </bean>
  • SimpleUrlHandlerMapping的初始化再WebMvcAutoConfiguration中完成:
    @Bean
    public SimpleUrlHandlerMapping faviconHandlerMapping() {
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
        mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler()));
        return mapping;
    }

WelcomePageHandlerMapping

获取处理器

首先来看下getHandlerInternal方法

public Object getHandlerInternal(HttpServletRequest request) throws Exception {
        for (MediaType mediaType : getAcceptedMediaTypes(request)) {
            if (mediaType.includes(MediaType.TEXT_HTML)) {
                return super.getHandlerInternal(request);
            }
        }
        return null;
    }

可以看到,逻辑比较简单,请求的 media type 需为 text/html, 然后执行父类的方法AbstractUrlHandlerMapping.getHandlerInternal

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        Object handler = lookupHandler(lookupPath, request);
        if (handler == null) {
            // We need to care for the default handler directly, since we need to
            // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
            Object rawHandler = null;
            if ("/".equals(lookupPath)) {
                rawHandler = getRootHandler();
            }
            if (rawHandler == null) {
                rawHandler = getDefaultHandler();
            }
            if (rawHandler != null) {
                // Bean name or resolved handler?
                if (rawHandler instanceof String) {
                    String handlerName = (String) rawHandler;
                    rawHandler = obtainApplicationContext().getBean(handlerName);
                }
                validateHandler(rawHandler, request);
                handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
            }
        }
        if (handler != null && logger.isDebugEnabled()) {
            logger.debug("Mapping [" + lookupPath + "] to " + handler);
        }
        else if (handler == null && logger.isTraceEnabled()) {
            logger.trace("No handler mapping found for [" + lookupPath + "]");
        }
        return handler;
    }

从源码中可以看到,先通过lookupHandler寻找处理方法,若找不到且请求为/,则返回rootHandler

映射关系写入

再看下WebMvcAutoConfigurationWelcomePageHandlerMapping的初始化源码,这里的getWelcomPage会在如下路径下寻找:

  • classpath:/META-INF/resources/index.html
  • classpath:/resources/index.html
  • classpath:/static/index.html
  • classpath:/public/index.html
        @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(
                ApplicationContext applicationContext) {
            return new WelcomePageHandlerMapping(
                    new TemplateAvailabilityProviders(applicationContext),
                    applicationContext, getWelcomePage(),
                    this.mvcProperties.getStaticPathPattern());
        }

总结

  • WelcomePageHandlerMapping 默认通过 WebMvcAutoConfiguration 初始化,将处理器直接注册为rootHandler,默认处理请求/,默认返回规定路径下的index.html

BeanNameUrlHandlerMapping

获取处理器

同样的先看getHandlerInternal方法,BeanNameUrlHandlerMapping和其父类AbstractDetectingUrlHandlerMapping都没有重写,故getHandlerInternal执行的是父类AbstractUrlHandlerMapping的方法,逻辑同SimpleUrlHandlerMapping

映射关系写入

不同点是handlerMap的初始化。通过源码阅读,发现AbstractDetectingUrlHandlerMapping重写了initApplicationContext,具体逻辑如下:

    @Override
    public void initApplicationContext() throws ApplicationContextException {
        super.initApplicationContext();
        detectHandlers();
    }
    
    protected void detectHandlers() throws BeansException {
        ApplicationContext applicationContext = obtainApplicationContext();
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for URL mappings in application context: " + applicationContext);
        }
        String[] beanNames = (this.detectHandlersInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
                applicationContext.getBeanNamesForType(Object.class));

        // Take any bean name that we can determine URLs for.
        for (String beanName : beanNames) {
            String[] urls = determineUrlsForHandler(beanName);
            if (!ObjectUtils.isEmpty(urls)) {
                // URL paths found: Let's consider it a handler.
                registerHandler(urls, beanName);
            }
            else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
                }
            }
        }
    }
    
    // BeanNameUrlHandlerMapping
    @Override
    protected String[] determineUrlsForHandler(String beanName) {
        List<String> urls = new ArrayList<>();
        if (beanName.startsWith("/")) {
            urls.add(beanName);
        }
        String[] aliases = obtainApplicationContext().getAliases(beanName);
        for (String alias : aliases) {
            if (alias.startsWith("/")) {
                urls.add(alias);
            }
        }
        return StringUtils.toStringArray(urls);
    }

根据源码不难发现,对于名称以/开头的bean,会被注册到handlerMap中,key即为bean name,value即为对应的bean,处理请求的时候会调用bean的handleRequest方法.

配置示例:

bean name="/hello.htm" class="com.raistudies.ui.comtroller.HelloController"/>
<bean name="/sayHello*" class="com.raistudies.ui.comtroller.HelloController"/>
<bean id="urlHandler" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

总结

  • 默认的handlerMapping有4类:RequestMappingHandlerMapping, BeanNameUrlHandlerMapping, SimpleUrlHandlerMapping, WelcomePageHandlerMapping
  • RequestMappingHandlerMapping的映射纬度是请求 --> 方法,另外3个都是请求 --> 类RequestMappingHandlerMapping的应用场景丰富,处理动态请求的最佳实践,另外3个基本上用来处理静态资源的
  • BeanNameUrlHandlerMapping, SimpleUrlHandlerMapping 原理很接近,最大的区别是BeanNameUrlHandlerMapping的bean名称就是url,必须以/开头,因此相比SimpleUrlHandlerMapping少了一步 url --> handler实例 的配置
  • 当容器中有多个handlerMapping实例时,会根据实例的优先级从高到底依次遍历,一旦找到对应 handler 后就会结束。优先级通过mapping的order属性设置,值越小,优先级越高。若不显示设置,默认就是max,即优先级最低。范围 Ordered.HIGHEST_PRECEDENCE -- Ordered.LOWEST_PRECEDENCE

示例

  • 代码下载地址:handler_mapping_sample
  • WelcomePageHandlerMapping示例请求:http://localhost:8080/,会渲染 static 目录下的 index.html
  • BeanNameUrlHandlerMapping示例请求:http://localhost:8080/beanNameUrl.html,对应的处理器BeanNameController
  • SimpleUrlHandlerMapping示例请求:http://localhost:8080/simpleUrl.html,对应的处理器SimpleUrlController,配置ControllerConfig
  • RequestMappingHandlerMapping示例请求:http://localhost:8080/data/model,对应的处理方法DemoController.getModel

附录

HanddlerMapping 关系图

clipboard.png

RequestMappingHandlerMapping 关系图

clipboard.png

BeanNameUrlHandlerMapping 关系图

clipboard.png

SimpleUrlHandlerMapping 关系图

clipboard.png

WelcomePageHandlerMapping 关系图

clipboard.png


skyarthur
1.6k 声望1.3k 粉丝

技术支持业务,技术增强业务,技术驱动业务