美文网首页
springboot 集成tomcat的启动流程和请求映射原理

springboot 集成tomcat的启动流程和请求映射原理

作者: 西5d | 来源:发表于2020-07-16 19:07 被阅读0次

背景

Springboot是日常项目中非常流行的框架,但很少有人深入了解过SpringBoot是如何将一个请求映射到最终的方法的。今天这篇文章就从请求入口开始,带大家了解其中的原理。

准备工作

首先创建一个空的SpringbootWeb项目,创建一个测试controller,写一个简单的get请求方法。以此为入口,一步步跟踪请求过程。

请求流程分析

  1. tomcat启动
    默认为集成tomcat,和springboot一起启动,在这里org.springframework.boot.SpringApplication.refreshContext()一直执行到org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#finishRefresh()方法,如下启动tomcat:
    @Override
    protected void finishRefresh() {
        super.finishRefresh();
        WebServer webServer = startWebServer();
        if (webServer != null) {
            publishEvent(new ServletWebServerInitializedEvent(webServer, this));
        }
    }

然后这里startWebServer()的时候,会调用入口performDeferredLoadOnStartup()方法,初始化servelet等一系列操作,这块就不多介绍了。
2.执行get请求
用一个简单的GET test/str , 在执行请求后进入断点。
这里tomcat用的是nio的网络模型,所以入口是org.apache.tomcat.util.net.NioEndpoint.Poller.processKey(SelectKey key, NioSocketWrapper wrapper),入后会将这次请求放到org.apache.tomcat.util.net.SocketProcessorBase中执行,如下代码:

            SocketProcessorBase<S> sc = null;
            if (processorCache != null) {
                sc = processorCache.pop();
            }
            if (sc == null) {
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            Executor executor = getExecutor();
            if (dispatch && executor != null) {
                executor.execute(sc);
            } else {
                sc.run();
            }

此处的socketWrapper是对一次请求Socket的封装。然后在org.apache.tomcat.util.net.NioEndpoint.SocketProcessor.doRun()方法中,执行请求,这里涉及大量NIO操作相关的内容。
然后,请求重点交给了org.apache.coyote.http11.Http11Processor.service(),以及org.apache.catalina.connector.CoyoteAdapter处理,一直到StandardWrapper执行org.apache.catalina.core.StandardWrapper.initServlet()方法,注意这里只首次请求会初始化,通过instanceInitialized标记来判断。

  1. 初始化 DispatcherServlet
    跟着上面的步骤,一直往下调用,到org.springframework.web.servlet.FrameworkServlet.initServletBean()中,初始化DispatcherServlet.
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
        if (logger.isInfoEnabled()) {
            logger.info("Initializing Servlet '" + getServletName() + "'");
        }
        long startTime = System.currentTimeMillis();

        try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException | RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            throw ex;
        }

        if (logger.isDebugEnabled()) {
            String value = this.enableLoggingRequestDetails ?
                    "shown which may lead to unsafe logging of potentially sensitive data" :
                    "masked to prevent unsafe logging of potentially sensitive data";
            logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                    "': request parameters and headers will be " + value);
        }

        if (logger.isInfoEnabled()) {
            logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
        }
    }

这里有一个refreshApplicationContext的操作,然后就进入了org.springframework.web.servlet.DispatcherServlet.onRefresh(),如下方法:

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

我们主要看initHandlerMappings(context);方法。实际的请求就是在这里映射的。

  1. 请求映射
    继续跟进,在initHandlerMappings中,代码如下:
    /**
     * Initialize the HandlerMappings used by this class.
     * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
     * we default to BeanNameUrlHandlerMapping.
     */
    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            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");
            }
        }
    }

这里Map matchingBeans 中有一个是requestMappingHandlerMapping,包含一个mappingLookup的map,里边是请求uri和方法的对应关系。而matchingBeans是org.springframework.beans.factory.BeanFactoryUtils.beansOfTypeIncludingAncestors(org.springframework.beans.factory.ListableBeanFactory, java.lang.Class<T>, boolean, boolean)方法获取的。最后这里将 matchingBeans的所有value赋值给DispatcherServlet
handlerMappings属性,是一个数组列表。

  1. DispatcherServlet执行
    然后请求到达org.springframework.web.servlet.DispatcherServlet.doDispatch(request, response), 里边有org.springframework.web.servlet.DispatcherServlet.getHandler(request),将请求与对应的controller对应上。这里就用到了上面的handlerMappings属性。代码如下:
    @Nullable
    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;
    }

最终的请求是org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(String lookupPath, HttpServletRequest request)

    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<>();
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            // No choice but to go through all mappings...
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }

        if (!matches.isEmpty()) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            matches.sort(comparator);
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                if (logger.isTraceEnabled()) {
                    logger.trace(matches.size() + " matching mappings: " + matches);
                }
                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }
                Match secondBestMatch = matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException(
                            "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                }
            }
            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

在获取了对应的controller之后,执行该bean的请求方法,就完成了这次请求。至此整个流程是完成了。

备注

使用的springboot版本是2.2.2.RELEASE和spring-webmvc:5.2.2.RELEASE

总结

本文简单追踪了Springboot 请求映射的流程,对该项内容做了初步了解,部分细节还有待更深入研究,时间仓促,可能有些疏漏,希望后面做个更全面的梳理。
感谢阅读。

相关文章

网友评论

      本文标题:springboot 集成tomcat的启动流程和请求映射原理

      本文链接:https://www.haomeiwen.com/subject/jjemhktx.html