[SpringMVC源码学习]initHandlerMappings(二)

277 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情

2. initHandlerMappings

初始化处理器映射器

从上面的流程,我们知道了初始化的大致流程,现在根据我所了解到的SpringMVC的流程,我们着重看下HandlerMapping,上面的方法中其实只是从spring的容器中获取对应的实例bean,不是我们的重点。

private void initHandlerMappings(ApplicationContext context) {
   this.handlerMappings = null;
   // detectAllHandlerMappings 默认为 true
   if (this.detectAllHandlerMappings) {
      // 找出Spring容器中所有类型为HandlerMapping的实例,再前面没有做任何类型的管理,所以spring容器中根本就没有
      Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
      if (!matchingBeans.isEmpty()) {
         this.handlerMappings = new ArrayList<>(matchingBeans.values());
         // We keep HandlerMappings in sorted order. 排序
         AnnotationAwareOrderComparator.sort(this.handlerMappings);
      }
   }
   else {
      try {
         HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
         this.handlerMappings = Collections.singletonList(hm);
      }
      catch (NoSuchBeanDefinitionException ex) {
         // Ignore, we'll add a default HandlerMapping later.
      }
   }
​
   // Ensure we have at least one HandlerMapping, by registering
   // a default HandlerMapping if no other mappings are found.
   if (this.handlerMappings == null) {
      this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);// 进到这里
      if (logger.isTraceEnabled()) {
         logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
               "': using default strategies from DispatcherServlet.properties");
      }
   }
​
   for (HandlerMapping mapping : this.handlerMappings) {
      if (mapping.usesPathPatterns()) {
         this.parseRequestPath = true;
         break;
      }
   }
}

首先从spring的容器中获取所有的类型为HandlerMapping类型的对象,我们此前没有设置过,所以是为空的。那接下来就会进入到getDefaultStrategies方法了。在进入到这个方法之前呢,还有一点需要说明。

static {
        try {
            ClassPathResource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        } catch (IOException var1) {
            throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + var1.getMessage());
        }
    }

上面这段代码,是DispatcherServlet中的一段静态代码块,我们知道,静态代码块会随着类的加载而执行,只会执行一次。也就是说,在调用其他方法之前,这段代码已经被执行。这段代码的意思其实就是,将DispatcherServlet类路径下的DispatcherServlet.properties加载,然后将其放入到defaultStrategies这样的一个属性中。了解这个之后我们接下来再去看getDefaultStrategies方法。

DispatcherServlet.properties一些配置信息

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
​
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
​
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
   org.springframework.web.servlet.function.support.RouterFunctionMapping
​
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
   org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
   org.springframework.web.servlet.function.support.HandlerFunctionAdapter
​
​
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
   org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
   org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
​
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
​
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
​
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

回到getDefaultStrategies方法里面

// context类型是XmlApplication
// strategyInterface是HandlerMapping
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
   if (defaultStrategies == null) {
      try {
         // Load default strategy implementations from properties file.
         // This is currently strictly internal and not meant to be customized
         // by application developers.
         ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
         defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
      }
      catch (IOException ex) {
         throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
      }
   }
​
   String key = strategyInterface.getName();
   String value = defaultStrategies.getProperty(key);
   if (value != null) {
      // 根据逗号将value切割为String数组
      String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
      List<T> strategies = new ArrayList<>(classNames.length);
      for (String className : classNames) {
         try {
            Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
            Object strategy = createDefaultStrategy(context, clazz);
            strategies.add((T) strategy);
         }
         catch (ClassNotFoundException ex) {
            throw new BeanInitializationException(
                  "Could not find DispatcherServlet's default strategy class [" + className +
                  "] for interface [" + key + "]", ex);
         }
         catch (LinkageError err) {
            throw new BeanInitializationException(
                  "Unresolvable class definition for DispatcherServlet's default strategy class [" +
                  className + "] for interface [" + key + "]", err);
         }
      }
      return strategies;
   }
   else {
      return Collections.emptyList();
   }
}

那么传过来的两个参数是什么呢,context已经在前面分析过,是XmlApplicationContext类型,在initHandlerMappings这个方法调用getDefaultStrategies这个方法的时候,可以看出穿的类型是HandlerMapping的类型。 首先利用strategyInterface得到他的全类名,然后再用这个全类名当 key 去defaultStrategies中获取value。上面提到,DispatcherServlet.properties已经全部被设置到defaultStrategies当中,那么根据HandlerMapping这个全类名在DispatcherServlet.properties能获取到什么呢。通过对DispatcherServlet.properties的查找我们知道,

  • org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
  • org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
  • org.springframework.web.servlet.function.support.RouterFunctionMapping

是这三个值。然后根据逗号将value切割成为一个String数组。然后去遍历这个数组,分别调用createDefaultStrategy方法。

