SpringMVC源码解析从service到doDispatch(下)

简介: SpringMVC源码解析从service到doDispatch(下)

HttpServlet#service

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
  //获取请求类型
    String method = req.getMethod();
  //如果是get请求
    if (method.equals(METHOD_GET)) {
    //检查是不是开启了页面缓存 通过header头的 Last-Modified/If-Modified-Since
    //获取Last-Modified的值
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
      //没有开启页面缓存调用doGet方法
            doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
        //获取If-Modified-Since的值
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                ifModifiedSince = -1;
            }
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // If the servlet mod time is later, call doGet()
                // Round down to the nearest second for a proper compare
                // A ifModifiedSince of -1 will always be less
        //更新Last-Modified
                maybeSetLastModified(resp, lastModified);
        //调用doGet方法
                doGet(req, resp);
            } else {
        //设置304状态码 在HttpServletResponse中定义了很多常用的状态码
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }
    } else if (method.equals(METHOD_HEAD)) {
    //调用doHead方法
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
    //调用doPost方法
        doPost(req, resp);
    } else if (method.equals(METHOD_PUT)) {
     //调用doPost方法
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
      //调用doPost方法
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
    //调用doPost方法
        doOptions(req,resp);
    } else if (method.equals(METHOD_TRACE)) {
    //调用doPost方法
        doTrace(req,resp);
    } else {
    //服务器不支持的方法 直接返回错误信息
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

根据请求类型调用响应的请求方法如果GET类型,调用doGet方法

POST类型,调用doPost方法。

这些方法都是在HttpServlet中定义的,平时我们做web开发的时候主要是继承HttpServlet这个类,然后重写它的doPost或者doGet方法。

我们的FrameworkServlet这个子类就重写了这些方法中的一部分:doGet、doPost、doPut、doDelete、doOption、doTrace。


这里我们只说我们最常用的doGet和doPost这两个方法。通过翻开源码我们发现,这两个方法体的内容是一样的,都是调用了processRequest

FrameworkServlet#processRequest

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
  long startTime = System.currentTimeMillis();
  Throwable failureCause = null;
  LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
  //国际化
  LocaleContext localeContext = buildLocaleContext(request);
  RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
  //构建ServletRequestAttributes对象
  ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
  //异步管理
  WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    //初始化ContextHolders
  initContextHolders(request, localeContext, requestAttributes);
  //执行doService
  try {
    doService(request, response);
  }
  finally {
    //重新设置ContextHolders
    resetContextHolders(request, previousLocaleContext, previousAttributes);
    if (requestAttributes != null) {
      requestAttributes.requestCompleted();
    }
    //发布请求处理事件
    publishRequestHandledEvent(request, response, startTime, failureCause);
  }
}

国际化的设置,创建ServletRequestAttributes对象,初始化上下文holders(即将Request对象放入到线程上下文中),调用doService方法。


国际化的设置

DispatcherServlet#buildLocaleContext这个方法中完成的,其源码如下:

protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
  if (this.localeResolver instanceof LocaleContextResolver) {
    return ((LocaleContextResolver) this.localeResolver).resolveLocaleContext(request);
  }
  else {
    return new LocaleContext() {
      @Override
      public Locale getLocale() {
        return localeResolver.resolveLocale(request);
      }
    };
  }
}

没有配置国际化解析器的话,那么它会使用默认的解析器:AcceptHeaderLocaleResolver,即从Header中获取国际化的信息。

除了AcceptHeaderLocaleResolver之外,SpringMVC中还提供了这样的几种解析器:CookieLocaleResolver、SessionLocaleResolver、FixedLocaleResolver。分别从cookie、session中去国际化信息和JVM默认的国际化信息(Local.getDefault())。


initContextHolders这个方法主要是将Request请求、ServletRequestAttribute对象和国际化对象放入到上下文中。其源码如下:

private void initContextHolders(
    HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {
  if (localeContext != null) {
    LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);//threadContextInheritable默认为false
  }
  if (requestAttributes != null) {//threadContextInheritable默认为false
    RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
  }
}

RequestContextHolder这个类有什么用呢?有时候我们想在某些类中获取HttpServletRequest对象,比如在AOP拦截的类中,那么我们就可以这样来获取Request的对象了,

HttpServletRequest request = (HttpServletRequest) RequestContextHolder.getRequestAttributes().resolveReference(RequestAttributes.REFERENCE_REQUEST);

DispatcherServlet#doService

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
  Map<String, Object> attributesSnapshot = null;
  if (WebUtils.isIncludeRequest(request)) {
    attributesSnapshot = new HashMap<String, Object>();
    Enumeration<?> attrNames = request.getAttributeNames();
    while (attrNames.hasMoreElements()) {
      String attrName = (String) attrNames.nextElement();
      if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
        attributesSnapshot.put(attrName, request.getAttribute(attrName));
      }
    }
  }
  //Spring上下文
  request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
  //国际化解析器
  request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
  //主题解析器
  request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
  //主题
  request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
  //重定向的数据
  FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
  if (inputFlashMap != null) {
    request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
  }
  request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
  request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
  try {
    //调用doDispatch方法-核心方法
    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);
      }
    }
  }
}

处理include标签的请求,将上下文放到request的属性中,将国际化解析器放到request的属性中,将主题解析器放到request属性中,将主题放到request的属性中,处理重定向的请求数据最后调用doDispatch这个核心的方法对请求进行处理

后续流程

目录
相关文章
|
7月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
1641 1
|
7月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
653 29
|
7月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
187 4
|
7月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
7月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
7月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
7月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
8月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
1354 0
|
11月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
269 2
|
10月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多
  • DNS