美文网首页
HandlerMapping简介

HandlerMapping简介

作者: Hogantry | 来源:发表于2019-03-03 16:48 被阅读0次

1、HandlerMapping接口继承结构体系

B0373966-7A31-4C09-912F-724CFA9243D6.png

2、HandlerMapping接口功能

HandlerMapping接口中只定义了一个方法

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

该方法很简单,就是根据请求的request,获取HandlerExecutionChain对象。

3、HandlerMapping家族成员

从HandlerMapping接口继承结构体系图中,我们可以发现HandlerMapping家族可以分为两支,一支继承AbstractUrlHandlerMapping,一支继承AbstractHandlerMethodMapping,这两支都继承自AbstractHandlerMapping。其中AbstractUrlHandlerMapping在目前大部分的项目使用中,已经很少使用到,本篇文章会稍微分析下SimpleUrlHandlerMapping子类。重点分析AbstractHandlerMethodMapping,他就是我们经常使用的@RequestMapping注解会使用到的方式。下面我们先分析这个父类,再分别分析这两个分支。

4、AbstractHandlerMapping

4.1、AbstractHandlerMapping概述

AbstractHandlerMapping是HandlerMapping的抽象实现,采用模板模式设计了HandlerMapping的整体架构。其定义了getHandlerInternal方法,根据request来获取Handler,由子类来具体实现该方法。然后再根据request来获取相应的interceptors,整合从子类获取的Handler,组成HandlerExecutionChain对象返回。

4.2、AbstractHandlerMapping初始化

AbstractHandlerMapping继承了WebApplicationObjectSupport,初始化时会自动调用模板方法initApplicationContext,具体如下:

@Override
protected void initApplicationContext() throws BeansException {
    // 模板方法,暂无子类实现
    extendInterceptors(this.interceptors);
    // 从容器中获取实现了MappedInterceptor接口的对象,添加到adaptedInterceptors列表中
    detectMappedInterceptors(this.adaptedInterceptors);
     // 将interceptors(由子类添加)中的对象封装后添加到adaptedInterceptors列表中
    initInterceptors();
}

4.3 AbstractHandlerMapping的使用

AbstractHandlerMapping的模板模式就是从如下方法开始的

@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 模板模式方法,具体由子类实现
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = getApplicationContext().getBean(handlerName);
    }
    // 根据request从adaptedInterceptors中选择匹配的interceptors,与handler一起封装成HandlerExecutionChain对象
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    return executionChain;
}

下面具体分析两个分支AbstractUrlHandlerMapping和AbstractHandlerMethodMapping

5、AbstractUrlHandlerMapping

5.1 AbstractUrlHandlerMapping概述

AbstractUrlHandlerMapping实现了父类的模板方法getHandlerInternal

@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    Object handler = lookupHandler(lookupPath, request);
    if (handler == null) {
        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 = getApplicationContext().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方法,具体代码就不贴了,大体思路是说一下。AbstractUrlHandlerMapping类中定义了一个Map<String, Object>类型的变量handlerMap,用来存储url与handler的对应关系,lookupHandler方法就是根据url在该变量中找到相应的handler。其中url是可以根据正则表达式来查找的,所以lookupHandler方法中还会根据url可能会查找到多个,然后根据正则的匹配关系找到最符合的handler对象返回。那handlerMap变量中的数据又是从哪来的呢?带着这个问题我们继续分析下去。我们看到AbstractUrlHandlerMapping类中提供了两个注册handler方法

protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException 
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException 

这两个方法就是提供给子类注册handler了的,子类通过调用这两个注册方法就可以将url与handler的对应关系存储到handlerMap变量中了。那问题又来了,子类又是如何获取url与handler的对应关系的呢?针对AbstractUrlHandlerMapping的子类,我们只分析下SimpleUrlHandlerMapping类。

5.2 SimpleUrlHandlerMapping

5.5.1 SimpleUrlHandlerMapping的使用

上面也提过,目前大部分项目都是使用@RequestMapping的方式来定义Handler,SimpleUrlHandlerMapping类很少会使用到,所以这里先介绍下该类的使用,方便后续对其使用原理的讲解。

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="simpleUrlController">simleUrl</prop>
        </props>
    </property>
</bean>
<bean id="simleUrl" class="com.zhao.web.SimpleUrlController" />

SimpleUrlHandlerMapping的使用非常简单,如上在spring mvc的配置文件中指定好url与bean的对应关系即刻,其中该bean类需要实现Controller接口。

5.5.2 SimpleUrlHandlerMapping原理

如上定义的url与bean的对应关系,最终会被解析设置到SimpleUrlHandlerMapping类中的urlmap变量中。而SimpleUrlHandlerMapping类重写了父类AbstractHandlerMapping的initApplicationContext方法

@Override
public void initApplicationContext() throws BeansException {
    // 调用父类AbstractHandlerMapping的方法,主要功能是实现interceptor的初始化
    super.initApplicationContext();
    // 将本类中的urlmap注册到父类AbstractUrlHandlerMapping的handlerMap变量中
    registerHandlers(this.urlMap);
}

6、AbstractHandlerMethodMapping

6.1 AbstractHandlerMethodMapping概述

与AbstractUrlHandlerMapping一样,AbstractHandlerMethodMapping也是实现了父类的模板方法getHandlerInternal

@Override
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();
    }
}

