代码地址:
https://gitee.com/chenscript/mybatis_learning.git
将SSM项目用tomcat启动,你会看到日志的第一行是
D:\tomcat8.5.50\apache-tomcat-8.5.50\bin\catalina.bat run
[2019-12-28 10:20:09,678] Artifact SSM:war exploded: Waiting for server connection to start artifact deployment…
Using CATALINA_BASE: “C:\Users\Administrator.IntelliJIdea2017.3\system\tomcat\Unnamed_parent”
Using CATALINA_HOME: “D:\tomcat8.5.50\apache-tomcat-8.5.50”
Using CATALINA_TMPDIR: “D:\tomcat8.5.50\apache-tomcat-8.5.50\temp”
Using JRE_HOME: “D:\Java\jdk1.8.0_161”
Using CLASSPATH: “D:\tomcat8.5.50\apache-tomcat-8.5.50\bin\bootstrap.jar;D:\tomcat8.5.50\apache-tomcat-8.5.50\bin\tomcat-juli.jar”
这里就是我们的入口:catalina.bat 。 在代码里,你会发现实质启动的是tomcat的一个类:Bootstrap。
tomcat Bootstrap源码:
如何调试tomcat源码? --> 直接在Bootstrap中右键运行main()就好了。(记得ant-build一下)
参考链接:
https://blog.csdn.net/yekong1225/article/details/81000446
有些单元测试会报错,但是可以注释掉。
tomcat是最腰间盘突出的设计模式就是状态设计模式(参考start()方法),采用netty方式实现获取请求,发送请求的。
直奔主题吧。tomcat如何实现servlet的?
先看tomcat容器关系图:
来自极客时间的图。
可以看到Servlet是在最底层的,所以跟着代码走,直接找到Wrapper的代码。假设代码使用的是JSP,那么这个Wrapper就是JspServletWrapper。(因为在tomcat代码有这包,方便看代码)
如果认真起来找的话,还追溯到这些类上,跟着上面的图一个个套进去找吧。
可以参考调用栈这里一步步调试。
在JspServlet的init()方法中,核心方法是
wrapper.service(request, response, precompile);
进行编译、加载文件、处理一定数量的加载的jsp、处理单线程请求。
当然,核心 是这个方法 wrapper.service() 再调用了 servlet.service(request, response); 这样就最终还是回到了原生httpservlet上进行doget,dopost请求了。
以 JspServletWrapper.service() 逻辑为例:
(1) Compile 编译文件
(2) (Re)load servlet class file 加载或重加载servlet类
(3) Handle limitation of number of loaded Jsps 处理一定数量的加载完的jsp页面
(4) Service request 处理页面请求
以上是关于tomcat代码中处理servlet的一些逻辑。详细的源码还需要另起专题研究,这里就引导一下吧。
下面开始SSM部分的分析。
web.xml的解析在tomcat源码中解析
org.apache.catalina.startup.ContextConfig.configureContext(WebXml webxml)
The created application context will be registered into the ServletContext under
the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
and the Spring application context will be closed when the {@link #contextDestroyed} lifecycle method is invoked on this listener
public void addServlet(ServletDef servletDef) {
servlets.put(servletDef.getServletName(), servletDef);
if (overridable) {
servletDef.setOverridable(overridable);
}
}
这是在通过WebXmlParser的digester.parse(source);解析后,调用的方法。 以后使用就直接调用getServlet()就是了。
然后就通过像JspServletWrapper.service()的操作进行解析。
以上就是使用的一些标签的解析。
这节就看SSM源码吧。
久闻DispatcherServlet()的九大组件,这个类的工作是要依赖这九大组件进行的,所以,所有工作当然是从初始化开始了。
在初始化的地方打上断点,可以看到调用链如下:
从下往上看,主要有几步:
Servlet的loadOnStartup,获取即时加载的servlet.
init()—GenericServlet
initWebApplicationContext ----- FrameworkServlet
createWebApplicationContext ----FrameworkServlet
configureAndRefreshWebApplicationContext ----FrameworkServlet
publishEvent
OnApplicationEvent
onRefresh()
解析一下: 就是要创建一个上下文servlet,但要创建这个上下文之前,需要与spring容器结合,所以就要先创建springWeb容器,也就是createWebApplicationContext,也就是调用了springIOC的refresh()。当完成spring容器之后,再提出事件发布,让FrameworkServlet中监听ApplicationEvent的观察者进行工作。如图。
这里就开始了DispatcherServlet的旅程。 (绕了一大圈啊,发现这文章越写越长了。。)
onRefresh()下的方法就是九大组件的初始化策略。
九大组件有哪些呢?
MultipartResolver:文件、多媒体等解析器
LocaleResolver: 其主要作用在于根据不同的用户区域展示不同的视图
ThemeResolver:Spring MVC的主题就是一些静态资源的集合,即包括样式及图片,用来控制应用的视觉风格。
HandlerMappings:就是 请求地址与请求方法映射了
HandlerAdapters:Handler的适配器,用来处理请求与Handler故事。
HandlerExceptionResolvers: 处理异常的处理器
RequestToViewNameTransLator: 请求与视图名的绑定
ViewResolver: 视图解析器
FlashMapManager:session管理器。 使用模板方法,实现一个SessionFlashMapManager管理request的session
这个初始化过程就是从spring容器中获取一个名叫以上九个组件的实例,另外就是要把DispatcherServlet的默认策略加入到各个组件中。
发送一个请求,如果用原生的servlet进行使用的话,那就是会直接走doGet或者doPost方法。所以,我们现在的目标也这么搞。在SSM项目中的Controller请求第一行打断点,直接跑调试。看方法调用栈!
看上面几步,
具体分析一下吧~
DispatcherServlet的继承图如下
所以调用方法的时候会子类优先嘛,去找DispatcherServlet的service()方法,但发现只有FrameworkServlet实现了这个方法。再看,我们使用的是doGet()方法,还是要走else分支,跑到父类HttpServletBean上找,无果,再往上找到HttpServlet,找到了。
doGet(),doPost()截图不到。。。
点开doGet(),找到子类实现的doGet()
在processRequest中算是做了三件事情吧。
1.解决语言区域问题
2.处理请求和响应
3.推送请求处理完毕的事件
我们重点看请求处理的部分的。
处理分两部分:
1.将全局属性(webApplicationContext,localeResolver,themeResolver,themeSource,flashManager)注入到request中
2.分发request,也就是doDispatch();
1.检查是否有文件|图片等
2.mappedHandler = getHandler(processedRequest); 获取mappingHandler
3.根据获得的handler获取适配器 HandlerAdapter
4.处理拦截器请求的preHandler
5.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());使用handlerAdapter让handler处理请求request,返回一个ModelAndView对象
6.查找并使用默认的视图名字
7.处理拦截器postHandler方法
8.processDispatchResult 处理请求后的结果,包括异常 。
到此。后面还有一步就是发送通知事件,我这个请求可以结束了,请大家知悉。然后退场。。