【面向过程】springboot接受到一个请求后做了什么

从启动开始

先从springboot怎么启动开始,启动完成之后建立web容器才能在容器中处理http请求

什么是 springboot

在spring的官网上,对springboot这样描述到:

Spring Boot 可以轻松创建独立的、生产级的基于 Spring 的应用程序,您可以"直接运行"。 我们对 Spring 平台和第三方库采取固执己见的观点,因此您可以轻松上手。 大多数 Spring Boot 应用程序需要最少的 Spring 配置。

springboot启动的时候怎么加载web容器

启动一个springboot应用,我们通常是这样启动

java 复制代码
package com.example.demo; import org.springframework.boot.SpringApplication; 
import org.springframework.boot.autoconfigure.SpringBootApplication; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.RequestParam; 
import org.springframework.web.bind.annotation.RestController; 
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args); 
    }
}

我们可以看到的是,在程序main函数内,我们只进行了SpringApplication.run操作,我们来看看#run方法之后,进行了什么,在SpringApplication类中,这样定义了#run方法

java 复制代码
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class[]{primarySource}, args);
}

最后执行的是这个方法

java 复制代码
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();

    Collection exceptionReporters;
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        this.refreshContext(context);
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        listeners.started(context);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

怎么构建一个容web器

主要的建立容器的方法在context = this.createApplicationContext();这样代码,这行代码创建了spirng的上下文,来看看他进行了什么操作

java 复制代码
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
            }
        } catch (ClassNotFoundException var3) {
            throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
        }
    }

    return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

选择什么类型的web容器

这之中根据webApplicationType的值来构建容器类型,这个值是主要定义的

java 复制代码
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.isCustomEnvironment = false;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

WebApplicationType.deduceFromClasspath();方法如下

java 复制代码
private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};


static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
        return REACTIVE;
    } else {
        String[] var0 = SERVLET_INDICATOR_CLASSES;
        int var1 = var0.length;

        for(int var2 = 0; var2 < var1; ++var2) {
            String className = var0[var2];
            if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                return NONE;
            }
        }

        return SERVLET;
    }
}

总结

由源码中可以看到的是,启动时根据DispatcherServlet版本来启动springboot应用,我们按SERVLET类型的DispatcherServlet来进行分析,org.springframework.web.servlet.DispatcherServlet类位于spring-webmvc包下,类注解是这样描述它自己的

Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception handling facilities. This servlet is very flexible: It can be used with just about any workflow, with the installation of the appropriate adapter classes.
HTTP 请求处理程序/控制器的中央调度程序,例如 适用于 Web UI 控制器或基于 HTTP 的远程服务导出器。 分派到已注册的处理程序来处理 Web 请求,提供方便的映射和异常处理设施。 该 servlet 非常灵活:通过安装适当的适配器类,它可以与几乎任何工作流程一起使用。

怎么处理HTTP请求

Servlet

我们都知道springboot默认基于tomcat容器,通过IDEA对DispatcherServlet类的关系分析,我们可以看到他继承的父类 FrameworkServlet继承了HttpServletHttpServlet继承了HttpServlet

作为javaboy的我们,熟知HttpServlet干的就是处理http请求,比如doGetdoPost等,那我们就可以知道DispatcherServlet干的活,就是处理请求,FrameworkServlet还实现了ApplicationContextAware,阅读FrameworkServlet的源码,我们可以看到他重写了处理请求的方法,且调用的都是processRequest(HttpServletRequest request, HttpServletResponse response)方法

处理请求的方法

来分析下processRequest方法

java 复制代码
/**
 * Process this request, publishing an event regardless of the outcome.
 * <p>The actual event handling is performed by the abstract
 * {@link #doService} template method.
 */
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
       throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    //获取当前线程的上下文(threadLocal实现)
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    //构建当前请求的 localeContext  用于本地话语言实现
    LocaleContext localeContext = buildLocaleContext(request);
    //获取当前线程的  RequestAttributes
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    //构建 ServletRequestAttributes ,ServletRequestAttributes有四个属性[request,response,sessionMutex,sessionId]
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
        //获取当前请求的 WebAsyncManager[管理异步请求处理的中心类,异步场景从线程(T1)中的请求处理开始。并发请求处理可以通过调用startCallableProcessing或startDeferredResultProcessing来启动,两者都在单独的线程(T2)中产生结果。保存结果并将请求分派给容器,以便在第三个线程(T3)中继续使用保存的结果进行处理。在分派的线程(T3)中,可以通过getConcurrentResult()访问保存的结果,也可以通过hasConcurrentResult()检测它的存在。]
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    //把当前请求注册给异步请求处理器
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    //初始化contextHolder,就是set  LocaleContext和RequestContextHolder的RequestAttributes
    initContextHolders(request, localeContext, requestAttributes);

    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);
    }
}

