浅谈SpringMVC请求映射handler源码解读

 更新时间:2021年03月18日 09:18:23   作者:是冯吉荣呀  
这篇文章主要介绍了浅谈SpringMVC请求映射handler源码解读,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

请求映射源码

首先看一张请求完整流转图(这里感谢博客园上这位大神的图,博客地址我忘记了):

前台发送给后台的访问请求是如何找到对应的控制器映射并执行后续的后台操作呢,其核心为DispatcherServlet.java与HandlerMapper。在spring boot初始化的时候,将会加载所有的请求与对应的处理器映射为HandlerMapper组件。我们可以在springMVC的自动配置类中找到对应的Bean。

@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping(
  @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
  @Qualifier("mvcConversionService") FormattingConversionService conversionService,
  @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
  // Must be @Primary for MvcUriComponentsBuilder to work
  return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
                       resourceUrlProvider);
}

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                              FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
  WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
    new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
    this.mvcProperties.getStaticPathPattern());
  welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
  welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
  return welcomePageHandlerMapping;
}

请求将首先执行FrameworkServlet下的service方法根据request请求的method找到对应的do**方法。

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {

  HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
  if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
    processRequest(request, response);
  }
  else {
    //父类根据method参数执行doGet,doPost,doDelete等
    super.service(request, response);
  }
}

而这些do**其都会进入核心方法,以doGet为例。

@Overrideprotected 
final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  //核心方法
  processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
  try {
  //进入此核心方法
  doService(request, response);
}
catch (ServletException | IOException ex) {
  failureCause = ex;
  throw ex;
}
catch (Throwable ex) {
  failureCause = ex;
  throw new NestedServletException("Request processing failed", ex);
}

finally {
  resetContextHolders(request, previousLocaleContext, previousAttributes);
  if (requestAttributes != null) {
    requestAttributes.requestCompleted();
  }
  logResult(request, response, failureCause, asyncManager);
  publishRequestHandledEvent(request, response, startTime, failureCause);
}

processRequest()方法中重点在doService(request, response);,而其核心处理逻辑位于DispatchServletl类重写的方法,如下。

@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
 ····

   try {
     //这里为实际分发控制器的逻辑,其内部是找到对应的handlerMapper
     doDispatch(request, response);
   }
    finally {
      if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Restore the original attribute snapshot, in case of an include.
        if (attributesSnapshot != null) {
          restoreAttributesAfterInclude(request, attributesSnapshot);
        }
      }
      if (requestPath != null) {
        ServletRequestPathUtils.clearParsedRequestPath(request);
      }
    }
}

接下来看分发处理逻辑方法,其中重要的方法都使用了原生的注释。接下来分别分析核心源码。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  HttpServletRequest processedRequest = request;
  HandlerExecutionChain mappedHandler = null;
  boolean multipartRequestParsed = false;

  WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

  try {
    ModelAndView mv = null;
    Exception dispatchException = null;

    try {
      processedRequest = checkMultipart(request);
      multipartRequestParsed = (processedRequest != request);

      // Determine handler for the current request.
      mappedHandler = getHandler(processedRequest);
      if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return;
      }

      // Determine handler adapter for the current request.
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

      // Process last-modified header, if supported by the handler.
      String method = request.getMethod();
      boolean isGet = "GET".equals(method);
      if (isGet || "HEAD".equals(method)) {
        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
          return;
        }
      }

      if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
      }

      // Actually invoke the handler.
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

      if (asyncManager.isConcurrentHandlingStarted()) {
        return;
      }

      applyDefaultViewName(processedRequest, mv);
      mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
      dispatchException = ex;
    }
    catch (Throwable err) {
      // As of 4.3, we're processing Errors thrown from handler methods as well,
      // making them available for @ExceptionHandler methods and other scenarios.
      dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  }
  catch (Exception ex) {
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  }
  catch (Throwable err) {
    triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
  }
  finally {
    if (asyncManager.isConcurrentHandlingStarted()) {
      // Instead of postHandle and afterCompletion
      if (mappedHandler != null) {
        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
      }
    }
    else {
      // Clean up any resources used by a multipart request.
      if (multipartRequestParsed) {
        cleanupMultipart(processedRequest);
      }
    }
  }
}