但其实这个方法就是createBean,去创建bean实例,但真的只有这么简单吗?

其实并不是。

我们看上面三个类,其中BeanNameUrlHandlerMappingr这个类,没有实现什么扩展性的接口,我们不用管他,RouterFunctionMapping这个类,是关于路由的,不是我们的重点,我们主要来看RequestMappingHandlerMapping这个类。 image-20220806200633296

RequestMappingHandlerMapping是继承于RequestMappingInfoHandlerMapping,而RequestMappingInfoHandlerMapping这个类实现了一个接口,InitializingBean接口,实现这个接口的类,会在bean实例化完成,填充完属性之后调用afterPropertiesSet方法。

public void afterPropertiesSet() {
​
   this.config = new RequestMappingInfo.BuilderConfiguration();
   this.config.setTrailingSlashMatch(useTrailingSlashMatch());
   this.config.setContentNegotiationManager(getContentNegotiationManager());
​
   if (getPatternParser() != null) {
      this.config.setPatternParser(getPatternParser());
      Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,
            "Suffix pattern matching not supported with PathPatternParser.");
   }
   else {
      this.config.setSuffixPatternMatch(useSuffixPatternMatch());
      this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
      this.config.setPathMatcher(getPathMatcher());
   }
​
   super.afterPropertiesSet();
}

在给config属性设置了一些属性之后,调用父类的afterPropertiesSet方法。

@Override
public void afterPropertiesSet() {
    initHandlerMethods();
}

->

protected void initHandlerMethods() {
   // spring容器中拿到所有的beanName
   for (String beanName : getCandidateBeanNames()) {
      if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
         processCandidateBean(beanName);
      }
   }
   handlerMethodsInitialized(getHandlerMethods());
}

先获取spring容器中所有的beanName,然后循环,如果beanName不是以scopedTarget.开头,就会调用processCandidateBean方法。

protected void processCandidateBean(String beanName) {
   Class<?> beanType = null;
   try {
      beanType = obtainApplicationContext().getType(beanName);
   }
   catch (Throwable ex) {
      // An unresolvable bean type, probably from a lazy bean - let's ignore it.
      if (logger.isTraceEnabled()) {
         logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
      }
   }
   if (beanType != null && isHandler(beanType)) {  // <--这里
      detectHandlerMethods(beanName);
   }
}

当我们拿到HandlerMapping的beanType之后,会进行 if 语句中的判断,值得注意的是isHandler(beanType)这个方法。

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

*这个方法会查找你传入的beanType类型中有没有Controller注解或者RequestMapping注解。*也就是说,spring容器中所有的实例都会一 一的进行筛选,只有bean类型上有Controller注解或者RequestMapping注解的才会继续执行detectHandlerMethods方法。

protected void detectHandlerMethods(Object handler) {
   // 获取当前beanName获取BeanClass对应类对象的类型
   Class<?> handlerType = (handler instanceof String ?
         obtainApplicationContext().getType((String) handler) : handler.getClass());
​
   if (handlerType != null) {
      // //获取到真实的类类型,因为有可能拿到的类的类型是一个实例的代理对象,需要得到代理对象对应类的真实类
      Class<?> userType = ClassUtils.getUserClass(handlerType);
      Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
            (MethodIntrospector.MetadataLookup<T>) method -> {
               try {
                  return getMappingForMethod(method, userType);
               }
               catch (Throwable ex) {
                  throw new IllegalStateException("Invalid mapping on handler class [" +
                        userType.getName() + "]: " + method, ex);
               }
            });
      if (logger.isTraceEnabled()) {
         logger.trace(formatMappings(userType, methods));
      }
      else if (mappingsLogger.isDebugEnabled()) {
         mappingsLogger.debug(formatMappings(userType, methods));
      }
      methods.forEach((method, mapping) -> {
         Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
         registerHandlerMethod(handler, invocableMethod, mapping);
      });
   }
}

如果传的handler参数是string类型,就根据这个参数到spring容器中获取对应类型,否则直接拿到该类型。然后获取到真实的类型,再去进行操作。那么里面这个lambda表达式就比较重要了。我们看看getMappingForMethod这个方法。

/*
使用方法和类型级别的 @RequestMapping 注释来创建 RequestMappingInfo。
*/
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
   RequestMappingInfo info = createRequestMappingInfo(method);
   if (info != null) {
      RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
      if (typeInfo != null) {
         info = typeInfo.combine(info);
      }
      String prefix = getPathPrefix(handlerType);
      if (prefix != null) {
         info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
      }
   }
   return info;
}

