spring boot 源码解析54-AbstractHandlerMethodMapping

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

前言

本文我们先来解析一下AbstractHandlerMethodMapping,以对下篇文章EndpointHandlerMapping的解析打下基础.其继承结构如下:

20191017100436\_1.png

解析

HandlerMapping

HandlerMapping的顶层接口,只声明了1个方法–>getHandler–>调用getHandler实际上返回的是一个HandlerExecutionChain,这是典型的command的模式的使用,这个HandlerExecutionChain不但持有Handler本身,还包括了处理这个HTTP请求相关的拦截器,如下:

    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

AbstractHandlerMapping

HandlerMapping的抽象实现,模板方法模式,将1些共性的方法抽象成1个类.其实现了getHandler.如下:

    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 = getApplicationContext().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;
        }
  1. 根据request获取对应的handler,该方法是1个抽象方法,由子类来实现,如下:

        protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
  2. 如果没有对应的handler,就是要默认的handler
  3. 如果没有默认的handler,返回null
  4. 通过名称取出对应的 handler bean
  5. 把handler 封装到HandlerExecutionChain中并加上拦截器.如下:

        protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
            // 1. 获得HandlerExecutionChain
            HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                    (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    
            // 2. 根据请求获得对应的Path
            String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
            // 3. 遍历adaptedInterceptors
            for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
                // 3.1 如果是MappedInterceptor,并且匹配当前的path,则加入到HandlerExecutionChain中
                if (interceptor instanceof MappedInterceptor) {
                    MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                    if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                        chain.addInterceptor(mappedInterceptor.getInterceptor());
                    }
                }
                // 3.2 否则,直接加入到HandlerExecutionChain
                else {
                    chain.addInterceptor(interceptor);
                }
            }
            return chain;
        }
    1. 获得HandlerExecutionChain
    2. 根据请求获得对应的Path
    3. 遍历adaptedInterceptors

      1. 如果是MappedInterceptor,并且匹配当前的path,则加入到HandlerExecutionChain中
      2. 否则,直接加入到HandlerExecutionChain
  6. 跨域处理

    1. 获得全局的CorsConfiguration和handler对应的CorsConfiguration,然后进行合并
    2. 针对cors请求添加Handler或者Interceptor

AbstractHandlerMethodMapping