源码中可以看到的是主要的处理是 #doService方法,DispatcherServlet中重写了这个方法

java 复制代码
/**
 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
 * for the actual dispatching.
 */
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
     //输出请求日志
    logRequest(request);

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
       attributesSnapshot = new HashMap<>();
       Enumeration<?> attrNames = request.getAttributeNames();
       while (attrNames.hasMoreElements()) {
          String attrName = (String) attrNames.nextElement();
          if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
             attributesSnapshot.put(attrName, request.getAttribute(attrName));
          }
       }
    }

    // Make framework objects available to handlers and view objects.
    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());

    if (this.flashMapManager != null) {
       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);
    }
    //以上代码设置请求的attribute
    try {
       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);
          }
       }
    }
}

dispatch

DispatcherServlet#doService方法中,对request进行了处理后,主要的处理方法是在#doDispatch

java 复制代码
/**
 * 处理对处理程序的实际调度。
 * 处理程序将通过按顺序应用 servlet 的 HandlerMappings 来获得。
 * HandlerAdapter 将通过查询 servlet 已安装的 HandlerAdapter 来找到第一个支持处理程序类的 HandlerAdapter。
 * 所有 HTTP 方法均由该方法处理。 由 HandlerAdapter 或处理程序本身来决定哪些方法是可接受的。
 * 
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
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);

          // 确定当前请求的处理程序。
          mappedHandler = getHandler(processedRequest);
          if (mappedHandler == null) {
              //没找到响应处理器,返回  SC_NOT_FOUND = 404;
             noHandlerFound(processedRequest, response);
             return;
          }

          //取得当前请求的处理程序适配器,如果没找到会抛出ServletException -  No adapter for handler
          HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

          // 获取请求方法
          String method = request.getMethod();
          
          //处理last-modified
          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.  invoke给对应的handler,handler处理完request和reponse后会返回对应的 mv(ModelAndView)
          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);
       }
       
       //↑  以上catch处理对应的错误
       
       //↓  处理dispath结果,结果不是个modelView就是个要处理成modelView的exception
       
       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);
          }
       }
    }
}

总结

由上来看,springboot在使用非响应式容器时,处理方式和springMVC一致,只是在简化了很多配置性的操作,让应用可以做到开箱机箱,但是处理请求一样是从接受到请求开始根据路径指向到对应的服务去处理并返回

相关推荐
敲代码娶不了六花1 小时前
jsp | servlet | spring forEach读取不了对象List
java·spring·servlet·tomcat·list·jsp
Yhame.1 小时前
深入理解 Java 中的 ArrayList 和 List:泛型与动态数组
java·开发语言
是小崔啊2 小时前
开源轮子 - EasyExcel02(深入实践)
java·开源·excel
myNameGL3 小时前
linux安装idea
java·ide·intellij-idea
青春男大3 小时前
java栈--数据结构
java·开发语言·数据结构·学习·eclipse
HaiFan.3 小时前
SpringBoot 事务
java·数据库·spring boot·sql·mysql
我要学编程(ಥ_ಥ)3 小时前
一文详解“二叉树中的深搜“在算法中的应用
java·数据结构·算法·leetcode·深度优先
music0ant4 小时前
Idea 添加tomcat 并发布到tomcat
java·tomcat·intellij-idea
计算机徐师兄4 小时前
Java基于SSM框架的无中介租房系统小程序【附源码、文档】
java·微信小程序·小程序·无中介租房系统小程序·java无中介租房系统小程序·无中介租房微信小程序
源码哥_博纳软云4 小时前
JAVA智慧养老养老护理帮忙代办陪诊陪护小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台