目录
用户请求/响应
浏览器与服务器之间交互,是浏览器通过与服务器建立TCP连接,并通过http的应用层协议,来传递请求数据和响应数据:
从图上你可以看到,这个过程是:
Tomcat 作为一个 HTTP 服务器,在这个过程中都做了些什么事情呢?
主要是接受连接、解析请求数据、处理请求和发送响应这几个步骤。这里请你注意,可能有成千上万的浏览器同时请求同一个 HTTP 服务器,因此 Tomcat 和 Jetty 为了提高服务的能力和并发度,往往会将自己要做的几个事情并行化(线程模型),具体来说就是使用多线程的技术。
Servlet技术
早期的 Web 应用主要用于浏览新闻等静态页面,HTTP 服务器(比如 Apache、Nginx)向浏览器返回静态 HTML,浏览器负责解析 HTML,将结果呈现给用户。
随着互联网发展,用户不满足于仅仅浏览静态页面,还希望有交互,有动态效果,因此也就需要一些扩展机制能够让 HTTP 服务器调用服务端程序。
于是 Sun 公司推出了 Servlet 技术。你可以把 Servlet 简单理解为运行在服务端的 Java 小程序,但是 Servlet 没有 main 方法,不能独立运行,因此必须把它部署到 Servlet 容器中,由容器来实例化并调用 Servlet。
而 Tomcat 和 Jetty 就是一个 Servlet 容器。为了方便使用,它们也具有 HTTP 服务器的功能,因此 Tomcat 或者 Jetty 就是一个“HTTP 服务器 + Servlet 容器”,我们也叫它们 Web 容器。
SpringMVC
MVC框架的意义就是为了让请求处理,和数据展示隔离
Springmvc就是典型的mvc框架、也是控制层框架,M就是javaBean、V就是view、C就是controller
SpringMVC整体流程
视图解析器:就是拿着ModeAndView中的逻辑视图名,给它拼接前缀后缀变成真实地址的视图名
springmvc整体调用栈
处理器适配器有很多种类型,所以这里就定义了HanderAdapter接口,然后所有实现类进行遍历看supports哪个
这一步就是用来执行拦截器的
这步是拦截器的后置步骤
这里就是定义接口的意义之一,统一了方法调用的入口,统一了方法名
视图渲染
最后通过视图对象,决定是转发还是重定向,把请求的响应html放入response对象中返回给客户端
如果不是上面的实现Controller接口的方式,而是我们平常开发用的@Controller注解的方式
这就是另一套执行逻辑,
以后如果又有新的控制器类型,就是既不是实现Controller接口的方式,也不是是我们平常开发用的@Controller注解的方式,那么我们就重新写一套新的处理器、处理器适配器就好了,上层的DispatcherServlet#doDispatch()的核心逻辑是一直不用变化的
DispatcherServlet#doDispatch核心逻辑
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
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;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
/**
内部会调用视图View.render()进行视图渲染
*/
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
从这里可以看到,拦截器的前置执行和后置执行还都是在DispatcherServlet#doDispatch中执行调用的,而真正的处理器的执行,还是通过处理器适配器来调用执行的
这么设计的原因,就是因为拦截器的接口是统一的,而处理器的类型是多样的,有多种不同的处理器类型这些处理器要被编排到统一的执行流程中去,就需要一套适配器,把不同的处理器适配到同一套执行接口中去,最后DispatcherServlet#doDispatch就只用针对这一套统一的处理器执行接口进行编程就好了
比如新加的一种处理器类型,这种处理器是没有返回值的
这种处理器,就对应的适配器就是:
通过以上,
RequestMappingHandlerAdapter内部就会调用处理器,也就是@Controller注解标注的类中的一些方法,最终也还是通过反射来调用到这些方法的
各个组件的初始化逻辑
RequestMappingHandlerAdapter
初始化逻辑,其中就会初始化 RequestMappingHandlerAdapter
这里就把所有的地址与controller的映射关系,就都初始化绑定好了
拦截器执行流程
需要声明拦截器
这里明确的指明了每个拦截器要拦截的具体url
这里开始遍历执行拦截器链中的拦截器
如果这里有某一个拦截器返回的是false,那么136行的if就是true,从而拦截器链就执行终止直接返回了,也就不会继续往后执行真正的处理器了
参数绑定
如何把url中带的参数值,解析出来,并绑定到这个方法的参数name中去
底层还是用了servlet的api来获取到参数值
Tomcat的连接数和线程数的区别
view对象的意义
参考文献
极客时间:李号双老师的《深入拆解Tomcat & Jetty》
在这里,强烈推荐李老师这门课程,高屋建瓴的讲解Tomcat,读完身心愉悦