AbstractHandlerMethodMapping是1个泛型类,其泛型参数T–>用来代表匹配handler的条件专门使用的一种类,这里的条件就不只是url了,还可以有很多其他条件,如request的类型,请求的参数,header等都可以作为匹配的HandlerMethod的条件.默认使用的是RequestMappingInfo实现了InitializingBean接口.

  1. 字段如下:

        // scpoed 代理 bean的name的前缀.用来去除handler method的判断
        private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
    
        // cors请求并且是options类型的请求并且请求头中含有Access-Control-Request-Method时返回的HandlerMethod
        private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =
                new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));
    
        private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
    
        static {
            ALLOW_CORS_CONFIG.addAllowedOrigin("*");
            ALLOW_CORS_CONFIG.addAllowedMethod("*");
            ALLOW_CORS_CONFIG.addAllowedHeader("*");
            ALLOW_CORS_CONFIG.setAllowCredentials(true);
        }
    
        // 如果为true,则在当前applicationContext和祖先applicationContext中获取所有的bean,如果为false,则在当前上下文获得所有的bean
        private boolean detectHandlerMethodsInAncestorContexts = false;
    
        // 向MappingRegistry中的nameLookup进行注册时用来生成beanName,这里默认使用的是RequestMappingInfoHandlerMethodMappingNamingStrategy
        // 其规则为:类名里的大写字母组合+"#"+方法名.
        private HandlerMethodMappingNamingStrategy namingStrategy;
    
        private final MappingRegistry mappingRegistry = new MappingRegistry();

    这里有必要说明1下MappingRegistry,其字段如下:

        private final Map> registry = new HashMap>();
    
        // 保存着匹配条件(也就是RequestMappingInfo)和HandlerMethod的对应关系
        private final Map mappingLookup = new LinkedHashMap();
    
        // 保存着url与匹配条件(也就是RequestMappingInfo)的对应关系,当然这里的url是pattren式的,可以使用通配符.
        // 由于RequestMappingInfo可以同时使用多种不同的匹配方式而不只是url一种,所有反过来说同一个url就可能有多个RequestMappingInfo与之对应
        // 这里的RequestMappingInfo其实就是在@RequestMapping 中注释的内容
        private final MultiValueMap urlLookup = new LinkedMultiValueMap();
    
        // 这个map是spring mvc 4 新增的,保存着name与HandlerMethod的对应关系,这个name是从HandlerMethodMappingNamingStrategy的实现类从
        // HandlerMethod中解析处理的,默认使用的是RequestMappingInfoHandlerMethodMappingNamingStrategy,解析规则是:
        // 类名里的大写字母组合+"#"+方法名.这个在正常的匹配过程不需要使用,它主要用在MvcUriComponentsBuilder里,可以根据name获取相应的url
        private final Map> nameLookup =
                new ConcurrentHashMap>();
    
        private final Map corsLookup =
                new ConcurrentHashMap();
    
        private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  2. 由于AbstractHandlerMethodMapping实现了InitializingBean,因此在其初始化之后,会调用其afterPropertiesSet方法,如下:

        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());
        }
    1. 获得ApplicationContext中的所有bean的id
    2. 遍历

      1. 如果bean Id 不是 scopedTarget.开头的,则获得其类型
      2. 如果其是handler则调用detectHandlerMethods进行注册.其中hanler是抽象方法. detectHandlerMethods如下:

            protected void detectHandlerMethods(final Object handler) {
                // 获得handler的类型
                Class handlerType = (handler instanceof String ?
                        getApplicationContext().getType((String) handler) : handler.getClass());
                // 如果是cgli代理的子对象类型,则返回父类型,否则直接返回传入的类型
                final Class userType = ClassUtils.getUserClass(handlerType);
                // 获取当前bean里所有符合Handler要求的Method
                Map methods = MethodIntrospector.selectMethods(userType,
                        new MethodIntrospector.MetadataLookup() {
                            @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);
                                }
                            }
                        });
                // 将符合要求的methods注册起来,也就是保存到3个map中
                for (Map.Entry entry : methods.entrySet()) {
                    Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
                    T mapping = entry.getValue();
                    registerHandlerMethod(handler, invocableMethod, mapping);
                }
            }
        1. 获得handler的类型,如果是cgli代理的子对象类型,则返回父类型,否则直接返回传入的类型
        2. 获取当前bean里所有符合Handler要求的Method,其中会回调getMappingForMethod方法,该方法是个抽象方法,由子类实现
        3. 将符合要求的methods注册.代码如下:

              protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method); }

          MappingRegistry#register 实现如下:

              public void register(T mapping, Object handler, Method method) { this.readWriteLock.writeLock().lock(); try { HandlerMethod handlerMethod = createHandlerMethod(handler, method); // 检查是否在mappingLookup已经存在,如果存在而且和现在传入的不同则抛出异常 assertUniqueMethodMapping(handlerMethod, mapping); if (logger.isInfoEnabled()) { logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod); } // 添加到mappingLookup中 this.mappingLookup.put(mapping, handlerMethod);
          
                      // 添加到urlLookup
                      List directUrls = getDirectUrls(mapping);
                      for (String url : directUrls) { this.urlLookup.add(url, mapping); }
          
                      // 添加到nameLookup
                      String name = null;
                      if (getNamingStrategy() != null) { name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); }
          
                      // 实例化CorsConfiguration
                      CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
                      if (corsConfig != null) { this.corsLookup.put(handlerMethod, corsConfig); }
          
                      this.registry.put(mapping, new MappingRegistration(mapping, handlerMethod, directUrls, name));
                  }
                  finally { this.readWriteLock.writeLock().unlock(); }
              }
          1. 创建HandlerMethod,代码如下:

                protected HandlerMethod createHandlerMethod(Object handler, Method method) { HandlerMethod handlerMethod; if (handler instanceof String) { String beanName = (String) handler; handlerMethod = new HandlerMethod(beanName, getApplicationContext().getAutowireCapableBeanFactory(), method); } else { handlerMethod = new HandlerMethod(handler, method); } return handlerMethod;
                }
          2. 检查是否在mappingLookup已经存在,如果存在而且和现在传入的不同则抛出异常.代码如下:

                private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
                    HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
                    if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
                        throw new IllegalStateException(
                                "Ambiguous mapping. Cannot map '" + newHandlerMethod.getBean() + "' method \n" +
                                newHandlerMethod + "\nto " + mapping + ": There is already '" +
                                handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
                    }
                }
          3. 添加到mappingLookup中
          4. 添加到urlLookup,其中getDirectUrls–>获得mapping的Path,如果不含有*或者含有?的话,则添加到结果集中.代码如下:

                private List getDirectUrls(T mapping) {
                    List urls = new ArrayList(1);
                    for (String path : getMappingPathPatterns(mapping)) {
                        if (!getPathMatcher().isPattern(path)) {
                            urls.add(path);
                        }
                    }
                    return urls;
                }

            AntPathMatcher#isPattern,如下:

                public boolean isPattern(String path) {
                    return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
                }
          5. 添加到nameLookup
          6. 实例化CorsConfiguration,如果不为null,则添加到corsLookup.此处默认返回null,由子类复写
          7. 添加到registry中
    3. 模板方法,空实现
  3. getHandlerInternal实现如下(删去多余代码):

        protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
            // 1.截取用于匹配的url有效路径
            String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
            // 2. 使用lookupHandlerMethod方法通过lookupPath和request 找HandlerMethod
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            // 3. 如果可以找到handlerMethod则调用createWithResolvedBean方法创建新的HandlerMethod
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); 
        }
    1. 截取用于匹配的url有效路径
    2. 使用lookupHandlerMethod方法通过lookupPath和request 找HandlerMethod.代码如下:

          protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
              // match 是内部类,保存匹配条件和HandlerMethod
              List matches = new ArrayList();
              // 1. 根据lookupPath获取到匹配条件
              List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
              if (directPathMatches != null) {
                  // 将匹配到的条件添加到matches
                  addMatchingMappings(directPathMatches, matches, request);
              }
              // 如果不能直接使用lookupPath得到匹配条件,则将所有匹配条件加入到matches
              if (matches.isEmpty()) {
                  // No choice but to go through all mappings...
                  addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
              }
      
              // 对matches进行排序,并取第一个作为bestMatch,如果前面两个排序相同则抛出异常
              if (!matches.isEmpty()) {
                  Comparator 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;
                      }
                      // 如果matches有多个匹配的,则将第2个和第一个进行比较,看顺序是否一样,如果是的话,则抛出异常
                      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的属性,子类RequestMappingInfoHandlerMapping
                  // 进行了重写,将更多的参数设置到了request,主要是为了以后使用时方便
                  handleMatch(bestMatch.mapping, lookupPath, request);
                  return bestMatch.handlerMethod;
              }
              else {
                  return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
              }
          }
      1. 根据lookupPath获取到匹配条件,将匹配到的条件添加到matches
      2. 如果不能直接使用lookupPath得到匹配条件,则将所有匹配条件加入到matches
      3. 如果matches非空

        1. 对matches进行排序,并取第一个作为bestMatch,如果前面两个排序相同则抛出异常
        2. 在返回前做一些处理,默认实现是讲lookupPath设置到request的属性,子类RequestMappingInfoHandlerMapping进行了重写,将更多的参数设置到了request,主要是为了以后使用时方便
      4. 否则,调用handleNoMatch,默认返回null.
    3. 如果可以找到handlerMethod则调用createWithResolvedBean方法创建新的HandlerMethod.代码如下:

          public HandlerMethod createWithResolvedBean() {
              Object handler = this.bean;
              if (this.bean instanceof String) {
                  String beanName = (String) this.bean;
                  handler = this.beanFactory.getBean(beanName);
              }
              return new HandlerMethod(this, handler);
          }