代码很简单,主要的实现逻辑就是lookupHandlerMethod方法

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<Match>();
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }

    if (!matches.isEmpty()) {
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        Collections.sort(matches, comparator);
        if (logger.isTraceEnabled()) {
            logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
                    lookupPath + "] : " + matches);
        }
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
                        request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
            }
        }
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    }
    else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

其中T是来自AbstractHandlerMethodMapping类的定义,目前SpringMVC中只有RequestMappingInfo的实现。RequestMappingInfo类主要是对多种类型的请求匹配对象的封装,包括请求url、cookie、header等。

在分析lookupHandlerMethod方法的整体思路之前,我们还得知晓AbstractHandlerMethodMapping的内部类MappingRegistry。MappingRegistry类中定义了两个比较重要的变量,Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>()MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>()。其中mappingLookup变量保存了RequestMappingInfo与HandlerMethod的一一对应关系,而urlLookup变量则保存了url与RequestMappingInfo的对应关系,需要注意的是MultiValueMap类型的变量是可以一个key对应多个value的,也就是说urlLookup变量中,一个url可能对应多个RequestMappingInfo。

好,现在继续看lookupHandlerMethod方法,首选根据lookupPath(可以理解为url)获取到RequestMappingInfo,就是从urlLookup获取,可能返回多个对象,最后找到一个最符合的RequestMappingInfo对象,然后根据该对象再去获取到HandlerMethod对象返回。大体思路如此,具体细节不做更多分析。

与分析AbstractUrlHandlerMapping类时一致,这里的mappingLookup和urlLookup中的数据又是何时初始化的呢?AbstractHandlerMethodMapping实现了InitializingBean接口,所以spring容器会自动调用其afterPropertiesSet方法,该方法又交给initHandlerMethods方法完成具体的初始化。

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

    for (String beanName : beanNames) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            Class<?> beanType = null;
            try {
                beanType = getApplicationContext().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());
}

逻辑不是很复杂,首先从spring容器中获取所有的bean,然后进行过滤与处理。主要关注其中的两个方法,isHandler(beanType)detectHandlerMethods(beanName)。其中isHandler(beanType)方法由子类RequestMappingHandlerMapping实现,用于对bean进行过滤,判断是否包含Controller或者RequestMapping注解。

@Override
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

detectHandlerMethods(beanName)方法,则根据筛选出的bean,进行进一步的处理,也就是对上述的mappingLookup和urlLookup两个变量的数据填充。

protected void detectHandlerMethods(final Object handler) {
    Class<?> handlerType = (handler instanceof String ?
            getApplicationContext().getType((String) handler) : handler.getClass());
    // CGLib动态代理的特殊处理
    final Class<?> userType = ClassUtils.getUserClass(handlerType);
    // methods包含了bean中所有符合条件的method与相关的RequestMappingInfo键值对
    Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
            new MethodIntrospector.MetadataLookup<T>() {
                @Override
                public T inspect(Method method) {
                    try {
                        // 如果method有@RequestMapping注解,则返回由注解得到的封装好的RequestMappingInfo对象,否则返回null
                        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();
    // 向mappingLookup和urlLookup两个变量中填充数据
        registerHandlerMethod(handler, invocableMethod, mapping);
    }
}

相关文章

网友评论

      本文标题:HandlerMapping简介

      本文链接:https://www.haomeiwen.com/subject/fnqmuqtx.html