这几天调试项目,突然对于http请求如何传到我的项目,然后找到对应的控制方法产生了兴趣,这部分的内容比较多,本来想用一篇长文来测试总结,现在还是先分点去记录。

首先先介绍一下请求传输到项目的流程。Http请求是属于应用层的,请求的传输其实是在传输层,通过socket进行传输到tomcat.
先介绍一些关于tomcat的知识。
tomcat目前的传输协议有四种,分别是:BIO, NIO,NIO2,APR
BIO协议依赖的类是org.apache.coyote.http11.Http11Protocol
这个协议下,分为两部分,第一部分是acceptor,用于接收socket请求的
第二部分是worker线程池,用于处理socket请求的
BIO是阻塞流的IO,acceptor接收socket请求后发送给worker线程池处理,如果线程池没有空闲的线程,那么acceptor就会发生阻塞。
NIO协议依赖的类是org.apache.coyote.http11.Http11NioProtocol
这个协议在BIO的基础上增加了一个poller设置
acceptor接收socket请求后不是直接发送给线程池,而是发送给poller。在Poller中,维护了一个Selector对象;当Poller从队列中取出socket后,注册到该Selector中;然后通过遍历Selector,找出其中可读的socket,并使用Worker中的线程处理相应请求。
通过BIO和NIO的描述,可以看出NIO在接收socket和处理socket的过程中,仍然会出现阻塞,不同的是,NIO增加了一个Poller,基于这个区别,如果tomcat在高分发的情况下,使用NIO可以提高Tomcat的工作效率
目前大多数HTTP请求使用的是长连接(HTTP/1.1默认keep-alive为true),而长连接意味着,一个TCP的socket在当前请求结束后,如果没有新的请求到来,socket不会立马释放,而是等timeout后再释放。如果使用BIO,“读取socket并交给Worker中的线程”这个过程是阻塞的,也就意味着在socket等待下一个请求或等待释放的过程中,处理这个socket的工作线程会一直被占用,无法释放;因此Tomcat可以同时处理的socket数目不能超过最大线程数,性能受到了极大限制。而使用NIO,“读取socket并交给Worker中的线程”这个过程是非阻塞的,当socket在等待下一个请求或等待释放时,并不会占用工作线程,因此Tomcat可以同时处理的socket数目远大于最大线程数,并发性能大大提高。
APR协议依赖的类是org.apache.coyote.http11.Http11AprProtocol
APR是Apache Portable Runtime,是Apache可移植运行库,利用本地库可以实现高可扩展性、高性能;Apr是在Tomcat上运行高并发应用的首选模式
配置相对应的协议:
配置BIO的
<Connector port="8080" protocol="org.apache.coyote.http11.Http11Protocol "
connectionTimeout="20000"
redirectPort="8443" />
配置NIO的
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol "
connectionTimeout="20000"
redirectPort="8443" />
配置APR的
<Connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol "
connectionTimeout="20000"
redirectPort="8443" />
注:Tomcat7中支持这3种,Tomcat8增加了对NIO2的支持,而到了Tomcat8.5和Tomcat9.0,则去掉了对BIO的支持。
如果没有指定Protocal,则使用默认值HTTP/1.1,其含义如下:在Tomcat7中,自动选取使用BIO或APR(如果找到ARP需要的本地库,则使用ARP,否则使用BIO;在Tomcat8,自动选取使用NIO或ARP(如果找到APR需要的本地库,则使用APR,否则使用NIO。
参考链接:http://www.cnblogs.com/kismetv/p/7806063.html
socket请求经过tomcat的线程处理后,会封装成RequestFacade然后到web.xml配置文件
通过配置先初始化springMVC容器,进入DispatcherServlet类,进行相关的初始化工作,这部分初始化我只了解了一点点,先将我现在理解的写出来,进入这个类,会先调用它的有参或者无参构造方法,然后进入它的父类或者子类的方法初始化spring容器,将handlerMethod和url进行关联,也就是在dispatcherServlet中有个onRefresh方法会执行初始化方法initStrategies
该方法的结构如下:
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context);
this.initLocaleResolver(context);
this.initThemeResolver(context);
this.initHandlerMappings(context);
this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
我了解的是HandlerMappings这部分,将handlerMappings初始化好会先调用一开始结构图中的doService方法,然后才到doDispatch方法,doDispatch方法是springMVC的核心方法之一。
rotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
//判断request是否是文件上传的
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//通过HandlerMappings迭代器,选出合适的HandlerMapping,然后调用这个对象的getHandler获取Handler的执行链
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//mappedHandler.getHandler()类型是Object,但是可以转换成HandlerMethod,之前初始化讲到过这个部分
//通过handlerAdapter的方法supports来获取相对应的handlerAdapter(Hanlder的适配器)
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (this.logger.isDebugEnabled()) {
this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
//测试所有的拦截器,有一个失败,则请求失败
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//绑定请求到视图上
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
//传输到前端页面
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
if (mv != null && !mv.hasView()) {
mv.setViewName(this.getDefaultViewName(request));
}
}
这部分代码最重要的就是获取HandlerExecutorChain和HandlerAdaptor这俩部分
HandlerExecutorChain是Handler的执行链,获取对应handler
首先进行迭代HandlerMappings,然后根据传入的request,调用HandlerMapping的方法getHandler(request),
该方法在AbstrateHandlerMapping类下,具体的代码如下:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//获取内部的handler
Object handler = this.getHandlerInternal(request);
if (handler == null) {
handler = this.getDefaultHandler();
}
if (handler == null) {
return null;
} else {
if (handler instanceof String) {
String handlerName = (String)handler;
handler = this.getApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, request);
CorsConfiguration config = globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig;
executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
}
这部分代码中有个方法getHandlerInternal(request)方法是为了获取内部的handler的,这个方法在AbstrateHandlerMethodMapping类中,这部分代码如下:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//获取request映射到的url地址
String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Looking up handler method for path " + lookupPath);
}
//设置读锁
this.mappingRegistry.acquireReadLock();
HandlerMethod var4;
try {
//取出该地址相关的handlerMethod
HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
if (this.logger.isDebugEnabled()) {
if (handlerMethod != null) {
this.logger.debug("Returning handler method [" + handlerMethod + "]");
} else {
this.logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;
} finally {
this.mappingRegistry.releaseReadLock();
}
return var4;
}
之前初始化的时候,我们说了,会将url和handler关联起来,这里代码里的handlerMethod其实就是handler如下图:


这个方法返回的变量也是设置成为了handler
获取内部handler方法getHandlerInternal(request)这部分讲完了,下一部分是方法getHandlerExecutionChain(handler,request),该方法是将相关的拦截器关联到这个handler上,代码如下:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = handler instanceof HandlerExecutionChain ? (HandlerExecutionChain)handler : new HandlerExecutionChain(handler);
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
Iterator var5 = this.adaptedInterceptors.iterator();
while(var5.hasNext()) {
HandlerInterceptor interceptor = (HandlerInterceptor)var5.next();
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
} else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
到这里就完成了一个handlerExcetionChain的创建,下一步是找到相关的handler适配器
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
Iterator var2 = this.handlerAdapters.iterator();
HandlerAdapter ha;
do {
if (!var2.hasNext()) {
throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
ha = (HandlerAdapter)var2.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Testing handler adapter [" + ha + "]");
}
} while(!ha.supports(handler));
return ha;
}
这个方法参数是将前面获得的handler执行链的handler,调用handlerExcutionChain.getHandler()的方法返回一个object类型
进入handlerAdapter.supports(handler)方法