RequestMappingInfoHandlerMapping

RequestMappingInfoHandlerMapping–> 继承自AbstractHandlerMethodMapping

  1. 字段,构造器如下:

        // 对OPTIONS请求的处理时用到
        private static final Method HTTP_OPTIONS_HANDLE_METHOD;
    
        static {
            try {
                HTTP_OPTIONS_HANDLE_METHOD = HttpOptionsHandler.class.getMethod("handle");
            }
            catch (NoSuchMethodException ex) {
                // Should never happen
                throw new IllegalStateException("Failed to retrieve internal handler method for HTTP OPTIONS", ex);
            }
        }
    
        protected RequestMappingInfoHandlerMapping() {
            setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
        }
  2. 该类复写了几个方法:

    1. getMappingPathPatterns,代码如下:

          protected Set getMappingPathPatterns(RequestMappingInfo info) {
              return info.getPatternsCondition().getPatterns();
          }

      该方法是在hander注册的时候调用.如下:

      20191017100436\_2.png

    2. getMatchingMapping–>检查给定的RequestMappingInfo是否匹配当前的请求,返回RequestMappingInfo,代码如下:

          protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
              return info.getMatchingCondition(request);
          }

      20191017100436\_3.png

    3. getMappingComparator–> 返回1个比较RequestMappingInfo的Comparator,在有多个Handler匹配当前请求时用到.代码如下:

          protected Comparator getMappingComparator(final HttpServletRequest request) {
              return new Comparator() {
                  @Override
                  public int compare(RequestMappingInfo info1, RequestMappingInfo info2) {
                      return info1.compareTo(info2, request);
                  }
              };
          }
    4. handleMatch,handleNoMatch比较简单,这里就不再贴出

