HandlerMapping

前言

我们已经知道HandlerMapping的主要作用是通过request找到Handler,Spring MVC中HandlerMapping的实现有很多,比如SimpleUrlHandlerMapping、BeanNameUrlHandlerMapping、DefaultAnnotationHandlerMapping以及RequestMappingHandlerMapping等等,本篇文章就来分析下最常用的RequestMappingHandlerMapping。

RequestMappingHandlerMapping

我们先来看下RequestMappingHandlerMapping的继承关系,然后从继承关系从上到下依次分析。

RequestMappingHandlerMapping继承关系

HandlerMapping

HandlerMapping我们已经分析过了,不过最好还是回顾下接口的定义

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 MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

AbstractHandlerMapping

创建过程

AbstractHandlerMapping继承了WebApplicationObjectSupport,初始化的时候会调用initApplicationContext,其内部主要是初始化interceptors,供以后使用。

Interceptor分为两种

  • 一种是直接或间接实现了Interceptor接口的普通拦截器,它对所有的请求都会拦截
  • 另一种是MappedInterceptor,MappedInterceptor内部包含一个第一种的拦截器,但是它有一个matches方法,只有符合要求的请求它才会拦截
    protected void initApplicationContext() throws BeansException {
        // 模板方法,用于子类扩展或修改interceptors,不过并没有子类实现
        extendInterceptors(this.interceptors);
        // 将ApplicationContext中所有的MappedInterceptor添加到adaptedInterceptors中
        detectMappedInterceptors(this.adaptedInterceptors);
        // 将interceptors中的元素添加到adaptedInterceptors中
        initInterceptors();
    }
使用过程

AbstractHandlerMapping实现了getHandler方法,主要分为两个部分

  1. 获取Handler,这部分是模板方法,由子类实现
  2. 将创建过程中初始化的interceptors和第一步获取的handler一起组成HandlerExecutionChain
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 模板方法,由子类实现
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            // 获取默认Handler,默认为null
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // handler是beanName,就从ApplicationContext中取出对应的bean
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }

        // 通过handler和request获取HandlerExecutionChain
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        // 如果是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;
    }

现在我们再来看下获取HandlerExecutionChain的具体过程,第一步是构造HandlerExecutionChain,第二步是根据情况添加拦截器。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        // 构造HandlerExecutionChain
        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

        // 添加拦截器
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            // 添加符合条件的MappedInterceptor
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            } else {
                // 非MappedInterceptor都添加进来
                chain.addInterceptor(interceptor);
            }
        }
        return chain;
    }

AbstractHandlerMethodMapping

我们先来解释下AbstractHandlerMethodMapping中泛型T的含义,官方定义为The mapping for a HandlerMethod containing the conditions needed to match the handler method to incoming request,也就是它是一个包含了各种条件(包括url、HttpMethod、Header等)的一个类,通过这个类可以找到一个HandlerMethod(一种Handler),T默认的实现是RequestMappingInfo。

创建过程

AbstractHandlerMethodMapping实现了InitializingBean,所以它的初始化入口是
afterPropertiesSet,其内部只是简单地调用了initHandlerMethods方法,并没有做其他操作,下面我们来看下initHandlerMethods的具体实现

    protected void initHandlerMethods() {
        // 找到ApplicationContext中所有的beanName
        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 {
                    // 获取到bean
                    beanType = getApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
                // isHandler是模板方法
                if (beanType != null && isHandler(beanType)) {
                    // 将bean中符合条件的方法注册到mappingRegistry
                    detectHandlerMethods(beanName);
                }
            }
        }
        // 对handler做一些初始化操作,模板方法,并没有子类实现
        handlerMethodsInitialized(getHandlerMethods());
    }

这里说一下isHandler方法,它由RequestMappingHandlerMapping实现,可以看出它的判断依据是类上是否有@Controller或@RequestMapping注解。

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

下面我们再来具体看下detectHandlerMethods方法

    protected void detectHandlerMethods(final Object handler) {
        // 获取handler的类
        Class<?> handlerType = (handler instanceof String ?
                getApplicationContext().getType((String) handler) : handler.getClass());
        // 如果是cglib代理的子类,则返回父类,否则直接返回传入的类
        final Class<?> userType = ClassUtils.getUserClass(handlerType);

        // 获取所有符合条件的方法以及对应的匹配条件
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                new MethodIntrospector.MetadataLookup<T>() {
                    @Override
                    public T inspect(Method method) {
                        try {
                            // 模板方法,返回方法对应的匹配条件
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    }
                });
        // 注册到mappingRegistry
        for (Map.Entry<Method, T> entry : methods.entrySet()) {
            Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
            T mapping = entry.getValue();
            registerHandlerMethod(handler, invocableMethod, mapping);
        }
    }

其实我们可以总结出AbstractHandlerMethodMapping的创建过程其实就是根据代码里@Controller和@RequestMapping注解,将符合条件的方法包装成HandlerMethod,并且建立HandlerMethod和T的对应关系。

AbstractHandlerMethodMapping的使用过程

从AbstractHandlerMapping的使用过程我们知道,AbstractHandlerMethodMapping使用的入口是getHandlerInternal方法

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        // 获取lookupPath,可以简单理解成url
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        this.mappingRegistry.acquireReadLock();
        try {
            // 通过lookupPath和request中的一些条件(比如是GET请求还是POST请求等)找到HandlerMethod
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

RequestMappingInfoHandlerMapping

RequestMappingHandlerMapping

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容