zoukankan      html  css  js  c++  java
  • SpringMVC源码分析--HandlerMappings

    之前分析过SpringMVC中的DispatcherServlet,分析了SpringMVC处理请求的过程。但忽略了一些DispatcherServlet协助请求处理的组件,例如SpringMVC中的HandlerMappingHandlerAdapterViewResolvers等等。

    HandlerMappings

    HandlerMappingsDispathServlet中主要作用是为请求的urlpath匹配对应的Controller,建立一个映射关系,根据请求查找HandlerInterceptorHandlerMappings将请求传递到HandlerExecutionChain上,HandlerExecutionChain包含了一个能够处理该请求的处理器,还可以包含拦截改请求的拦截器。

    在没有处理器映射相关配置情况下,DispatcherServlet会为你创建一个BeanNameUrlHandlerMapping作为默认映射的配置。在DispatchServlet.properties文件中对于HandlerMapping的默认配置是:

    org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
        org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
    

    HandlerMapping的配置策略一般分为配置式BeanNameUrlHandlerMapping和注解式DefaultAnnotationHandlerMapping。不过DefaultAnnotationHandlerMapping已经被放弃了,取代它的是RequestMappingHandlerMapping,不知道为啥SpringMVC这个默认配置尚未做修改。

    AbstractHandlerMapping

    AbstractHandlerMappingHandlerMapping的抽象实现,是所有HandlerMapping实现类的父类。

    AbstractHandlerMapping的作用是是为了初始化InterceptorsAbstractHandlerMapping重写了WebApplicationObjectSupportinitApplicationContext方法。

        protected void initApplicationContext() throws BeansException {
            extendInterceptors(this.interceptors);
            detectMappedInterceptors(this.adaptedInterceptors);
            initInterceptors();
        }
    
    • extendInterceptors方法,Springmvc并没有做出具体实现,这里留下一个拓展,子类可以重写这个模板方法,为子类添加或者修改Interceptors

    • detectMappedInterceptors方法将SpringMVC容器中所有MappedInterceptor类的bean添加到adaptedInterceptors中。

    • 最后调用initInterceptors初始化拦截器。遍历interceptorsWebRequestInterceptorHandlerInterceptor类型的拦截器添加到adaptedInterceptors中。

    HandlerMapping通过getHandler方法来获取请求的处理器Handler和拦截器Interceptor。在getHandlerExecutionChain方法中将遍历之前初始化的adaptedInterceptors,为当前的请求选择对应的MappedInterceptorsadaptedInterceptors

    AbstractUrlHandlerMapping

    AbstractUrlHandlerMapping

    AbstractUrlHandlerMapping继承于AbstractHandlerMapping,它是通过URL来匹配具体的HandlerAbstractUrlHandlerMapping维护一个handlerMap来存储UrlHandler的映射关系。

    AbstractUrlHandlerMapping重写了AbstractHandlerMapping类中的getHandlerInternal方法。HandlerMapping通过getHandler方法,就会调用这里的getHandlerInternal方法来获取HandlergetHandlerInternal方法中关键调用lookupHandler方法去获取handler

        protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
            // Direct match?
            Object handler = this.handlerMap.get(urlPath);
            if (handler != null) {
                // Bean name or resolved handler?
                if (handler instanceof String) {
                    String handlerName = (String) handler;
                    handler = getApplicationContext().getBean(handlerName);
                }
                validateHandler(handler, request);
                return buildPathExposingHandler(handler, urlPath, urlPath, null);
            }
    
            // Pattern match?
            List<String> matchingPatterns = new ArrayList<String>();
            for (String registeredPattern : this.handlerMap.keySet()) {
                if (getPathMatcher().match(registeredPattern, urlPath)) {
                    matchingPatterns.add(registeredPattern);
                }
                else if (useTrailingSlashMatch()) {
                    if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
                        matchingPatterns.add(registeredPattern +"/");
                    }
                }
            }
    
            String bestMatch = null;
            Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
            if (!matchingPatterns.isEmpty()) {
                Collections.sort(matchingPatterns, patternComparator);
                if (logger.isDebugEnabled()) {
                    logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
                }
                bestMatch = matchingPatterns.get(0);
            }
            if (bestMatch != null) {
                handler = this.handlerMap.get(bestMatch);
                if (handler == null) {
                    if (bestMatch.endsWith("/")) {
                        handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
                    }
                    if (handler == null) {
                        throw new IllegalStateException(
                                "Could not find handler for best pattern match [" + bestMatch + "]");
                    }
                }
                // Bean name or resolved handler?
                if (handler instanceof String) {
                    String handlerName = (String) handler;
                    handler = getApplicationContext().getBean(handlerName);
                }
                validateHandler(handler, request);
                String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
    
                // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
                // for all of them
                Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
                for (String matchingPattern : matchingPatterns) {
                    if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
                        Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
                        Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
                        uriTemplateVariables.putAll(decodedVars);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
                }
                return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
            }
    
            // No handler found...
            return null;
        }
    
    • 首先调用lookupHandler方法来获取handler。在lookupHandler方法中,先通过URLhandlerMap查找是否有合适的handler
    • 如果没有获取到handler,遍历handlerMap利用正则匹配的方法,找到符合要求的handlers(有可能是多个)。
    • 正则匹配是采用Ant风格,将会通过排序筛选出一个匹配程度最高的Handler
    • 最后调用buildPathExposingHandler方法构建一个handler,添加PathExposingHandlerInterceptorUriTemplateVariablesHandlerInterceptor两个拦截器并返回。

    上面介绍获取handler的过程中,会先从handlerMap查找。下面看一下handlerMap是如何初始化的。AbstractUrlHandlerMapping是通过registerHandler初始化handlerMap的。AbstractUrlHandlerMapping共有两个registerHandler方法。分别是注册多个url到一个handler和注册一个url到一个handler。首先判断handlerMap是否有此handler。如果存在的话,判断是否一致,不一致则抛出异常,如果不存在的话,如果url//*,则,返回root handlerdefault handler,如果不是将添加到handlerMap中。

    SimpleUrlHandlerMapping

    SimpleUrlHandlerMapping继承于AbstractUrlHandlerMappingSimpleUrlHandlerMapping重写了父类AbstractHandlerMapping中的初始化方法initApplicationContext。在initApplicationContext方法中调用registerHandlers方法。

        protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
            if (urlMap.isEmpty()) {
                logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
            }
            else {
                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);
                }
            }
        }
    

    判断是url是否以/开头,如果不是,默认补齐/,确保所有的url都是以/开头,然后依次调用父类的registerHandler方法注册到AbstractUrlHandlerMapping中的handlerMap

    在使用SimpleUrlHandlerMapping时,需要在注册的时候配置其urlmap否则会抛异常。

    AbstractDetectingUrlHandlerMapping

    AbstractDetectingUrlHandlerMapping类继承于AbstractUrlHandlerMapping类,重写了initApplicationContext方法,在initApplicationContext方法中调用了detectHandlers方法。

        protected void detectHandlers() throws BeansException {
            if (logger.isDebugEnabled()) {
                logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
            }
            String[] beanNames = (this.detectHandlersInAncestorContexts ?
                    BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                    getApplicationContext().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");
                    }
                }
            }
        }
    
    

    获取所有容器的beanNames,遍历所有的beanName,调用determineUrlsForHandler方法解析url,这里的determineUrlsForHandler也是运用了模板方法设计模式,具体的实现在其子类中,如果解析到子类,将注册到父类的handlerMap中。

    BeanNameUrlHandlerMapping

    BeanNameUrlHandlerMapping类的类图大致如下:

    BeanNameUrlHandlerMapping类继承于AbstractDetectingUrlHandlerMapping类。重写了父类中的determineUrlsForHandler方法。

        protected String[] determineUrlsForHandler(String beanName) {
            List<String> urls = new ArrayList<String>();
            if (beanName.startsWith("/")) {
                urls.add(beanName);
            }
            String[] aliases = getApplicationContext().getAliases(beanName);
            for (String alias : aliases) {
                if (alias.startsWith("/")) {
                    urls.add(alias);
                }
            }
            return StringUtils.toStringArray(urls);
        }
    

    其通过beanName解析Url规则也很简单,判断beanName是否以/开头。

    BeanNameUrlHandlerMappingSpringMVC的默认映射配置。

    AbstractHandlerMethodMapping

    通常我们也习惯于用@Controller@Re questMapping来定义HandlerAbstractHandlerMethodMapping可以将method作为Handler来使用。

    AbstractHandlerMethodMapping实现了InitializingBean接口,实现了afterPropertiesSet方法。当容器启动的时候会调用initHandlerMethods注册委托handler中的方法。

    
        public void 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());
        }
    

    initHandlerMethods方法中,做了以下工作:

    • 首先通过BeanFactoryUtils扫描应用上下文,获取所有的bean
    • 遍历所有的beanName,调用isHandler方法判断是目标bean是否包含@Controller@RequestMapping注解。
    • 对于带有@Controller@RequestMapping注解的类,调用detectHandlerMethods委托处理,获取所有的method,并调用registerHandlerMethod注册所有的方法。

    detectHandlerMethods方法负责将Handler保存到Map中。

        protected void detectHandlerMethods(final Object handler) {
           //  获取handler的类型
            Class<?> handlerType = (handler instanceof String ?
                    getApplicationContext().getType((String) handler) : handler.getClass());
            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);
                            }
                        }
                    });
    
            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();
                registerHandlerMethod(handler, invocableMethod, mapping);
            }
        }
        
        
    

    selectMethods方法中重写了MetadataLookup中的inspect方法,inspect方法中调用了子类RequestMappingHandlerMapping实现了getMappingForMethod模板方法,用于构建RequestMappingInfo

    public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
            final Map<Method, T> methodMap = new LinkedHashMap<Method, T>();
            Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
            Class<?> specificHandlerType = null;
    
            if (!Proxy.isProxyClass(targetType)) {
                handlerTypes.add(targetType);
                specificHandlerType = targetType;
            }
            handlerTypes.addAll(Arrays.asList(targetType.getInterfaces()));
    
            for (Class<?> currentHandlerType : handlerTypes) {
                final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
    
                ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
                    @Override
                    public void doWith(Method method) {
                        Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
                        T result = metadataLookup.inspect(specificMethod);
                        if (result != null) {
                            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                            if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
                                methodMap.put(specificMethod, result);
                            }
                        }
                    }
                }, ReflectionUtils.USER_DECLARED_METHODS);
            }
    
            return methodMap;
        }
    
    

    selectMethods通过反射获取所有的方法,重写了doWith方法,将handler中的method和请求对应的RequestMappingInfo保存到methodMap中。

    最终detectHandlerMethods将遍历这个methodMap,调用registerHandlerMethod注册HandlerMethodMappingRegistry

    AbstractHandlerMethodMapping类中,有个内部类MappingRegistry,用来存储mapping和 handler methods注册关系,并提供了并发访问方法。

    AbstractHandlerMethodMapping通过getHandlerInternal来为一个请求选择对应的handler

        protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
           // 根据request获取对应的urlpath
            String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
            if (logger.isDebugEnabled()) {
                logger.debug("Looking up handler method for path " + lookupPath);
            }
            // 获取读锁
            this.mappingRegistry.acquireReadLock();
            try {
              // 调用lookupHandlerMethod方法获取请求对应的HandlerMethod
                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>();
            // 通过lookupPath获取所有匹配到的path
            List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
            if (directPathMatches != null) {
               // 将匹配条件添加到matches
                addMatchingMappings(directPathMatches, matches, request);
            }
            if (matches.isEmpty()) {
                // 如果没有匹配条件,将所有的匹配条件都加入matches
                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 + "}");
                    }
                }
                // 将lookupPath设为请求request的PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE属性
                handleMatch(bestMatch.mapping, lookupPath, request);
                return bestMatch.handlerMethod;
            }
            else {
                return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
            }
        }
    
    

    整个过程以Match作为载体,Match是个内部类,封装了匹配条件和handlerMethod两个属性,默认的实现是将lookupPath设置为请求的属性。

    总结

    本文从源码角度上分析了HandlerMapping的各种实现。主要功能是为请求找到合适的handlerinterceptors,并组合成HandlerExecutionChain。查找handler的过程通过getHandlerInternal方法实现,每个子类都其不同的实现。

    所有的HandlerMapping的实现都继承于AbstarctHandlerMappingAbstarctHandlerMapping主要作用是完成拦截器的初始化工作。而通过AbstarctHandlerMapping又衍生出两个系列,AbstractUrlHandlerMappingAbstractHandlerMethodMapping

    AbstractUrlHandlerMapping也有很多子类的实现,如SimpleUrlHandlerMappingAbstractDetectingUrlHandlerMapping。总体来说,AbstractUrlHandlerMapping需要用到一个保存urlhandler的对应关系的mapmap的初始化工作由子类实现。不同的子类会有自己的策略,可以在配置文件中注册,也可以在spring容器中找。

    AbstractHandlerMethodMapping系列则通常用于注解的方法,解析包含@Controller或者@RequestMapping注解的类,建立urlmethod的直接对应关系,这也是目前使用最多的一种方式。

  • 相关阅读:
    《Java技术》第七次作业
    《Java技术》第六次作业
    《Java技术》第五次作业
    《Java技术》第四次作业
    《Java技术》第三次作业
    《Java技术》第二次作业
    《Java技术》第一次作业
    股票——布林带
    股票——指数移动平均线
    股票——简单移动平均线
  • 原文地址:https://www.cnblogs.com/zhoading/p/11789379.html
Copyright © 2011-2022 走看看