RequestMappingHandlerMapping

RequestMappingHandlerMapping–> 继承自RequestMappingInfoHandlerMapping.根据 在实现Controller接口或者被@Controller注解的类中的在类和方法上声明的@RequestMapping,创建一个 RequestMappingInfo

  1. 字段如下:

        // 是否使用后缀匹配(.*)当对请求进行模式匹配时,如果可用时,则/users 对/users.*也匹配.默认是true.
        private boolean useSuffixPatternMatch = true;
    
        // 是否后缀匹配应该只对ContentNegotiationManager中注册的扩展符匹配时生效.这一般建议减少歧义和避免问题比如当.出现在路径的情况下
        private boolean useRegisteredSuffixPatternMatch = false;
    
        // 是否有无斜杠都匹配,如果启用的化,则/users 也匹配 /users/.默认是true
        private boolean useTrailingSlashMatch = true;
    
        // 内容协商
        private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
    
        // 这里使用的是EmbeddedValueResolver
        private StringValueResolver embeddedValueResolver;
    
        // RequestMappingInfo的Builder类,用来创建RequestMappingInfo的
        private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
  2. 其主要方法如下:

    1. 覆写了afterPropertiesSet,如下:

          public void afterPropertiesSet() {
              this.config = new RequestMappingInfo.BuilderConfiguration();
              this.config.setUrlPathHelper(getUrlPathHelper());
              this.config.setPathMatcher(getPathMatcher());
              this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
              this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
              this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
              this.config.setContentNegotiationManager(getContentNegotiationManager());
      
              super.afterPropertiesSet();
          }
    2. isHandler,代码如下:

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

      调用链如下:

      20191017100436\_4.png

    3. getMappingForMethod –> 使用在类,方法 声明的@RequestMapping来创建RequestMappingInfo.代码如下:

          protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType) { // 1. 根据Method上的@RequestMapping 创建RequestMappingInfo RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { // 2. 根据类上的@RequestMapping 创建RequestMappingInfo RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { // 3. 合并 info = typeInfo.combine(info); } } return info;
          }

      createRequestMappingInfo,如下:

          private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
              // 获取@RequestMapping 注解
              RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
              // 此处返回的都是null
              RequestCondition condition = (element instanceof Class ?
                      getCustomTypeCondition((Class) element) : getCustomMethodCondition((Method) element));
              return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
          }
      1. 获取@RequestMapping 注解
      2. 获得RequestCondition,此处返回的都是null
      3. 如果requestMapping等于null,则返回null,否则根据RequestMapping创建RequestMappingInfo.代码如下:

            protected RequestMappingInfo createRequestMappingInfo(
                    RequestMapping requestMapping, RequestCondition customCondition) {
                return RequestMappingInfo
                        .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
                        .methods(requestMapping.method())
                        .params(requestMapping.params())
                        .headers(requestMapping.headers())
                        .consumes(requestMapping.consumes())
                        .produces(requestMapping.produces())
                        .mappingName(requestMapping.name())
                        .customCondition(customCondition)
                        .options(this.config)
                        .build();
            }

来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring boot 源码解析54-AbstractHandlerMethodMapping

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