SpringMVC中9大组件之HandlerMapping源码分析

目录

前言

正文

基于Spring的HandlerMapping

SpringMVC上下文如何初始化HandlerMapping

DispatcherServlet中如何获取HandlerMapping对象

基于Spring boot的HandlerMapping

总结


前言

相信能看到此篇帖子的小伙伴们对SpringMVC的大致源码还是有了解,可能是来仔细阅读SpringMVC中每个组件的功能和底层源码实现。对于SpringMVC来说对URL地址中地址处理和地址对应的controller层面的类与接口的映射关系都是HandlerMapping来做处理。所以本篇帖子来解读SpringMVC启动时HandlerMapping组件对上下文的作用。

正文

很明确的说如今已经不是ssm框架的天下了,基本全是基于Spring boot微服务的天下了,但是呢SpringMVC对与Spring的整合和Spring boot的整合是有区别的,所以本帖对于两个整合都做以说明

基于Spring的HandlerMapping

SpringMVC上下文如何初始化HandlerMapping

对于SpringMVC来说,核心类就是DispatcherServlet类,任何请求都通过tomcat然后映射到此类,然后走doDispatch()方法,然后就是通过HandlerMapping找请求映射的类和类中的方法。所以说在整个Spring容器启动的时候,我们的controller层的类和方法都会被储存到HandlerMapping中,当请求过来的时候从HandlerMapping中找到映射的方法,然后包装成HandlerExecutionChain继续往下执行。所以我们目前要关心的是在spring容器启动的时候,我们controller层的类如何解析并添加到HandlerMapping中?

但是在这之前我们是不是要考虑HandlerMapping是在什么时候被创建的,我们知道整合Spring那就肯定是要注入到IoC容器中,注入到IoC容器中无非就是@Bean、或者注解或者是XML配置文件,而我们ssm整合是写了几个xml配置文件的,所以看到springMVC的XML配置文件中。

但是并没有看到HandlerMapping的身影。但是说

<mvc:annotation-driven />

会解析这个配置,这里会把RequestMappingHandlerMapping——HandlerMapping的实现类给解析给BeanDefinition,相信大家对BeanDefinition不陌生他就是一个bean对象的抽象实例。

我们再看到RequestMappingHandlerMapping的继承图

我们知道实现了InitializingBean接口是在Bean创建完实例并且执行完赋值操作才开始执行InitializingBean接口的实现方法

 说了这么多,那我们具体看看afterPropertiesSet()方法做了一些什么事情。

由于RequestMappingHandlerMapping最后也是super.afterPropertiesSet(),所以我们看到父类

protected void initHandlerMethods() {
   if (logger.isDebugEnabled()) {
      logger.debug("Looking for request mappings in application context: " + getApplicationContext());
   }

   // 获取到全部的bean对象。
   String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
         BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
         obtainApplicationContext().getBeanNamesForType(Object.class));

    // 增强for循环遍历bean对象
   for (String beanName : beanNames) {

        // 查看Bean对象是否有特殊的代理,如果有特殊的代理会在bean对象的名字上加上前缀
      if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
        
        // 获取到bean对象
         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.isDebugEnabled()) {
               logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
            }
         }

        // 判断bean对象是否实现了@Controller注解或者是@RequestMapping注解
        // 很好理解这里就是要把业务中controller层的类加载到HandlerMapping中
         if (beanType != null && isHandler(beanType)) {

            // 加载逻辑,下面会具体讲解
            detectHandlerMethods(beanName);

         }
      }
   }

    // 方法实现为null,方法的注释说是加载完所有的类和方法的一个回调方法
    // 作用应该是给子类去实现重写,做一个加载完毕的回调把。
   handlerMethodsInitialized(getHandlerMethods());
}
isHandler()方法的实现

这里也就是通过上下文容器获取到所有要加载进IoC容器中的bean实例,然后判断是否实现了@Controller注解或者是@RequestMapping注解,实现了就做下一步的操作。

detectHandlerMethods()方法的具体实现

protected void detectHandlerMethods(final Object handler) {

    // 获取到bean对象的class实例
   Class<?> handlerType = (handler instanceof String ?
         obtainApplicationContext().getType((String) handler) : handler.getClass());

   if (handlerType != null) {

      final Class<?> userType = ClassUtils.getUserClass(handlerType);

        // 获取到类中的方法和RequestMappingInfo(具体的方法映射信息,url地址、请求方式等等...)
        // 然后使用LinkedHashMap存储
      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.isDebugEnabled()) {
         logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
      }

        // 遍历方法
      for (Map.Entry<Method, T> entry : methods.entrySet()) {

        // 通过工具类对method方法做一个筛选,如果是private或者static修饰的方法就会直接抛出异常
         Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);

        // 获取到RequestMappingInfo对象
         T mapping = entry.getValue();

        // 添加到缓存中的具体逻辑了
         registerHandlerMethod(handler, invocableMethod, mapping);
      }
   }
}

这里是给添加到缓存中的一个铺垫操作,解析类和方法,并且做一个包装,为添加到缓存做准备

registerHandlerMethod()方法具体实现

写这么多缓存,其实也就是证实了如今硬件设备强,大家开发都是空间换时间。

添加缓存中的逻辑就不细讲了,内部维护了一把读写锁来控制并发。

大家要注意一个问题,我们现在是一个方法栈,也就是说,前面的方法还在当前方法的下面,而当前方法都是在前面方法的for循环中。如图所示

添加到缓存中大致图

 先是遍历类的for循环,再是类中方法的循环,是一个嵌套的循环!

DispatcherServlet中如何获取HandlerMapping对象

