SpingBoot-请求映射原理

97 阅读4分钟

我们浏览器发送请求的时候,我们的后台是怎么知道这个请求要去寻找COntoller中的哪个方法呢?

我们已经知道SpringBoot 是一个整合框架的框架,在Web部分,SPringBoot 底层使用的是SpringMvc, SpringMvc是通过DispatcherServlet 来执行请求分发的

打开类结构树

image.png

原生的Servlet 被HttpServletBean 继承, HttpServletBean又被FramworkServlet继承,最后被我们的DispatcherServlet继承

Servlet 用来接收请求必定会有用到doGet 和 doPost 方法

HttpServletBean

image.png

这里面并没有比较核心的doget 或者是dopost方法,再看 FramworkServlet

image.png

发现这个类重写了doget dopost 方法 而且这些方法最终都将request 和responce请求进行了包装 processRequest()方法

image.png

进入processRequest()方法查看

image.png

进入DoService方法(),结果发现这是一个抽象方法,在这个类中并没有定义具体的实现,那么他的实现自然就交给了他的子类, DispatcherServlet 进入DispathcerServlet的类中 查看doservice方法

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {  
this.logRequest(request);  
Map<String, Object> attributesSnapshot = null;  
if (WebUtils.isIncludeRequest(request)) {  
attributesSnapshot = new HashMap();  
Enumeration<?> attrNames = request.getAttributeNames();  
  
label116:  
while(true) {  
String attrName;  
do {  
if (!attrNames.hasMoreElements()) {  
break label116;  
}  
  
attrName = (String)attrNames.nextElement();  
} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));  
  
attributesSnapshot.put(attrName, request.getAttribute(attrName));  
}  
}  
  
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());  
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);  
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);  
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.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);  
}  
  
RequestPath previousRequestPath = null;  
if (this.parseRequestPath) {  
previousRequestPath = (RequestPath)request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);  
ServletRequestPathUtils.parseAndCache(request);  
}  
  
try {  
this.doDispatch(request, response);   // 核心方法, 上面的代码都是在做初始化,为这一条做准备
} 

finally {  
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null){  
this.restoreAttributesAfterInclude(request, attributesSnapshot);  
}  
  
if (this.parseRequestPath) {  
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);  
}  
  
}  
  
}

try {
this.doDispatch(request, response); // 核心方法, 上面的代码都是在做初始化,为这一条做准备 每个请求都会经过这个方法 }

我们进入这个方法 看看他到底做了什么!

进入dodispath()方法,打上断点访问 localhost:8080

image.png

这里发现原生的请求是 / 也就是访问的是我们的主页

继续向下放行有一个mappedHandler 表示这个请求被哪个映射处理器接收了

image.png

从标注的第一行开始, 第一行的意思是 检查这个请求是不是文件上传请求,检查结果是这个不是文件啥贯穿请求, 然后 MapperHandller 的值为 ParameterizableViewController 接收了,并且通过试图解析器,重定向到了 Index.html , 这解释了 为什么Spring 只要静态资源有INdex.html 访问主页就会定向到index.html 页面了

我们现在断点打在

image.png

在访问/get 请求 localhost:8080/get 这个请求映射写在TestController2 的get 方法上面

image.png

发现他直接就找到了 这个TestController 2 的 get 方法, 那么他是怎么找到的呢 这时候我就需要把注意力放在断点语句的右边 this.getHandler(ProcessedRequest)

进入这个方法

发现他 发现了5个handlerMapping 分别如下

image.png

这段代码的意思 , 请求进来,会依次遍历这五个请求映射器, 如果有哪一个请求映射器可以匹配到这个请求,那么就把这个映射器给返回出去, 从图上蓝色代码可以看到, springboot.controller.testcontroller2 .get 匹配到了这个请求, 就把这个映射给返回出去了

我们在Mapping.getHandler(request) 打上断点 重新请求/get 看看他是怎么找到这个映射器的, 进入了这个类

image.png

也就是说 getHandlerInternal(request) 找到了这个映射, 我们再断点调试 在Object Handler 打上断点请求/get

image.png

发现在这个地方就找到了这个映射,也就是handlerMethod,createWithResolvedBean 找到了 我们继续断点请求调试

image.png

emm 分析不动了, 不过 分析出来了 他通过哪些方法 一步一步的找到了这个类,. ,不过重新分析回来查看这个断点, 我们会发现,我们在controller中写的所有请求都会在mappingRegistry 中被找到, 应该是通过,反射和注解 来做到的这些我也不太清楚了,以我目前的能力我只能分析到这里了

image.png

我知道为什么了, 我们请求/get 这个请求, 这个请求在 五个handlmapping 中的 requesthandllermapping 中的 mappingregistry 中找到了这个请求, 所以这个请求就被 requesthandlermapping 给接受返回了

image.png

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {  
List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();  
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);  
if (directPathMatches != null) {  
this.addMatchingMappings(directPathMatches, matches, request);  
}  
  
if (matches.isEmpty()) {  
this.addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);  
}  
  
if (matches.isEmpty()) {  
return this.handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);  
} else {  
AbstractHandlerMethodMapping<T>.Match bestMatch = (Match)matches.get(0);  
if (matches.size() > 1) {  
Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new MatchComparator(this.getMappingComparator(request));  
matches.sort(comparator);  
bestMatch = (Match)matches.get(0);  
if (this.logger.isTraceEnabled()) {  
Log var10000 = this.logger;  
int var10001 = matches.size();  
var10000.trace("" + var10001 + " matching mappings: " + matches);  
}  
  
if (CorsUtils.isPreFlightRequest(request)) {  
Iterator var7 = matches.iterator();  
  
while(var7.hasNext()) {  
AbstractHandlerMethodMapping<T>.Match match = (Match)var7.next();  
if (match.hasCorsConfig()) {  
return PREFLIGHT_AMBIGUOUS_MATCH;  
}  
}  
} else {  
AbstractHandlerMethodMapping<T>.Match secondBestMatch = (Match)matches.get(1);  
if (comparator.compare(bestMatch, secondBestMatch) == 0) {  
Method m1 = bestMatch.getHandlerMethod().getMethod();  
Method m2 = secondBestMatch.getHandlerMethod().getMethod();  
String uri = request.getRequestURI();  
throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");  
}  
}  
}  
  
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());  
this.handleMatch(bestMatch.mapping, lookupPath, request);  
return bestMatch.getHandlerMethod();  
}  
}

先记录着,下次再总结 一下