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

81 阅读3分钟

背景

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标记来判断。 3. 初始化 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);方法。实际的请求就是在这里映射的。 4. 请求映射 继续跟进,在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赋值给DispatcherServlethandlerMappings属性,是一个数组列表。 5. 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 请求映射的流程,对该项内容做了初步了解,部分细节还有待更深入研究,时间仓促,可能有些疏漏,希望后面做个更全面的梳理。 感谢阅读。