首先是分析getHandler(),找到对应的处理器映射逻辑。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  if (this.handlerMappings != null) {
    for (HandlerMapping mapping : this.handlerMappings) {
      HandlerExecutionChain handler = mapping.getHandler(request);
      if (handler != null) {
        return handler;
      }
    }
  }
  return null;
}

我们将断点标记在getHandler方法上时,可以清除看到handlerMappings,如图。

这里,用户请求与处理器的映射关系都在RequestMapperHandlerMapping中,而欢迎页处理请求则在WelcomePageHanderMapping中进行映射。

以下为RequestMapperHandlerMapping中映射部分截图,可以看到用户的所有请求映射这里面都有:

getHandler()后的方法是通过比较request请求中method与HandlerMapper中相同url下的method,再进行唯一性校验,不通过异常,通过找到唯一的handler。

后续,通过handler找到处理的设配器,通过适配器得到一个ModelAndView对象,这个对象就是最后返回给前端页面的对象。

至此,一个请求完整映射到返回前端结束。

说明:这是实现了FramworkServlet的doService方法,FramworkServlet继承自HttpServlet,并且重写了父类中的doGet(),doPost(),doPut(),doDelete 等方法,在这些重写的方法里都调用了 processRquest() 方法做请求处理,进入processRquest()可以看到里面调用了FramworkServlet中定义的doService() 方法。

到此这篇关于浅谈SpringMVC请求映射handler源码解读的文章就介绍到这了,更多相关SpringMVC请求映射handler 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解RestTemplate 用法

    详解RestTemplate 用法

    RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,也有的称之为网络框架,说白了就是Java版本的一个postman,这篇文章主要介绍了详解RestTemplate 用法,需要的朋友可以参考下
    2022-07-07
  • 详解Spring注解@Autowired的实现原理和使用方法

    详解Spring注解@Autowired的实现原理和使用方法

    在使用Spring开发的时候,配置的方式主要有两种,一种是xml的方式,另外一种是 java config的方式,在使用的过程中,我们使用最多的注解应该就是@Autowired注解了,所以本文就给大家讲讲@Autowired注解是如何使用和实现的,需要的朋友可以参考下
    2023-07-07
  • Spring5新功能@Nullable注解及函数式注册对象

    Spring5新功能@Nullable注解及函数式注册对象

    这篇文章主要为大家介绍了Spring5新功能详解@Nullable注解及函数式注册对象,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • MyBatis根据条件批量修改字段的方式

    MyBatis根据条件批量修改字段的方式

    这篇文章主要介绍了MyBatis根据条件批量修改字段的方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • Idea打不了断点如何解决

    Idea打不了断点如何解决

    这篇文章主要介绍了Idea打不了断点如何解决的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • mybatis plus自动生成代码的示例代码

    mybatis plus自动生成代码的示例代码

    本文主要介绍了mybatis plus自动生成代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • 基于String变量的两种创建方式(详解)

    基于String变量的两种创建方式(详解)

    下面小编就为大家带来一篇基于String变量的两种创建方式(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • Windows安装Maven并配置环境的详细步骤

    Windows安装Maven并配置环境的详细步骤

    Maven是一个非常流行的构建和项目管理工具,用于Java开发,它提供了一个强大的依赖管理系统和一系列标准化的构建生命周期,本文将指导您如何在Windows操作系统上安装和配置Maven,需要的朋友可以参考下
    2023-05-05
  • springboot vue组件开发实现接口断言功能

    springboot vue组件开发实现接口断言功能

    这篇文章主要为大家介绍了springboot+vue组件开发实现接口断言功能,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • java线程池使用及原理面试题

    java线程池使用及原理面试题

    很多面试官喜欢把线程池作为问题的起点,然后延伸到其它内容,由于我们专栏已经说过队列、线程、锁面试题了,所以本章面试题还是以线程池为主
    2022-03-03

最新评论