上面的步骤也就是把RequestMappingHandlerMapping给注入到IoC容器中,但是我们处理请求还是在DispatcherServlet中处理,那么肯定在DispatcherServlet中要获取到具体的HandlerMapping对象,所以接下来追寻一下怎么获取到RequestMappingHandlerMapping对象。

我们知道我们的请求都是先经过tomcat服务器,而对于servlet来说是懒加载机制,当第一个请求通过tomcat达到servlet控制器的后,servlet才开始进行一些初始化操作。

在DispatchServlet中定义了一个内部类来实现了Spring中的事件监听事件,还是容器上下文刷新完毕回调事件,所以这个事件是在哪里触发的呢? 

没错就是在AbstractApplicationContext中的refresh()方法中的finishRefresh()方法中。

 我们继续回到FrameworkServlet中

private void initHandlerMappings(ApplicationContext context) {
   this.handlerMappings = null;

   if (this.detectAllHandlerMappings) {
      // 获取到IoC容器中所有HandlerMapping子类对象,其中就包括了RequestMappingHandlerMapping
      Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
      if (!matchingBeans.isEmpty()) {

        // 将获取到的HandlerMapping子类对象赋值给DispatcherServlet中维护的全handlerMappings变量,是一个List集合。
         this.handlerMappings = new ArrayList<>(matchingBeans.values());
         // We keep HandlerMappings in sorted order.
         AnnotationAwareOrderComparator.sort(this.handlerMappings);
      }
   }
   else {

     // 这里就代表IoC中没有HandlerMapping子类对象,将手动创建一个放入IoC容器中。
      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.
      }
   }

   // 这里代表上面操作都没获取到HandlerMapping对象,这里将手动创建一个
   if (this.handlerMappings == null) {
      this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
      if (logger.isDebugEnabled()) {
         logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
      }
   }
}

看着onRefresh()方法是不是以为是IoC中refresh()方法中的OnRefresh()方法?很显然他欺骗了你。

通过上面的流程就在DispatcherServlet中获取到HandlerMapping对象的集合了,当请求到DispatcherServlet中运行doDispatch中会获取到RequestMappingHandlerMapping对象来做请求地址和缓存的一个查询匹对。

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 解析HttpServletRequest中的路径,并且命中缓存
    // 因为缓存中的HandlerMethod对象为了性能存的是String类型的,所以再根据getBean()取出实例再封装一个新的HandlerMethod对象。
   Object handler = getHandlerInternal(request);

    // 判空操作,如果没有命中缓存,就代表你的请求地址有问题,或者是tomcat启动的第一次默认请求
   if (handler == null) {
      handler = getDefaultHandler();
   }
   if (handler == null) {
      return null;
   }

    
   // Bean name or resolved handler?
   if (handler instanceof String) {
      String handlerName = (String) handler;
      handler = obtainApplicationContext().getBean(handlerName);
   }

    // 根据HandlerMethod对象new一个HandlerExecutionChain对象,并且还会获取到当前请求的拦截器
   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;
}

这里就是解析请求,并且尝试获取到缓存中的值,最后获取到的值和拦截器对象一起封装成一个HandlerMethod对象返回为后面处理请求做铺垫。

基于Spring boot的HandlerMapping

我们知道对于Spring boot来说为开发者减少了很多配置项,当然肯定也减少了SpringMVC的配置,对于Spring boot来说自动配置遵循一个规定,那就是在jar包中的META-INF中定义一个spring.factory文件下,里面写好需要自动配置的类路径,让Spring boot来扫描并且添加到Spring容器中完成一个自动配置。

所以在Spring boot中会通过这种方式注入到IoC容器中,并且RequestMappingHandlerMapping他的父类是实现了InitializingBean接口。

 这些流程跟Spring整合SpringMVC一模一样这里就不过多解释,也就是加载Bean对象赋值后的一个回调事件,然后项目中的标有@Controller注解和@RequestMapping注解的类和方法做一个简单的解析放入到MappingRegisty中的多个缓存中。来请求就从缓存中查询是否存在。

我们知道在Spring整合SpringMVC中,是需要开发者搭建一个tomcat服务器,而Spring的上下文容器需要通过tomcat服务器对Servlet初始化过程中才能进行refresh的(懒加载,需要用户发第一个请求时加载),而RequestMappingHandlerMapping等等SpringMVC的组件都是在Spring上下文刷新的时候注入到IoC容器中,在DispatcherServlet类中维护组件的初始化(就是从IoC容器获取)都是在Spring上下文刷新完毕的监听事件中执行的。而对于Spring boot来说内置了一个tomcat服务器,是Spring boot实现的Spring上下文刷新的时候对其内置的Tomcat做一个初始化。两者是相反的过程。所以RequestMappingHandlerMapping等等SpringMVC的组件都是在上下文刷新的过程中就加入到IoC容器中。但是对于DispatcherServlet中维护的组件初始化还是需要内置的tomcat来进行初始化,所以也是一个懒加载,需要用户发第一个请求时对其进行一个初始化工作,其实不过是Spring整合还是Spring boot整合都是需要通过tomcat来进行一定的初始化工作。

 在看initWebApplicationContext()方法之前,我们看一定下FrameworkServlet的继承和实现关系,他实现了ApplicationContextAware接口,所以可以得到了ApplicationContext容器上下文。

 最后就回到了DispatcherServlet的onRefresh()方法来对DispatcherServlet中维护的SpringMVC的组件进行初始化操作(也就是从IoC容器中获取)。当请求过来时走doDispatch()方法就可以获取到SpringMVC的各种组件进行工作了。

总结

总体难度不大,就是要区分Spring和Spring boot两者的初始化过程的区别。

最后如果本帖对您有帮助希望点赞+关注,一直在更新各种框架的源码帖,您的支持就是给我最大的支持!~

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员李哈

创作不易,希望能给与支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值