背景
Springboot是日常项目中非常流行的框架,但很少有人深入了解过SpringBoot是如何将一个请求映射到最终的方法的。今天这篇文章就从请求入口开始,带大家了解其中的原理。
准备工作
首先创建一个空的SpringbootWeb项目,创建一个测试controller
,写一个简单的get请求方法。以此为入口,一步步跟踪请求过程。
请求流程分析
- 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
标记来判断。
- 初始化 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);
方法。实际的请求就是在这里映射的。
- 请求映射
继续跟进,在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
属性,是一个数组列表。
- 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 请求映射的流程,对该项内容做了初步了解,部分细节还有待更深入研究,时间仓促,可能有些疏漏,希望后面做个更全面的梳理。
感谢阅读。
网友评论