这个RequestMappingInfo这个类就比较重要了,这个类实际上对应的就是@RequestMapping这个注解。他封装了这个注解里面的内容,在下面的源码中我们会看到。那么进入到这个方法,首先就调用createRequestMappingInfo方法传入method去创建一个RequestMappingInfo对象。如果拿到的info不为空的话,再调用这个方法再去创建一个RequestMappingInfo对象,那么为什么要创建两次,值得注意的是,虽然调用了两次方法,但是传入的参数却不一样,我们刚刚说RequestMappingInfo对象对应@RequestMapping注解,这个注解是可以作用于方法或者类上面得到,所以要想拿到所有的注解信息,就需要调用两次。我们现在来看看这个创建方法。

@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
   RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
   RequestCondition<?> condition = (element instanceof Class ?
         getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
   return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

首先通过传来的参数,去拿到这个方法或者是类上面的RequestMapping注解对象。然后如果有自定义的策略,去调用。我们这里没有,condition 为null,然后createRequestMappingInfo

protected RequestMappingInfo createRequestMappingInfo(
      RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
​
   RequestMappingInfo.Builder builder = RequestMappingInfo
         .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
         .methods(requestMapping.method())
         .params(requestMapping.params())
         .headers(requestMapping.headers())
         .consumes(requestMapping.consumes())
         .produces(requestMapping.produces())
         .mappingName(requestMapping.name());
   if (customCondition != null) {
      builder.customCondition(customCondition);
   }
   return builder.options(this.config).build();
}

这里的也比较明了,这个方法的作用就是去创建一个对应的RequestMappingInfo对象,在创建对象之前,把requestMapping注解的所有参数给builder包装起来,然后调用build方法真正的去创建RequestMappingInfo对象。

public RequestMappingInfo build() {
​
      PathPatternsRequestCondition pathPatterns = null;
      PatternsRequestCondition patterns = null;
​
      if (this.options.patternParser != null) {
         pathPatterns = (ObjectUtils.isEmpty(this.paths) ?
               EMPTY_PATH_PATTERNS :
               new PathPatternsRequestCondition(this.options.patternParser, this.paths));
      }
      else {
         patterns = (ObjectUtils.isEmpty(this.paths) ?
               EMPTY_PATTERNS :
               new PatternsRequestCondition(
                     this.paths, null, this.options.getPathMatcher(),
                     this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
                     this.options.getFileExtensions()));
      }
​
      ContentNegotiationManager manager = this.options.getContentNegotiationManager();
​
      return new RequestMappingInfo(
            this.mappingName, pathPatterns, patterns,
            ObjectUtils.isEmpty(this.methods) ?
                  EMPTY_REQUEST_METHODS : new RequestMethodsRequestCondition(this.methods),
            ObjectUtils.isEmpty(this.params) ?
                  EMPTY_PARAMS : new ParamsRequestCondition(this.params),
            ObjectUtils.isEmpty(this.headers) ?
                  EMPTY_HEADERS : new HeadersRequestCondition(this.headers),
            ObjectUtils.isEmpty(this.consumes) && !this.hasContentType ?
                  EMPTY_CONSUMES : new ConsumesRequestCondition(this.consumes, this.headers),
            ObjectUtils.isEmpty(this.produces) && !this.hasAccept ?
                  EMPTY_PRODUCES : new ProducesRequestCondition(this.produces, this.headers, manager),
            this.customCondition != null ?
                  new RequestConditionHolder(this.customCondition) : EMPTY_CUSTOM,
            this.options);
   }
}

其实这个build方法就是包装参数,new对象。刚刚builder包装了RequestMapping注解中的所有内容,这里在new对象的时候,再把他的所有封装的属性再包装成对象。

  • methods ------>new RequestMethodsRequestCondition(this.methods)---->RequestMethodsRequestCondition对象
  • params ----->new ParamsRequestCondition(this.params)---->ParamsRequestCondition对象
  • headers ---->new HeadersRequestCondition(this.headers)---->HeadersRequestCondition对象
  • consumes—>new ConsumesRequestCondition(this.consumes, this.headers)—>ConsumesRequestCondition对象
  • produces------> new ProducesRequestCondition(this.produces, this.headers, manager)---->ProducesRequestCondition对象

封装之后,返回创建好的RequestMappingInfo对象。直接返回到getMappingForMethod这个方法中。到这,方法和方法所在类上面的RequestMapping注解解析完成,并把内容封装的两个RequestMappingInfo对象中。那其实这两个对象分开是不行的,因为,他们对应的RequestMapping中的属性,最后所需要的其实是两个注解进行合并的,比如他的value值,也就是路径,他是需要进行合并的,所以,在combine方法中,进行了合并,然后再获取路径前缀,如果有的话在进行一次合并,返回合并好的RequestMappingInfo对象。返回到detectHandlerMethods这个方法中。

后面的,明天学!!