Spring学习总结(2.3)-Spring MVC:handlermapping

    上篇博客讲了DispathcerServlet的流转过程以及它是如何工作的,从这篇博客开始就开始深入到DispatcherServlet的内部看看它的几个主要的组件。那么这一篇就从HandlerMapping这个组件开始学习。

HandlerMapping

    首先这是一个接口,也就是可扩展。它的作用就是根据不同的请求去匹配对应的Handler,也就是根据请求匹配一个请求处理器。这个过程需要两个步骤:第一步,需要将Handler注册到HandlerMapping中;第二步,分析请求根据规则从已注册的Handler中匹配到对应的Handler,即Controller。默认情况下,SpringMvc为我们提供了几个默认的HandlerMapping的实现,通过优先级的次序决定执行的顺序。

HandlerMapping执行顺序

    在基于Spring MVC的Web应用程序中,我们可以为DispatcherServlet提供多个Handler- Mapping供其使用。DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping。如果当前的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping。否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止。

SimpleUrlHandlerMapping

     现在,通过这个实现类,我们来看看handlerMapping是如何注册和获取handler的。

public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {  
    
    private final Map<String, Object> urlMap = new HashMap<String, Object>();  
  
    // 通过属性配置URL到Bean名的映射  
    public void setMappings(Properties mappings) {  
        CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);  
    }  
  
    // 配置URL到Bean的映射  
    public void setUrlMap(Map<String, ?> urlMap) {  
        this.urlMap.putAll(urlMap);  
    }  
  
    public Map<String, ?> getUrlMap() {  
        return this.urlMap;  
    }  
  
    @Override  
    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 {  
            // 对于没一个配置的URL到处理器的映射,如果URL不是以斜线(/)开头,则追加斜线开头,则注册处理器  
            for (Map.Entry<String, Object> entry : urlMap.entrySet()) {  
                String url = entry.getKey();  
                Object handler = entry.getValue();  
                // 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);  
            }  
        }  
    }  
  
}  

    首先,它通过一个hashmap来存储请求和controller的对应关系,也就是保存注册信息。类都被Ioc容器管理者,HandlerMapping管理的是对应关系。其中key是http请求的path信息,value可以是一个字符串,或者是一个处理请求的HandlerExecutionChain,如果是String类型,则会将其视为Spring的bean名称。在HandlerMapping对象的创建中,IoC容器执行了一个容器回调方法setApplicationContext,在这个方法中调用initApplicationContext方法进行初始化,各个子类可以根据需求的不同覆写这个方法。关于handlerMap信息的注册就是在initApplicationContext方法中被执行的。
    取得Handler的方法在他的父类中,源码如下:

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {  
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);  
            //查找符合匹配规则的handler。可能的结果是HandlerExecutionChain对象或者是null  
    Object handler = lookupHandler(lookupPath, request);  
            //如果没有找到匹配的handler,则需要处理下default handler  
    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();  
        }  
                    //在getRootHandler和getDefaultHandler方法中,可能持有的是bean name。  
        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);  
        }  
    }  
            //如果handler还是为空,则抛出错误。  
    if (handler != null && this.mappedInterceptors != null) {  
        Set<HandlerInterceptor> mappedInterceptors =  
                this.mappedInterceptors.getInterceptors(lookupPath, this.pathMatcher);  
        if (!mappedInterceptors.isEmpty()) {  
            HandlerExecutionChain chain;  
            if (handler instanceof HandlerExecutionChain) {  
                chain = (HandlerExecutionChain) handler;  
            } else {  
                chain = new HandlerExecutionChain(handler);  
            }  
            chain.addInterceptors(mappedInterceptors.toArray(new HandlerInterceptor[mappedInterceptors.size()]));  
        }  
    }  
    if (handler != null && logger.isDebugEnabled()) {  
        logger.debug("Mapping [" + lookupPath + "] to handler '" + handler + "'");  
    }  
    else if (handler == null && logger.isTraceEnabled()) {  
        logger.trace("No handler mapping found for [" + lookupPath + "]");  
    }  
    return handler;  
} 

接口定义

    看过了这些之后我们再回头看看HandleraMapping接口的定义:

public interface HandlerMapping {
 
    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
 
    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
 
    String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
 
    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
 
    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
 
    public abstract HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
 
}

   
眼尖的同学可能就要说了,这个接口下取得handler的方法怎么返回值是HandlerExecutionChain而不是一个handler呢?HandlerExecutionChain这个类,从名字可以直观的看得出,这个对象是一个执行链的封装。熟悉Struts2的都知道,Action对象也是被层层拦截器包装,这里可以做个类比,说明SpringMVC确实是吸收了Struts2的部分设计思想。大家可以再看看源码,太长了我就不贴了。从源码中可以看出,一个实质执行对象,还有一堆拦截器。这不就是Struts2中的实现么,SpringMVC没有避嫌,还是采用了这种封装。得到HandlerExecutionChain这个执行链(execution chain)之后,下一步的处理将围绕其展开。至此,HandlerExecutionChain整个执行脉络也就清楚了:在真正调用其handler对象前,HandlerInterceptor接口实现类组成的数组将会被遍历,其preHandle方法会被依次调用,然后真正的handler对象将被调用。
    小结:这里主要的内容就是注册和获取的两个过程,以及SpringMvc对handler的一些封装。认真的读读源码,就能很清晰的看到这个类的整个执行过程。加深对SpringMvc的执行过程的了解,感觉还是相当不错的。

    原文作者:Spring MVC
    原文地址: https://blog.csdn.net/zhuojiajin/article/details/46292125
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