前面的条件是判断handler是否是HandlerMethod的实例,这个地方我们回想一下,在前边获取HandlerMethod的时候,我们将这个类型转换成了Object类型,那么考虑一下,现在这种情况下,handler是否依然是HandlerMethod的实例呢?答案是肯定的,还是HandlerMethod的实例
测试代码如下:

控制台输出如下:

因为Object是所有类的父类,所以可以子类转换成父类,也可以将父类转换成之前对应的子类。是同一个实例
好了言归正传,这个方法还有后一个条件,supportsInternal((HandlerMethod)handler)这个方法的代码如下:

这个条件的作用是干嘛的,我现在还不清楚,看方法里的意思是只要是handlerMethod的参数,就会返回一个true
经过这两个条件的判断,返回一个布尔值到getHandlerAdapter方法中,主要找到一个适配器,就会自动跳出循环,返回handler适配器。
handler的执行链和handler的适配器都有的情况下,接下来是将handler执行链是否能够成功通过拦截器,只要有一个失败,那么此次请求失败,代码语句是:
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
然后通过handler适配器进行视图的处理,handlerAdapter.handle(processedRequest, response, mappedHandler.getHandler())
返回一个ModelAndView的对象
最后通过
processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
方法将mv对象反馈到前端页面。
总结
上边说了那么多,如果没看过源码,应该看起来比较吃力,下边将本次的内容进行总结
1、http请求在tcp层被封装成socket请求发送给tomcat,tomcat用acceptor来接收socket请求,然后根据具体的协议来进行相对应的处理,再交付给线程,tomcat将request打包成requestFacade,经过doFilter重重的过滤器,到达HttpServlet类,调用service(request,response)方法传入请求和响应对象,判断请求类型,然后调用doGet或者doPost方法,接着到FrameworkServlet的processRequest()方法中,调用doService(),再在这个方法中调用doDispatch()方法,至此,完成了请求到分发器的步骤。
2、请求到达分发器,先判断请求是否是文件上传,文件上传则请求失败,否则通过HandlerMappings迭代器迭代出当前请求对应的handlerMapping,判断是否匹配请求,需要调用handlerMapping.getHandler(request)方法,在这个方法首先调用getHandlerInternal(request)判断根据路径是否能够获取到对应的handler(handlerMethod),如果不能获取到,则设置一个默认的handler,注意在这个方法里边加了一层读锁,接着调用getHandlerExecutionChain(handler,request)方法获取handler的执行链,这个方法实际上是给handler执行链增加拦截器的作用,然后判断一下是否跨域,最后返回匹配好的handler执行链
3、handler执行链获取成功后,进行第三个重要的步骤,获取handler的适配器,调用getHandlerAdapter()方法,传入handlerExecutionChain.getHandler()作为参数,获取适配器方法的内部使用do...while(条件)的方式迭代出匹配的适配器,判断是否匹配,使用HandlerAdapter.supports(handler)作为条件,这个方法返回的是一个布尔值,如果传入的handler是HandlerMethod的实例,则返回true,跳出循环,匹配成功。
4、关于ModelAndView这块,这部分内容是根据HandlerExecutionChain和HandlerAdapter二者进行进一步的判定,然后反馈给前端信息,这部分我还没怎么看,现在无法总结。
网友评论